From ececd3e5723e29d8e70d4c17e11b510c976bba16 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Mon, 5 Aug 2024 12:34:15 +0200 Subject: [PATCH] Order search: Provide typing for answers to questions (Z#23160848) (#4350) * Order search: Provide typing for answers to questions (Z#23160848) * Update src/pretix/control/forms/filter.py Co-authored-by: Mira * Update src/pretix/control/forms/filter.py Co-authored-by: Mira * Apply suggestions from code review Co-authored-by: Mira --------- Co-authored-by: Mira --- src/pretix/control/forms/filter.py | 97 ++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 4f26abc8ba..00561ca98e 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -48,6 +48,7 @@ from django.utils.formats import date_format, localize from django.utils.functional import cached_property from django.utils.timezone import get_current_timezone, make_aware, now from django.utils.translation import gettext, gettext_lazy as _, pgettext_lazy +from django_countries.fields import CountryField from django_scopes.forms import SafeModelChoiceField from pretix.base.forms.widgets import ( @@ -60,6 +61,7 @@ from pretix.base.models import ( SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher, ) from pretix.base.signals import register_payment_providers +from pretix.control.forms import SplitDateTimeField from pretix.control.forms.widgets import Select2, Select2ItemVarQuota from pretix.control.signals import order_search_filter_q from pretix.helpers.countries import CachedCountries @@ -67,7 +69,7 @@ from pretix.helpers.database import ( get_deterministic_ordering, rolledback_transaction, ) from pretix.helpers.dicts import move_to_end -from pretix.helpers.i18n import i18ncomp +from pretix.helpers.i18n import get_format_without_seconds, i18ncomp PAYMENT_PROVIDERS = [] @@ -687,11 +689,71 @@ class EventOrderExpertFilterForm(EventOrderFilterForm): ) self.fields['quota'].widget.choices = self.fields['quota'].choices for q in self.event.questions.all(): - self.fields['question_{}'.format(q.pk)] = forms.CharField( - label=q.question, - required=False, - help_text=_('Exact matches only') - ) + kwargs = { + "label": q.question, + "required": False, + } + fname = 'question_{}'.format(q.pk) + if q.type == Question.TYPE_NUMBER: + self.fields[fname] = forms.DecimalField( + help_text=_('Exact matches only'), + **kwargs, + ) + elif q.type == Question.TYPE_BOOLEAN: + self.fields[fname] = forms.ChoiceField( + choices=( + ("", ""), + ("True", _("Yes")), + ("False", _("No")), + ), + **kwargs, + ) + elif q.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE): + self.fields[fname] = forms.ModelChoiceField( + queryset=q.options, + widget=forms.Select, + to_field_name='identifier', + empty_label='', + **kwargs, + ) + elif q.type == Question.TYPE_COUNTRYCODE: + self.fields[fname] = CountryField( + countries=CachedCountries, + blank=True, null=True, blank_label=' ', + ).formfield( + **kwargs, + widget=forms.Select, + empty_label=' ', + ) + elif q.type == Question.TYPE_DATE: + self.fields[fname] = forms.DateField( + widget=DatePickerWidget(), + help_text=_('Exact matches only'), + **kwargs, + ) + elif q.type == Question.TYPE_TIME: + self.fields[fname] = forms.TimeField( + widget=TimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')), + help_text=_('Exact matches only'), + **kwargs, + ) + elif q.type == Question.TYPE_DATETIME: + self.fields[fname] = SplitDateTimeField( + widget=SplitDateTimePickerWidget( + time_format=get_format_without_seconds('TIME_INPUT_FORMATS'), + min_date=q.valid_datetime_min, + max_date=q.valid_datetime_max + ), + help_text=_('Exact matches only'), + **kwargs, + ) + elif q.type == Question.TYPE_FILE: + continue + else: + self.fields[fname] = forms.CharField( + help_text=_('Exact matches only'), + **kwargs, + ) def filter_qs(self, qs): fdata = self.cleaned_data @@ -787,11 +849,24 @@ class EventOrderExpertFilterForm(EventOrderFilterForm): ).distinct() for q in self.event.questions.all(): if fdata.get(f'question_{q.pk}'): - answers = QuestionAnswer.objects.filter( - question_id=q.pk, - orderposition__order_id=OuterRef('pk'), - answer__iexact=fdata.get(f'question_{q.pk}') - ) + if q.type == Question.TYPE_BOOLEAN: + answers = QuestionAnswer.objects.filter( + question_id=q.pk, + orderposition__order_id=OuterRef('pk'), + answer__exact=fdata.get(f'question_{q.pk}') + ) + elif q.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE): + answers = QuestionAnswer.objects.filter( + question_id=q.pk, + orderposition__order_id=OuterRef('pk'), + options=fdata.get(f'question_{q.pk}') + ) + else: + answers = QuestionAnswer.objects.filter( + question_id=q.pk, + orderposition__order_id=OuterRef('pk'), + answer__iexact=fdata.get(f'question_{q.pk}') + ) qs = qs.annotate(**{f'q_{q.pk}': Exists(answers)}).filter(**{f'q_{q.pk}': True}) return qs