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 %} -
- {% csrf_token %} - - - - -
- {% 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 %} +
+ {% csrf_token %} + + + + +
+ {% 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 @@