diff --git a/doc/api/resources/events.rst b/doc/api/resources/events.rst index fc755dccc4..9eed404ffd 100644 --- a/doc/api/resources/events.rst +++ b/doc/api/resources/events.rst @@ -41,6 +41,10 @@ plugins list A list of packa The ``plugins`` field has been added. The operations POST, PATCH, PUT and DELETE have been added. +.. versionchanged:: 2.1 + + Filters have been added to the list of events. + Endpoints --------- @@ -96,6 +100,12 @@ Endpoints } :query page: The page number in case of a multi-page result set, default is 1 + :query is_public: If set to ``true``/``false``, only events with a matching value of ``is_public`` are returned. + :query live: If set to ``true``/``false``, only events with a matching value of ``live`` are returned. + :query has_subevents: If set to ``true``/``false``, only events with a matching value of ``has_subevents`` are returned. + :query is_future: If set to ``true`` (``false``), only events that happen currently or in the future are (not) returned. Event series are never (always) returned. + :query is_past: If set to ``true`` (``false``), only events that are over are (not) returned. Event series are never (always) returned. + :query ends_after: If set to a date and time, only events that happen during of after the given time are returned. Event series are never returned. :param organizer: The ``slug`` field of a valid organizer :statuscode 200: no error :statuscode 401: Authentication failure diff --git a/src/pretix/api/views/event.py b/src/pretix/api/views/event.py index d685129025..23b2749813 100644 --- a/src/pretix/api/views/event.py +++ b/src/pretix/api/views/event.py @@ -1,5 +1,7 @@ +import django_filters from django.db import transaction -from django.db.models import ProtectedError +from django.db.models import ProtectedError, Q +from django.utils.timezone import now from django_filters.rest_framework import DjangoFilterBackend, FilterSet from rest_framework import filters, viewsets from rest_framework.exceptions import PermissionDenied @@ -15,12 +17,60 @@ from pretix.base.models.event import SubEvent from pretix.helpers.dicts import merge_dicts +class EventFilter(FilterSet): + is_past = django_filters.rest_framework.BooleanFilter(method='is_past_qs') + is_future = django_filters.rest_framework.BooleanFilter(method='is_future_qs') + ends_after = django_filters.rest_framework.IsoDateTimeFilter(method='ends_after_qs') + + class Meta: + model = Event + fields = ['is_public', 'live', 'has_subevents'] + + def ends_after_qs(self, queryset, name, value): + expr = ( + Q(has_subevents=False) & + Q( + Q(Q(date_to__isnull=True) & Q(date_from__gte=value)) + | Q(Q(date_to__isnull=False) & Q(date_to__gte=value)) + ) + ) + return queryset.filter(expr) + + def is_past_qs(self, queryset, name, value): + expr = ( + Q(has_subevents=False) & + Q( + Q(Q(date_to__isnull=True) & Q(date_from__lt=now())) + | Q(Q(date_to__isnull=False) & Q(date_to__lt=now())) + ) + ) + if value: + return queryset.filter(expr) + else: + return queryset.exclude(expr) + + def is_future_qs(self, queryset, name, value): + expr = ( + Q(has_subevents=False) & + Q( + Q(Q(date_to__isnull=True) & Q(date_from__gte=now())) + | Q(Q(date_to__isnull=False) & Q(date_to__gte=now())) + ) + ) + if value: + return queryset.filter(expr) + else: + return queryset.exclude(expr) + + class EventViewSet(viewsets.ModelViewSet): serializer_class = EventSerializer queryset = Event.objects.none() lookup_field = 'slug' lookup_url_kwarg = 'event' permission_classes = (EventCRUDPermission,) + filter_backends = (DjangoFilterBackend, filters.OrderingFilter) + filterset_class = EventFilter def get_queryset(self): return self.request.organizer.events.prefetch_related('meta_values', 'meta_values__property') diff --git a/src/tests/api/test_events.py b/src/tests/api/test_events.py index 832db7cb68..6be5cacbcc 100644 --- a/src/tests/api/test_events.py +++ b/src/tests/api/test_events.py @@ -115,9 +115,36 @@ def free_quota(event, free_item): def test_event_list(token_client, organizer, event): resp = token_client.get('/api/v1/organizers/{}/events/'.format(organizer.slug)) assert resp.status_code == 200 - print(resp.data) assert TEST_EVENT_RES == resp.data['results'][0] + resp = token_client.get('/api/v1/organizers/{}/events/?live=true'.format(organizer.slug)) + assert resp.status_code == 200 + assert [] == resp.data['results'] + resp = token_client.get('/api/v1/organizers/{}/events/?live=false'.format(organizer.slug)) + assert resp.status_code == 200 + assert [TEST_EVENT_RES] == resp.data['results'] + + resp = token_client.get('/api/v1/organizers/{}/events/?is_public=false'.format(organizer.slug)) + assert resp.status_code == 200 + assert [] == resp.data['results'] + resp = token_client.get('/api/v1/organizers/{}/events/?is_public=true'.format(organizer.slug)) + assert resp.status_code == 200 + assert [TEST_EVENT_RES] == resp.data['results'] + + resp = token_client.get('/api/v1/organizers/{}/events/?has_subevents=true'.format(organizer.slug)) + assert resp.status_code == 200 + assert [] == resp.data['results'] + resp = token_client.get('/api/v1/organizers/{}/events/?has_subevents=false'.format(organizer.slug)) + assert resp.status_code == 200 + assert [TEST_EVENT_RES] == resp.data['results'] + + resp = token_client.get('/api/v1/organizers/{}/events/?ends_after=2017-12-27T10:01:00Z'.format(organizer.slug)) + assert resp.status_code == 200 + assert [] == resp.data['results'] + resp = token_client.get('/api/v1/organizers/{}/events/?ends_after=2017-12-27T09:59:59Z'.format(organizer.slug)) + assert resp.status_code == 200 + assert [TEST_EVENT_RES] == resp.data['results'] + @pytest.mark.django_db def test_event_get(token_client, organizer, event):