diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 6e330bb8f..0f3b2a1e4 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -61,6 +61,10 @@ from pretix.base.models import ( SubEvent, SubEventMetaValue, Team, TeamAPIToken, TeamInvite, Voucher, ) from pretix.base.signals import register_payment_providers +from pretix.base.timeframes import ( + DateFrameField, + resolve_timeframe_to_datetime_start_inclusive_end_exclusive, +) from pretix.control.forms import SplitDateTimeField from pretix.control.forms.widgets import Select2, Select2ItemVarQuota from pretix.control.signals import order_search_filter_q @@ -1219,6 +1223,129 @@ class OrderPaymentSearchFilterForm(forms.Form): return qs +class QuestionAnswerFilterForm(forms.Form): + STATUS_VARIANTS = [ + ("", _("All orders")), + (Order.STATUS_PAID, _("Paid")), + (Order.STATUS_PAID + 'v', _("Paid or confirmed")), + (Order.STATUS_PENDING, _("Pending")), + (Order.STATUS_PENDING + Order.STATUS_PAID, _("Pending or paid")), + ("o", _("Pending (overdue)")), + (Order.STATUS_EXPIRED, _("Expired")), + (Order.STATUS_PENDING + Order.STATUS_EXPIRED, _("Pending or expired")), + (Order.STATUS_CANCELED, _("Canceled")) + ] + + status = forms.ChoiceField( + choices=STATUS_VARIANTS, + required=False, + label=_("Order status"), + ) + item = forms.ChoiceField( + choices=[], + required=False, + label=_("Products"), + ) + subevent = forms.ModelChoiceField( + queryset=SubEvent.objects.none(), + required=False, + empty_label=pgettext_lazy('subevent', 'All dates'), + label=pgettext_lazy("subevent", "Date"), + ) + date_range = DateFrameField( + required=False, + include_future_frames=True, + label=_('Event date'), + ) + + def __init__(self, *args, **kwargs): + self.event = kwargs.pop('event') + super().__init__(*args, **kwargs) + self.initial['status'] = Order.STATUS_PENDING + Order.STATUS_PAID + + choices = [('', _('All products'))] + for i in self.event.items.prefetch_related('variations').all(): + variations = list(i.variations.all()) + if variations: + choices.append((str(i.pk), _('{product} – Any variation').format(product=str(i)))) + for v in variations: + choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s' % (str(i), v.value))) + else: + choices.append((str(i.pk), str(i))) + self.fields['item'].choices = choices + + if self.event.has_subevents: + self.fields["subevent"].queryset = self.event.subevents.all() + self.fields['subevent'].widget = Select2( + attrs={ + 'data-model-select2': 'event', + 'data-select2-url': reverse('control:event.subevents.select2', kwargs={ + 'event': self.event.slug, + 'organizer': self.event.organizer.slug, + }), + 'data-placeholder': pgettext_lazy('subevent', 'All dates') + } + ) + self.fields['subevent'].widget.choices = self.fields['subevent'].choices + else: + del self.fields['subevent'] + + def clean(self): + cleaned_data = super().clean() + subevent = cleaned_data.get('subevent') + date_range = cleaned_data.get('date_range') + + if subevent is not None and date_range is not None: + d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone) + if ( + (d_start and not (d_start <= subevent.date_from)) or + (d_end and not (subevent.date_from < d_end)) + ): + self.add_error('subevent', pgettext_lazy('subevent', "Date doesn't start in selected date range.")) + return cleaned_data + + def filter_qs(self, opqs): + fdata = self.cleaned_data + + subevent = fdata.get('subevent', None) + date_range = fdata.get('date_range', None) + + if subevent is not None: + opqs = opqs.filter(subevent=subevent) + + if date_range is not None: + d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), date_range, self.event.timezone) + opqs = opqs.filter( + subevent__date_from__gte=d_start, + subevent__date_from__lt=d_end + ) + + s = fdata.get("status", Order.STATUS_PENDING + Order.STATUS_PAID) + if s != "": + if s == Order.STATUS_PENDING: + opqs = opqs.filter(order__status=Order.STATUS_PENDING, + order__expires__lt=now().replace(hour=0, minute=0, second=0)) + elif s == Order.STATUS_PENDING + Order.STATUS_PAID: + opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_PAID]) + elif s == Order.STATUS_PAID + 'v': + opqs = opqs.filter( + Q(order__status=Order.STATUS_PAID) | + Q(order__status=Order.STATUS_PENDING, order__valid_if_pending=True) + ) + elif s == Order.STATUS_PENDING + Order.STATUS_EXPIRED: + opqs = opqs.filter(order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED]) + else: + opqs = opqs.filter(order__status=s) + + if s not in (Order.STATUS_CANCELED, ""): + opqs = opqs.filter(canceled=False) + if fdata.get("item", "") != "": + i = fdata.get("item", "") + opqs = opqs.filter(item_id__in=(i,)) + + return opqs + + class SubEventFilterForm(FilterForm): orders = { 'date_from': 'date_from', diff --git a/src/pretix/control/templates/pretixcontrol/items/question.html b/src/pretix/control/templates/pretixcontrol/items/question.html index 168c59744..3be5f25a6 100644 --- a/src/pretix/control/templates/pretixcontrol/items/question.html +++ b/src/pretix/control/templates/pretixcontrol/items/question.html @@ -20,35 +20,20 @@