From 513a90f9763a63d7ffa350e2a36ff20dca6d7467 Mon Sep 17 00:00:00 2001 From: Richard Schreiber Date: Mon, 6 Feb 2023 17:51:47 +0100 Subject: [PATCH] Subevent list: Add meta-data filter (Z#23114466) (#3083) Co-authored-by: Raphael Michel --- src/pretix/control/forms/filter.py | 49 ++++++++++++++++++- .../pretixcontrol/subevents/index.html | 5 ++ src/pretix/control/urls.py | 1 + src/pretix/control/views/subevents.py | 5 +- src/pretix/control/views/typeahead.py | 35 ++++++++++++- 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 3a58267d8e..61767d2bae 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -57,8 +57,8 @@ from pretix.base.forms.widgets import ( from pretix.base.models import ( Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue, Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition, - OrderRefund, Organizer, Question, QuestionAnswer, SubEvent, Team, - TeamAPIToken, TeamInvite, + OrderRefund, Organizer, Question, QuestionAnswer, SubEvent, + SubEventMetaValue, Team, TeamAPIToken, TeamInvite, ) from pretix.base.signals import register_payment_providers from pretix.control.forms.widgets import Select2 @@ -1116,9 +1116,25 @@ class SubEventFilterForm(FilterForm): ) def __init__(self, *args, **kwargs): + self.event = kwargs.pop('event') super().__init__(*args, **kwargs) self.fields['date_from'].widget = DatePickerWidget() self.fields['date_until'].widget = DatePickerWidget() + for p in self.meta_properties.all(): + self.fields['meta_{}'.format(p.name)] = forms.CharField( + label=p.name, + required=False, + widget=forms.TextInput( + attrs={ + 'data-typeahead-url': reverse('control:event.subevents.meta.typeahead', kwargs={ + 'organizer': self.event.organizer.slug, + 'event': self.event.slug + }) + '?' + urlencode({ + 'property': p.name, + }) + } + ) + ) def filter_qs(self, qs): fdata = self.cleaned_data @@ -1181,6 +1197,31 @@ class SubEventFilterForm(FilterForm): if fdata.get('time_from'): qs = qs.filter(date_from__time__gte=fdata.get('time_from')) + filters_by_property_name = {} + for i, p in enumerate(self.meta_properties): + d = fdata.get('meta_{}'.format(p.name)) + if d: + semv_with_value = SubEventMetaValue.objects.filter( + subevent=OuterRef('pk'), + property__pk=p.pk, + value=d + ) + semv_with_any_value = SubEventMetaValue.objects.filter( + subevent=OuterRef('pk'), + property__pk=p.pk, + ) + qs = qs.annotate(**{'attr_{}'.format(i): Exists(semv_with_value)}) + if p.name in filters_by_property_name: + filters_by_property_name[p.name] |= Q(**{'attr_{}'.format(i): True}) + else: + filters_by_property_name[p.name] = Q(**{'attr_{}'.format(i): True}) + default = self.event.meta_data[p.name] + if default == d: + qs = qs.annotate(**{'attr_{}_any'.format(i): Exists(semv_with_any_value)}) + filters_by_property_name[p.name] |= Q(**{'attr_{}_any'.format(i): False}) + for f in filters_by_property_name.values(): + qs = qs.filter(f) + if fdata.get('ordering'): qs = qs.order_by(self.get_order_by()) else: @@ -1188,6 +1229,10 @@ class SubEventFilterForm(FilterForm): return qs + @cached_property + def meta_properties(self): + return self.event.organizer.meta_properties.filter(filter_allowed=True) + class OrganizerFilterForm(FilterForm): orders = { diff --git a/src/pretix/control/templates/pretixcontrol/subevents/index.html b/src/pretix/control/templates/pretixcontrol/subevents/index.html index 442bcdbaa0..144d988d80 100644 --- a/src/pretix/control/templates/pretixcontrol/subevents/index.html +++ b/src/pretix/control/templates/pretixcontrol/subevents/index.html @@ -50,6 +50,11 @@
{% bootstrap_field filter_form.weekday %}
+ {% for mf in meta_fields %} +
+ {% bootstrap_field mf %} +
+ {% endfor %}
diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 3bf1adecbf..cc3d89b7df 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -261,6 +261,7 @@ urlpatterns = [ re_path(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'), re_path(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'), re_path(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'), + re_path(r'^subevents/typeahead/meta/$', typeahead.subevent_meta_values, name='event.subevents.meta.typeahead'), re_path(r'^items/$', item.ItemList.as_view(), name='event.items'), re_path(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'), re_path(r'^items/(?P\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'), diff --git a/src/pretix/control/views/subevents.py b/src/pretix/control/views/subevents.py index 5ad3089b19..8dd109f50c 100644 --- a/src/pretix/control/views/subevents.py +++ b/src/pretix/control/views/subevents.py @@ -110,7 +110,7 @@ class SubEventQueryMixin: @cached_property def filter_form(self): - return SubEventFilterForm(data=self.request_data, prefix='filter') + return SubEventFilterForm(data=self.request_data, prefix='filter', event=self.request.event) class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryMixin, ListView): @@ -125,6 +125,9 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) ctx['filter_form'] = self.filter_form + ctx['meta_fields'] = [ + self.filter_form['meta_{}'.format(p.name)] for p in self.request.organizer.meta_properties.filter(filter_allowed=True) + ] quotas = [] for s in ctx['subevents']: diff --git a/src/pretix/control/views/typeahead.py b/src/pretix/control/views/typeahead.py index 815df116f7..8c36ee1662 100644 --- a/src/pretix/control/views/typeahead.py +++ b/src/pretix/control/views/typeahead.py @@ -48,7 +48,8 @@ from django.utils.translation import gettext as _, pgettext from pretix.base.models import ( EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue, - ItemVariation, ItemVariationMetaValue, Order, Organizer, User, Voucher, + ItemVariation, ItemVariationMetaValue, Order, Organizer, SubEventMetaValue, + User, Voucher, ) from pretix.control.forms.event import EventWizardCopyForm from pretix.control.permissions import ( @@ -743,6 +744,38 @@ def meta_values(request): }) +def subevent_meta_values(request, organizer, event): + q = request.GET.get('q') + propname = request.GET.get('property') + + matches = SubEventMetaValue.objects.filter( + value__icontains=q, + property__name=propname, + subevent__event_id=request.event.pk, + ) + event_matches = EventMetaValue.objects.filter( + value__icontains=q, + property__name=propname, + event_id=request.event.pk, + ) + defaults = EventMetaProperty.objects.filter( + default__icontains=q, + name=propname, + organizer_id=request.organizer.pk, + ) + + return JsonResponse({ + 'results': [ + {'name': v, 'id': v} + for v in sorted( + set(defaults.values_list('default', flat=True)[:10]) | + set(matches.values_list('value', flat=True)[:10]) | + set(event_matches.values_list('value', flat=True)[:10]) + ) + ] + }) + + def item_meta_values(request, organizer, event): q = request.GET.get('q') propname = request.GET.get('property')