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

@@ -414,10 +414,34 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
for cp in self._positions_for_questions:
answ = {
aw.question_id: aw.answer for aw in cp.answerlist
aw.question_id: aw for aw in cp.answerlist
}
question_cache = {
q.pk: q for q in cp.item.questions_to_ask
}
def question_is_visible(parentid, qval):
parentq = question_cache[parentid]
if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_value):
return False
if parentid not in answ:
return False
if qval == 'True':
return answ[parentid].answer == 'True'
elif qval == 'False':
return answ[parentid].answer == 'False'
else:
return qval in [o.identifier for o in answ[parentid].options.all()]
def question_is_required(q):
return (
q.required and
(not q.dependency_question_id or question_is_visible(q.dependency_question_id, q.dependency_value))
)
for q in cp.item.questions_to_ask:
if q.required and q.id not in answ:
print("question", q, "is required", question_is_required(q), "has answer", q.id in answ)
if question_is_required(q) and not answ.get(q.id):
if warn:
messages.warning(request, _('Please fill in answers to all required questions.'))
return False

View File

@@ -26,6 +26,7 @@
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.js" %}"></script>
<script type="text/javascript" src="{% static "datetimepicker/bootstrap-datetimepicker.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/jquery.qrcode.min.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/questions.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/main.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>

View File

@@ -65,7 +65,7 @@
</h4>
</summary>
<div>
<div class="panel-body" data-idx="{{ forloop.counter0 }}">
<div class="panel-body questions-form" data-idx="{{ forloop.counter0 }}">
{% if pos.addons.all %}
<div class="form-group">
<label class="col-md-3 control-label">
@@ -97,7 +97,7 @@
{# Add-Ons #}
<legend>+ {{ form.pos.item }}</legend>
{% endif %}
{% bootstrap_form form layout="horizontal" %}
{% bootstrap_form form layout="checkout" %}
{% endfor %}
</div>
</div>

View File

@@ -53,13 +53,13 @@
</h4>
</summary>
<div id="cp{{ pos.id }}">
<div class="panel-body">
<div class="panel-body questions-form">
{% for form in forms %}
{% if form.pos.item != pos.item %}
{# Add-Ons #}
<legend>+ {{ form.pos.item }}</legend>
{% endif %}
{% bootstrap_form form layout="horizontal" %}
{% bootstrap_form form layout="checkout" %}
{% endfor %}
</div>
</div>

View File

@@ -13,7 +13,7 @@ class QuestionsViewMixin(BaseQuestionsViewMixin):
@cached_property
def _positions_for_questions(self):
qqs = Question.objects.all()
qqs = self.request.event.questions.all()
if self.only_user_visible:
qqs = qqs.filter(ask_during_checkin=False)
cart = get_cart(self.request).select_related(
@@ -33,7 +33,7 @@ class QuestionsViewMixin(BaseQuestionsViewMixin):
Question.objects.none(),
to_attr='dummy'
)))
),
).select_related('dependency_question'),
to_attr='questions_to_ask')
)
return sorted(list(cart), key=self._keyfunc)