diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py
index e74e450e8c..c526dbc6e4 100644
--- a/src/pretix/base/models/orders.py
+++ b/src/pretix/base/models/orders.py
@@ -1426,6 +1426,12 @@ class QuestionAnswer(models.Model):
else:
return self.answer
+ def to_dependency_values(self):
+ if self.question.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
+ return [o.identifier for o in self.options.all()]
+ elif self.question.type in (Question.TYPE_BOOLEAN, Question.TYPE_COUNTRYCODE):
+ return self.answer
+
def save(self, *args, **kwargs):
if self.orderposition and self.cartposition:
raise ValueError('QuestionAnswer cannot be linked to an order and a cart position at the same time.')
@@ -1570,53 +1576,80 @@ class AbstractPosition(RoundingCorrectionMixin, models.Model):
def cache_answers(self, all=True):
"""
- Creates two properties on the object.
- (1) answ: a dictionary of question.id → answer string
- (2) questions: a list of Question objects, extended by an 'answer' property
+ Creates a new property on the object:
+ questions: a list of Question objects, extended by an 'answer' property
"""
- self.answ = {}
- for a in getattr(self, 'answerlist', self.answers.all()): # use prefetch_related cache from get_cart
- self.answ[a.question_id] = a
-
# We need to clone our question objects, otherwise we will override the cached
# answers of other items in the same cart if the question objects have been
# selected via prefetch_related
if not all:
- if hasattr(self.item, 'questions_to_ask'):
- questions = list(copy.copy(q) for q in self.item.questions_to_ask)
+ if hasattr(self.item, 'relevant_questionnaires'):
+ children = list(copy.copy(qc) for qq in self.item.relevant_questionnaires for qc in qq.childlist)
else:
- questions = list(copy.copy(q) for q in self.item.questions.filter(ask_during_checkin=False,
- hidden=False))
+ children = list(copy.copy(qc) for qq in self.item.questionnaires.filter(type='PS') for qc in qq.children.all())
else:
- questions = list(copy.copy(q) for q in self.item.questions.all())
+ children = list(copy.copy(qc) for qq in self.item.questionnaires.filter(type__startswith='P') for qc in qq.children.all())
- question_cache = {
- q.pk: q for q in questions
+ qc_cache = {
+ q.pk: q for q in children
}
- def question_is_visible(parentid, qvals):
- if parentid not in question_cache:
+ def qc_is_visible(parentid, qvals):
+ if parentid not in qc_cache:
return False
- parentq = question_cache[parentid]
- if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_values):
+ parentqc = qc_cache[parentid]
+ if parentqc.dependency_question_id and not qc_is_visible(parentqc.dependency_question_id, parentqc.dependency_values):
return False
- if parentid not in self.answ:
- return False
- return (
- ('True' in qvals and self.answ[parentid].answer == 'True')
- or ('False' in qvals and self.answ[parentid].answer == 'False')
- or (any(qval in [o.identifier for o in self.answ[parentid].options.all()] for qval in qvals))
- )
+ answer_values = self.get_dependency_answer_values(parentqc)
+ return any(qval in answer_values for qval in qvals)
self.questions = []
- for q in questions:
- if q.id in self.answ:
- q.answer = self.answ[q.id]
- q.answer.question = q # cache object
+ for qc in children:
+ if qc.user_question_id and qc.user_question_id in self.answer_cache:
+ qc.answer = self.answer_cache[qc.user_question_id]
+ #qc.answer.question = qc # cache object
+ elif qc.system_question:
+ qc.answer = self.get_system_answer(qc.system_question)
+ #qc.answer.question = qc # cache object
else:
- q.answer = ""
- if not q.dependency_question_id or question_is_visible(q.dependency_question_id, q.dependency_values):
- self.questions.append(q)
+ qc.answer = ""
+ if not qc.dependency_question_id or qc_is_visible(qc.dependency_question_id, qc.dependency_values):
+ self.questions.append(qc)
+
+ @cached_property
+ def answer_cache(self):
+ return {
+ aw.question_id: aw for aw in getattr(self, 'answerlist', self.answers.all())
+ }
+
+ def get_dependency_answer_values(self, qc):
+ if qc.user_question_id:
+ if qc.user_question_id not in self.answer_cache:
+ return None
+ answer = self.answer_cache[qc.user_question_id]
+ return answer.to_dependency_values()
+ elif qc.system_question:
+ return [self.get_system_answer(qc.system_question)]
+ else:
+ raise ValueError('Questionnaire child without question has no answer')
+
+ def get_system_answer(self, system_question_name):
+ if system_question_name == 'attendee_name_parts':
+ return self.attendee_name_parts
+ elif system_question_name == 'attendee_email':
+ return self.attendee_email
+ elif system_question_name == 'street':
+ return self.street
+ elif system_question_name == 'zipcode':
+ return self.zipcode
+ elif system_question_name == 'city':
+ return self.city
+ elif system_question_name == 'state':
+ return self.state
+ elif system_question_name == 'country':
+ return self.country
+ else:
+ raise ValueError('Unknown system question name')
@property
def net_price(self):
diff --git a/src/pretix/base/templates/pretixbase/redirect.html b/src/pretix/base/templates/pretixbase/redirect.html
index 6c8404b4dd..a409b52838 100644
--- a/src/pretix/base/templates/pretixbase/redirect.html
+++ b/src/pretix/base/templates/pretixbase/redirect.html
@@ -8,7 +8,7 @@
{% trans "Redirect" %}
- {% blocktrans trimmed with host=""|add:hostname|add:""|safe %}
+ {% blocktrans trimmed with host=bold_hostname %}
The link you clicked on wants to redirect you to a destination on the website {{ host }}.
{% endblocktrans %}
{% blocktrans trimmed %}
diff --git a/src/pretix/base/views/redirect.py b/src/pretix/base/views/redirect.py
index 687338f4a8..a33e74e9ce 100644
--- a/src/pretix/base/views/redirect.py
+++ b/src/pretix/base/views/redirect.py
@@ -25,6 +25,7 @@ from django.core import signing
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
+from django.utils.html import format_html
def _is_samesite_referer(request):
@@ -52,6 +53,7 @@ def redir_view(request):
u = urllib.parse.urlparse(url)
return render(request, 'pretixbase/redirect.html', {
'hostname': u.hostname,
+ 'bold_hostname': format_html("{}", u.hostname),
'url': url,
})
diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html
index 37e6171c6f..e8cfa1fd4b 100644
--- a/src/pretix/control/templates/pretixcontrol/order/index.html
+++ b/src/pretix/control/templates/pretixcontrol/order/index.html
@@ -602,60 +602,9 @@
{% endif %}
{% if line.has_questions %}
- {% if line.item.ask_attendee_data and event.settings.attendee_names_asked %}
- - {% trans "Attendee name" %}
- - {% if line.attendee_name %}{{ line.attendee_name_all_components }}{% else %}
- {% trans "not answered" %}{% endif %}
- {% endif %}
- {% if line.item.ask_attendee_data and event.settings.attendee_emails_asked %}
- - {% trans "Attendee email" %}
- -
- {% if line.attendee_email %}
- {{ line.attendee_email }}
- {% if not line.addon_to %}
-
- {% endif %}
- {% else %}
- {% trans "not answered" %}
- {% endif %}
-
- {% endif %}
- {% if line.item.ask_attendee_data and event.settings.attendee_company_asked %}
- -
- {% trans "Attendee company" %}
-
- -
- {% if line.company %}{{ line.company }}{% else %}{% trans "not answered" %}{% endif %}
-
- {% endif %}
- {% if line.item.ask_attendee_data and event.settings.attendee_addresses_asked %}
- -
- {% trans "Attendee address" %}
-
- -
- {% if line.street or line.zipcode or line.city or line.country %}
- {{ line.street|default_if_none:""|linebreaksbr }}
- {{ line.zipcode|default_if_none:"" }} {{ line.city|default_if_none:"" }}
- {% if line.state %}{{ line.state_for_address }}
{% endif %}
- {{ line.country.name|default_if_none:"" }}
- {% else %}
- {% trans "not answered" %}
- {% endif %}
-
- {% endif %}
{% for q in line.questions %}
-
- {{ q.question }}
+ {{ q.label }}
{% if q.ask_during_checkin %}
{% trans "not answered" %}
{% endif %}
+ {% if q.system_question == "attendee_email" and not line.addon_to %}
+
+ {% endif %}
{% endfor %}
{% for q in line.additional_fields %}
diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py
index 76c4886246..d2d0e4d939 100644
--- a/src/pretix/presale/checkoutflow.py
+++ b/src/pretix/presale/checkoutflow.py
@@ -1060,26 +1060,18 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
return False
for cp in self._positions_for_questions:
- answ = {
- aw.question_id: aw for aw in cp.answerlist
- }
- question_cache = {
- q.pk: q for q in cp.item.questions_to_ask
+ qc_cache = {
+ qc.pk: qc for qq in cp.item.relevant_questionnaires for qc in qq.childlist
}
def question_is_visible(parentid, qvals):
- if parentid not in question_cache:
+ if parentid not in qc_cache:
return False
- parentq = question_cache[parentid]
- if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_values):
+ parentqc = qc_cache[parentid]
+ if parentqc.dependency_question_id and not question_is_visible(parentqc.dependency_question_id, parentqc.dependency_values):
return False
- if parentid not in answ:
- return False
- return (
- ('True' in qvals and answ[parentid].answer == 'True')
- or ('False' in qvals and answ[parentid].answer == 'False')
- or (any(qval in [o.identifier for o in answ[parentid].options.all()] for qval in qvals))
- )
+ answer_values = cp.get_dependency_answer_values(parentqc)
+ return any(qval in answer_values for qval in qvals)
def question_is_required(q):
return (
@@ -1088,31 +1080,16 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
)
if not self.all_optional:
- for q in cp.item.questions_to_ask:
- if question_is_required(q) and q.id not in answ:
- if warn:
- messages.warning(request, _('Please fill in answers to all required questions.'))
- return False
- if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_names_required', as_type=bool) \
- and not cp.attendee_name_parts:
- if warn:
- messages.warning(request, _('Please fill in answers to all required questions.'))
- return False
- if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_emails_required', as_type=bool) \
- and cp.attendee_email is None:
- if warn:
- messages.warning(request, _('Please fill in answers to all required questions.'))
- return False
- if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_company_required', as_type=bool) \
- and cp.company is None:
- if warn:
- messages.warning(request, _('Please fill in answers to all required questions.'))
- return False
- if cp.item.ask_attendee_data and self.request.event.settings.get('attendee_addresses_required', as_type=bool) \
- and (cp.street is None and cp.city is None and cp.country is None):
- if warn:
- messages.warning(request, _('Please fill in answers to all required questions.'))
- return False
+ for qq in cp.item.relevant_questionnaires:
+ for qc in qq.childlist:
+ if qc.user_question_id and question_is_required(qc) and qc.user_question_id not in cp.answer_cache:
+ if warn:
+ messages.warning(request, _('Please fill in answers to all required questions.'))
+ return False
+ if qc.system_question and question_is_required(qc) and not cp.get_system_answer(qc.system_question):
+ if warn:
+ messages.warning(request, _('Please fill in answers to all required questions.'))
+ return False
responses = question_form_fields.send(sender=self.request.event, position=cp)
form_data = cp.meta_info_data.get('question_form_data', {})
diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html
index b0ae79bdf5..f3e1e8420a 100644
--- a/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html
+++ b/src/pretix/presale/templates/pretixpresale/event/fragment_cart.html
@@ -204,7 +204,7 @@
{% endif %}
{% endif %}
{% for q in line.questions %}
-
- {{ q.question }}
+ - {{ q.label }}
-
{% if q.answer %}
{% if q.answer.file %}
diff --git a/src/pretix/static/pretixcontrol/js/ui/questionnaires/App.vue b/src/pretix/static/pretixcontrol/js/ui/questionnaires/App.vue
index fd3d443781..60eab68c8c 100644
--- a/src/pretix/static/pretixcontrol/js/ui/questionnaires/App.vue
+++ b/src/pretix/static/pretixcontrol/js/ui/questionnaires/App.vue
@@ -1,15 +1,32 @@