use questionnaire data for rendering checkout questions step

This commit is contained in:
Mira Weller
2026-06-17 16:33:58 +02:00
parent 9467f1d08d
commit 5b34a25fb5
4 changed files with 72 additions and 75 deletions
+31 -47
View File
@@ -650,7 +650,7 @@ class BaseQuestionsForm(forms.Form):
orderpos = self.orderpos = kwargs.pop('orderpos', None)
pos = cartpos or orderpos
item = pos.item
questions = pos.item.questions_to_ask
questionnaires = pos.item.relevant_questionnaires
event = kwargs.pop('event')
self.all_optional = kwargs.pop('all_optional', False)
self.attendee_addresses_required = event.settings.attendee_addresses_required and not self.all_optional
@@ -696,38 +696,13 @@ class BaseQuestionsForm(forms.Form):
validators=([MaxDateTimeValidator(max_date)] if max_date else []) + [MinDateTimeValidator(min_date)]
)
add_fields = {}
if item.ask_attendee_data and event.settings.attendee_names_asked:
add_fields['attendee_name_parts'] = self.build_system_question_field(request, event, pos, 'attendee_name_parts')
if item.ask_attendee_data and event.settings.attendee_emails_asked:
add_fields['attendee_email'] = self.build_system_question_field(request, event, pos, 'attendee_email')
if item.ask_attendee_data and event.settings.attendee_company_asked:
add_fields['company'] = self.build_system_question_field(request, event, pos, 'company')
if item.ask_attendee_data and event.settings.attendee_addresses_asked:
add_fields['street'] = self.build_system_question_field(request, event, pos, 'street')
add_fields['zipcode'] = self.build_system_question_field(request, event, pos, 'zipcode')
add_fields['city'] = self.build_system_question_field(request, event, pos, 'city')
add_fields['country'] = self.build_system_question_field(request, event, pos, 'country')
add_fields['state'] = self.build_system_question_field(request, event, pos, 'state')
field_positions = list(
[
(n, event.settings.system_question_order.get(n if n != 'state' else 'country', 0))
for n in add_fields.keys()
]
)
for q in questions:
field = self.build_user_question_field(request, event, pos, q)
add_fields['question_%s' % q.id] = field
field_positions.append(('question_%s' % q.id, q.position))
field_positions.sort(key=lambda e: e[1])
for fname, p in field_positions:
self.fields[fname] = add_fields[fname]
for questionnaire in questionnaires:
for child in getattr(questionnaire, 'childlist', questionnaire.children.all()):
if child.user_question:
q = child.user_question
self.fields['question_%s' % q.id] = self.build_user_question_field(request, event, pos, q)
elif child.system_question:
self.fields[child.system_question] = self.build_system_question_field(request, event, pos, child)
responses = question_form_fields.send(sender=event, position=pos)
data = pos.meta_info_data
@@ -752,20 +727,23 @@ class BaseQuestionsForm(forms.Form):
else:
v.widget.attrs['autocomplete'] = 'section-{} '.format(self.prefix) + autocomplete
def build_system_question_field(self, request, event, pos, field_name):
def build_system_question_field(self, request, event, pos, qc):
field_name = qc.system_question
if field_name == 'attendee_name_parts':
return NamePartsFormField(
max_length=255,
required=event.settings.attendee_names_required and not self.all_optional,
required=qc.required and not self.all_optional,
scheme=event.settings.name_scheme,
titles=event.settings.name_scheme_titles,
label=_('Attendee name'),
label=qc.label,
help_text=qc.help_text,
initial=pos.attendee_name_parts,
)
if field_name == 'attendee_email':
return forms.EmailField(
required=event.settings.attendee_emails_required and not self.all_optional,
label=_('Attendee email'),
required=qc.required and not self.all_optional,
label=qc.label,
help_text=qc.help_text,
initial=pos.attendee_email,
widget=forms.EmailInput(
attrs={
@@ -775,16 +753,18 @@ class BaseQuestionsForm(forms.Form):
)
if field_name == 'company':
return forms.CharField(
required=event.settings.attendee_company_required and not self.all_optional,
label=_('Company'),
required=qc.required and not self.all_optional,
label=qc.label,
help_text=qc.help_text,
max_length=255,
initial=pos.company,
)
if field_name == 'street':
return forms.CharField(
required=self.attendee_addresses_required,
label=_('Address'),
required=qc.required and not self.all_optional,
label=qc.label,
help_text=qc.help_text,
widget=forms.Textarea(attrs={
'rows': 2,
'placeholder': _('Street and Number'),
@@ -796,7 +776,8 @@ class BaseQuestionsForm(forms.Form):
return forms.CharField(
required=False,
max_length=30,
label=_('ZIP code'),
label=qc.label,
help_text=qc.help_text,
initial=pos.zipcode,
widget=forms.TextInput(attrs={
'autocomplete': 'postal-code',
@@ -805,7 +786,8 @@ class BaseQuestionsForm(forms.Form):
if field_name == 'city':
return forms.CharField(
required=False,
label=_('City'),
label=qc.label,
help_text=qc.help_text,
max_length=255,
initial=pos.city,
widget=forms.TextInput(attrs={
@@ -817,8 +799,9 @@ class BaseQuestionsForm(forms.Form):
return CountryField(
countries=CachedCountries
).formfield(
required=self.attendee_addresses_required,
label=_('Country'),
required=qc.required and not self.all_optional,
label=qc.label,
help_text=qc.help_text,
initial=country,
widget=forms.Select(attrs={
'autocomplete': 'country',
@@ -845,7 +828,8 @@ class BaseQuestionsForm(forms.Form):
del self.data[fprefix + 'state']
field = forms.ChoiceField(
label=pgettext_lazy('address', 'State'),
label=qc.label,
help_text=qc.help_text,
required=False,
choices=c,
initial=state,
+1 -1
View File
@@ -37,7 +37,7 @@ from .invoices import Invoice, InvoiceLine, invoice_filename
from .items import (
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaProperty, ItemMetaValue,
ItemProgramTime, ItemVariation, ItemVariationMetaValue, Question,
QuestionOption, Quota, SubEventItem, SubEventItemVariation,
QuestionOption, Questionnaire, QuestionnaireChild, Quota, SubEventItem, SubEventItemVariation,
itempicture_upload_to,
)
from .log import LogEntry
+21 -14
View File
@@ -36,7 +36,7 @@ from pretix.base.forms.questions import (
)
from pretix.base.models import (
CartPosition, InvoiceAddress, OrderPosition, Question, QuestionAnswer,
QuestionOption,
QuestionOption, QuestionnaireChild,
)
from pretix.base.models.customers import AttendeeProfile
from pretix.presale.signals import contact_form_fields_overrides
@@ -307,27 +307,34 @@ class OrderQuestionsViewMixin(BaseQuestionsViewMixin):
@cached_property
def positions(self):
qqs = self.request.event.questions.all()
qqs = self.request.event.questionnaires.all()
if self.only_user_visible:
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
qqs = qqs.filter(type='PS')
else:
qqs = qqs.filter(type__startswith='P')
return list(self.order.positions.select_related(
'item', 'variation'
).prefetch_related(
Prefetch('answers',
QuestionAnswer.objects.prefetch_related('options'),
to_attr='answerlist'),
Prefetch('item__questions',
Prefetch('item__questionnaires',
qqs.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
).select_related('dependency_question'),
to_attr='questions_to_ask')
Prefetch('children', QuestionnaireChild.objects.prefetch_related(
Prefetch('user_question', Question.objects.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
))
),
to_attr='childlist')
),
to_attr='relevant_questionnaires')
))
@cached_property
+19 -13
View File
@@ -54,6 +54,7 @@ from pretix.base.models import (
CartPosition, Customer, InvoiceAddress, ItemAddOn, OrderFee, Question,
QuestionAnswer, QuestionOption, TaxRule,
)
from pretix.base.models.items import QuestionnaireChild
from pretix.base.services.cart import get_fees
from pretix.base.services.pricing import apply_rounding
from pretix.base.templatetags.money import money_filter
@@ -397,8 +398,8 @@ def get_cart(request):
if not cart_id:
request._cart_cache = CartPosition.objects.none()
else:
qqs = request.event.questions.all()
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
qqs = request.event.questionnaires.all()
qqs = qqs.filter(type='PS')
request._cart_cache = CartPosition.objects.filter(
cart_id=cart_id, event=request.event
).annotate(
@@ -419,18 +420,23 @@ def get_cart(request):
Prefetch('answers',
QuestionAnswer.objects.prefetch_related('options'),
to_attr='answerlist'),
Prefetch('item__questions',
Prefetch('item__questionnaires',
qqs.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
).select_related('dependency_question'),
to_attr='questions_to_ask')
Prefetch('children', QuestionnaireChild.objects.prefetch_related(
Prefetch('user_question', Question.objects.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
))
),
to_attr='childlist')
),
to_attr='relevant_questionnaires')
)
by_id = {cp.pk: cp for cp in request._cart_cache}
for cp in request._cart_cache: