diff --git a/doc/api/resources/checkinlists.rst b/doc/api/resources/checkinlists.rst index 0c8e713bcb..b4a7504b3d 100644 --- a/doc/api/resources/checkinlists.rst +++ b/doc/api/resources/checkinlists.rst @@ -254,6 +254,12 @@ Endpoints Order position endpoints ------------------------ +.. versionchanged:: 1.15 + + The order positions endpoint has been extended by the filter queries ``item__in``, ``variation__in``, + ``order__status__in``, ``subevent__in``, and ``addon_to__in``. The search for attendee names and order codes is now + case-insensitive. + .. http:get:: /api/v1/organizers/(organizer)/events/(event)/checkinlists/(list)/positions/ Returns a list of all order positions within a given event. The result is the same as @@ -326,14 +332,22 @@ Order position endpoints ``attendee_name,positionid`` :query string order: Only return positions of the order with the given order code :query integer item: Only return positions with the purchased item matching the given ID. + :query integer item__in: Only return positions with the purchased item matching one of the given comma-separated IDs. :query integer variation: Only return positions with the purchased item variation matching the given ID. + :query integer variation__in: Only return positions with one of the purchased item variation matching the given + comma-separated IDs. :query string attendee_name: Only return positions with the given value in the attendee_name field. Also, add-on products positions are shown if they refer to an attendee with the given name. :query string secret: Only return positions with the given ticket secret. - :query bollean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been - checked in already on this list. + :query string order__status: Only return positions with the given order status. + :query string order__status__in: Only return positions with one the given comma-separated order status. + :query boolean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been + checked in already. :query integer subevent: Only return positions of the sub-event with the given ID + :query integer subevent__in: Only return positions of one of the sub-events with the given comma-separated IDs :query integer addon_to: Only return positions that are add-ons to the position with the given ID. + :query integer addon_to__in: Only return positions that are add-ons to one of the positions with the given + comma-separated IDs. :param organizer: The ``slug`` field of the organizer to fetch :param event: The ``slug`` field of the event to fetch :param list: The ID of the check-in list to look for diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 2b4ed2b9f1..81970dc6b5 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -150,6 +150,10 @@ answers list of objects Answers to user Order endpoints --------------- +.. versionchanged:: 1.15 + + Filtering for emails or order codes is now case-insensitive. + .. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/ Returns a list of all orders within a given event. @@ -609,6 +613,12 @@ Order endpoints Order position endpoints ------------------------ +.. versionchanged:: 1.15 + + The order positions endpoint has been extended by the filter queries ``item__in``, ``variation__in``, + ``order__status__in``, ``subevent__in``, and ``addon_to__in``. The search for attendee names and order codes is now + case-insensitive. + .. http:get:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/ Returns a list of all order positions within a given event. @@ -681,15 +691,22 @@ Order position endpoints ``order__datetime,positionid`` :query string order: Only return positions of the order with the given order code :query integer item: Only return positions with the purchased item matching the given ID. + :query integer item__in: Only return positions with the purchased item matching one of the given comma-separated IDs. :query integer variation: Only return positions with the purchased item variation matching the given ID. + :query integer variation__in: Only return positions with one of the purchased item variation matching the given + comma-separated IDs. :query string attendee_name: Only return positions with the given value in the attendee_name field. Also, add-on products positions are shown if they refer to an attendee with the given name. :query string secret: Only return positions with the given ticket secret. :query string order__status: Only return positions with the given order status. - :query bollean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been + :query string order__status__in: Only return positions with one the given comma-separated order status. + :query boolean has_checkin: If set to ``true`` or ``false``, only return positions that have or have not been checked in already. :query integer subevent: Only return positions of the sub-event with the given ID + :query integer subevent__in: Only return positions of one of the sub-events with the given comma-separated IDs :query integer addon_to: Only return positions that are add-ons to the position with the given ID. + :query integer addon_to__in: Only return positions that are add-ons to one of the positions with the given + comma-separated IDs. :param organizer: The ``slug`` field of the organizer to fetch :param event: The ``slug`` field of the event to fetch :statuscode 200: no error diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index ca87b90cd7..ee536c2968 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -1,5 +1,4 @@ -import django_filters -from django.db.models import F, Max, OuterRef, Prefetch, Q, Subquery +from django.db.models import F, Max, OuterRef, Prefetch, Subquery from django.db.models.functions import Coalesce from django.shortcuts import get_object_or_404 from django.utils.functional import cached_property @@ -9,6 +8,7 @@ from rest_framework import viewsets from pretix.api.serializers.checkin import CheckinListSerializer from pretix.api.serializers.order import OrderPositionSerializer from pretix.api.views import RichOrderingFilter +from pretix.api.views.order import OrderPositionFilter from pretix.base.models import Checkin, CheckinList, Order, OrderPosition from pretix.base.models.organizer import TeamAPIToken from pretix.helpers.database import FixedOrderBy @@ -67,21 +67,11 @@ class CheckinListViewSet(viewsets.ModelViewSet): super().perform_destroy(instance) -class OrderPositionFilter(FilterSet): - order = django_filters.CharFilter(name='order', lookup_expr='code') - has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs') - attendee_name = django_filters.CharFilter(method='attendee_name_qs') +class CheckinOrderPositionFilter(OrderPositionFilter): def has_checkin_qs(self, queryset, name, value): return queryset.filter(last_checked_in__isnull=not value) - def attendee_name_qs(self, queryset, name, value): - return queryset.filter(Q(attendee_name=value) | Q(addon_to__attendee_name=value)) - - class Meta: - model = OrderPosition - fields = ['item', 'variation', 'attendee_name', 'secret', 'order', 'has_checkin', 'addon_to', 'subevent'] - class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = OrderPositionSerializer @@ -109,7 +99,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): }, } - filter_class = OrderPositionFilter + filter_class = CheckinOrderPositionFilter permission = 'can_view_orders' @cached_property diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 452ffe6a9e..41413f8689 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -31,6 +31,10 @@ from pretix.base.signals import register_ticket_outputs class OrderFilter(FilterSet): + email = django_filters.CharFilter(name='email', lookup_expr='iexact') + code = django_filters.CharFilter(name='code', lookup_expr='iexact') + status = django_filters.CharFilter(name='status', lookup_expr='iexact') + class Meta: model = Order fields = ['code', 'status', 'email', 'locale'] @@ -207,7 +211,7 @@ class OrderViewSet(viewsets.ReadOnlyModelViewSet): class OrderPositionFilter(FilterSet): - order = django_filters.CharFilter(name='order', lookup_expr='code') + order = django_filters.CharFilter(name='order', lookup_expr='code__iexact') has_checkin = django_filters.rest_framework.BooleanFilter(method='has_checkin_qs') attendee_name = django_filters.CharFilter(method='attendee_name_qs') @@ -215,12 +219,18 @@ class OrderPositionFilter(FilterSet): return queryset.filter(checkins__isnull=not value) def attendee_name_qs(self, queryset, name, value): - return queryset.filter(Q(attendee_name=value) | Q(addon_to__attendee_name=value)) + return queryset.filter(Q(attendee_name__iexact=value) | Q(addon_to__attendee_name__iexact=value)) class Meta: model = OrderPosition - fields = ['item', 'variation', 'attendee_name', 'secret', 'order', 'order__status', 'has_checkin', - 'addon_to', 'subevent'] + fields = { + 'item': ['exact', 'in'], + 'variation': ['exact', 'in'], + 'secret': ['exact'], + 'order__status': ['exact', 'in'], + 'addon_to': ['exact', 'in'], + 'subevent': ['exact', 'in'] + } class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet): diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 05d05bce92..084987b52d 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -1,3 +1,4 @@ +import copy import datetime from decimal import Decimal from distutils.version import LooseVersion @@ -203,7 +204,10 @@ def test_order_detail(token_client, organizer, event, order, item, taxrule, ques @pytest.mark.django_db -def test_orderposition_list(token_client, organizer, event, order, item, subevent, question): +def test_orderposition_list(token_client, organizer, event, order, item, subevent, subevent2, question): + i2 = copy.copy(item) + i2.pk = None + i2.save() var = item.variations.create(value="Children") res = dict(TEST_ORDERPOSITION_RES) op = order.positions.first() @@ -228,6 +232,11 @@ def test_orderposition_list(token_client, organizer, event, order, item, subeven resp = token_client.get( '/api/v1/organizers/{}/events/{}/orderpositions/?item={}'.format(organizer.slug, event.slug, item.pk)) assert [res] == resp.data['results'] + resp = token_client.get( + '/api/v1/organizers/{}/events/{}/orderpositions/?item__in={},{}'.format( + organizer.slug, event.slug, item.pk, i2.pk + )) + assert [res] == resp.data['results'] resp = token_client.get( '/api/v1/organizers/{}/events/{}/orderpositions/?item={}'.format(organizer.slug, event.slug, item.pk + 1)) assert [] == resp.data['results'] @@ -242,6 +251,9 @@ def test_orderposition_list(token_client, organizer, event, order, item, subeven resp = token_client.get( '/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=Peter'.format(organizer.slug, event.slug)) assert [res] == resp.data['results'] + resp = token_client.get( + '/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=peter'.format(organizer.slug, event.slug)) + assert [res] == resp.data['results'] resp = token_client.get( '/api/v1/organizers/{}/events/{}/orderpositions/?attendee_name=Mark'.format(organizer.slug, event.slug)) assert [] == resp.data['results'] @@ -282,6 +294,10 @@ def test_orderposition_list(token_client, organizer, event, order, item, subeven resp = token_client.get( '/api/v1/organizers/{}/events/{}/orderpositions/?subevent={}'.format(organizer.slug, event.slug, subevent.pk)) assert [res] == resp.data['results'] + resp = token_client.get( + '/api/v1/organizers/{}/events/{}/orderpositions/?subevent__in={},{}'.format(organizer.slug, event.slug, + subevent.pk, subevent2.pk)) + assert [res] == resp.data['results'] resp = token_client.get( '/api/v1/organizers/{}/events/{}/orderpositions/?subevent={}'.format(organizer.slug, event.slug, subevent.pk + 1))