Allow dependencies between questions (#1202)

- [x] data model
- [x] api
- [x] backend editor
- [x] backend validation logic
- [x] frontend display logic
- [x] frontend validation logic
- [x] test checkout step
- [x] test modify order in frontend
- [x] test modify order in backend
- [x] validation tests
- [x] correctly evaluate dependency tree in frontend?
- [x] copy events
This commit is contained in:
Raphael Michel
2019-03-13 16:49:20 +01:00
committed by GitHub
parent d10cbd07a7
commit f95e8f374d
22 changed files with 825 additions and 211 deletions

View File

@@ -36,14 +36,39 @@ class CategoryForm(I18nModelForm):
class QuestionForm(I18nModelForm):
question = I18nFormField(
label=_("Question"),
widget_kwargs={'attrs': {'rows': 5}},
widget_kwargs={'attrs': {'rows': 2}},
widget=I18nTextarea
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['items'].queryset = self.instance.event.items.all()
self.fields['dependency_question'].queryset = self.instance.event.questions.filter(
type__in=(Question.TYPE_BOOLEAN, Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE)
)
if self.instance.pk:
self.fields['dependency_question'].queryset = self.fields['dependency_question'].queryset.exclude(
pk=self.instance.pk
)
self.fields['identifier'].required = False
self.fields['help_text'].widget.attrs['rows'] = 3
def clean_dependency_question(self):
dep = val = self.cleaned_data.get('dependency_question')
if dep:
seen_ids = {self.instance.pk} if self.instance else set()
while dep:
if dep.pk in seen_ids:
raise ValidationError(_('Circular dependency between questions detected.'))
seen_ids.add(dep.pk)
dep = dep.dependency_question
return val
def clean(self):
d = super().clean()
if d.get('dependency_question') and not d.get('dependency_value'):
raise ValidationError({'dependency_value': [_('This field is required')]})
return d
class Meta:
model = Question
@@ -55,12 +80,15 @@ class QuestionForm(I18nModelForm):
'required',
'ask_during_checkin',
'identifier',
'items'
'items',
'dependency_question',
'dependency_value'
]
widgets = {
'items': forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice'}
),
'dependency_value': forms.Select,
}

View File

@@ -33,6 +33,7 @@
<script type="text/javascript" src="{% static "charts/morris.js" %}"></script>
<script type="text/javascript" src="{% static "clipboard/clipboard.js" %}"></script>
<script type="text/javascript" src="{% static "rrule/rrule.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/questions.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/jquery.qrcode.min.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/clipboard.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/menu.js" %}"></script>
@@ -70,7 +71,10 @@
</head>
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}"
data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}"
data-pretixlocale="{{ request.LANGUAGE_CODE }}"
data-payment-weekdays-disabled="{{ js_payment_weekdays_disabled }}"
{% if request.organizer %}data-organizer="{{ request.organizer.slug }}"{% endif %}
{% if request.event %}data-event="{{ request.event.slug }}"{% endif %}
data-select2-locale="{{ select2locale }}" data-longdateformat="{{ js_long_date_format }}" class="nojs">
<div id="wrapper">
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">

View File

@@ -21,15 +21,9 @@
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.question layout="control" %}
{% bootstrap_field form.help_text layout="control" %}
{% bootstrap_field form.type layout="control" %}
{% bootstrap_field form.identifier layout="control" %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
{% bootstrap_field form.required layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Apply to products" %}</legend>
{% bootstrap_field form.items layout="control" %}
{% bootstrap_field form.required layout="control" %}
</fieldset>
<div class="alert alert-info alert-required-boolean">
{% blocktrans trimmed %}
@@ -110,6 +104,26 @@
</p>
</div>
</fieldset>
<fieldset>
<legend>{% trans "Advanced settings" %}</legend>
{% bootstrap_field form.help_text layout="control" %}
{% bootstrap_field form.identifier layout="control" %}
{% bootstrap_field form.ask_during_checkin layout="control" %}
<div class="form-group">
<label class="col-md-3 control-label" for="id_dependency_question">
{% trans "Question dependency" %}
<br><span class="optional">{% trans "Optional" context "form" %}</span>
</label>
<div class="col-md-4">
{% bootstrap_field form.dependency_question layout="inline" form_group_class="inner" %}
</div>
<div class="col-md-5">
<script type="text/plain" id="dependency_value_val">{{ form.instance.dependency_value }}</script>
{% bootstrap_field form.dependency_value layout="inline" form_group_class="inner" %}
</div>
</div>
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}