diff --git a/doc/api/resources/checkinlists.rst b/doc/api/resources/checkinlists.rst index 522d999a79..f99787c9b2 100644 --- a/doc/api/resources/checkinlists.rst +++ b/doc/api/resources/checkinlists.rst @@ -41,6 +41,11 @@ include_pending boolean If ``true``, th The ``include_pending`` field has been added. +.. versionchanged:: 2.7 + + The ``positions`` subresource now contains the new attributes ``require_attention`` and ``order__status`` and + accepts the new ``ignore_status`` filter. + Endpoints --------- @@ -339,8 +344,12 @@ Order position endpoints .. 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 - the :ref:`order-position-resource`, with one important difference: the ``checkins`` value will only include - check-ins for the selected list. + the :ref:`order-position-resource`, with the following differences: + + * The ``checkins`` value will only include check-ins for the selected list. + + * An additional boolean property ``require_attention`` will inform you whether either the order or the item + have the ``checkin_attention`` flag set. **Example request**: @@ -407,6 +416,8 @@ Order position endpoints } :query integer page: The page number in case of a multi-page result set, default is 1 + :query string ignore_status: If set to ``true``, results will be returned regardles of the state of + the order they belong to and you will need to do your own filtering by order status. :query string ordering: Manually set the ordering of results. Valid fields to be used are ``order__code``, ``order__datetime``, ``positionid``, ``attendee_name``, ``last_checked_in`` and ``order__email``. Default: ``attendee_name,positionid`` diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index bbcf8e34d6..91d761424b 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -179,6 +179,23 @@ class OrderPositionSerializer(I18nAwareModelSerializer): self.fields.pop('pdf_data') +class RequireAttentionField(serializers.Field): + def to_representation(self, instance: OrderPosition): + return instance.order.checkin_attention or instance.item.checkin_attention + + +class CheckinListOrderPositionSerializer(OrderPositionSerializer): + require_attention = RequireAttentionField(source='*') + order__status = serializers.SlugRelatedField(read_only=True, slug_field='status', source='order') + + class Meta: + model = OrderPosition + fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', + 'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins', + 'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'require_attention', + 'order__status') + + class OrderPaymentTypeField(serializers.Field): # TODO: Remove after pretix 2.2 def to_representation(self, instance: Order): diff --git a/src/pretix/api/views/checkin.py b/src/pretix/api/views/checkin.py index 5c78fac209..6da94cf19c 100644 --- a/src/pretix/api/views/checkin.py +++ b/src/pretix/api/views/checkin.py @@ -13,7 +13,7 @@ from rest_framework.response import Response from pretix.api.serializers.checkin import CheckinListSerializer from pretix.api.serializers.item import QuestionSerializer -from pretix.api.serializers.order import OrderPositionSerializer +from pretix.api.serializers.order import CheckinListOrderPositionSerializer from pretix.api.views import RichOrderingFilter from pretix.api.views.order import OrderPositionFilter from pretix.base.models import ( @@ -153,7 +153,7 @@ class CheckinOrderPositionFilter(OrderPositionFilter): class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): - serializer_class = OrderPositionSerializer + serializer_class = CheckinListOrderPositionSerializer queryset = OrderPosition.objects.none() filter_backends = (DjangoFilterBackend, RichOrderingFilter) ordering = ('attendee_name_cached', 'positionid') @@ -199,11 +199,15 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): qs = OrderPosition.objects.filter( order__event=self.request.event, - order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.checkinlist.include_pending else [Order.STATUS_PAID], subevent=self.checkinlist.subevent ).annotate( last_checked_in=Subquery(cqs) ) + + if self.request.query_params.get('ignore_status', 'false') != 'true': + qs = qs.filter( + order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING] if self.checkinlist.include_pending else [Order.STATUS_PAID] + ) if self.request.query_params.get('pdf_data', 'false') == 'true': qs = qs.prefetch_related( Prefetch( @@ -225,7 +229,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): ) )) ).select_related( - 'item', 'variation', 'item__category', 'addon_to' + 'item', 'variation', 'item__category', 'addon_to', 'order' ) else: qs = qs.prefetch_related( @@ -235,7 +239,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): ), 'answers', 'answers__options', 'answers__question', Prefetch('addons', OrderPosition.objects.select_related('item', 'variation')) - ).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address') + ).select_related('item', 'variation', 'order', 'addon_to', 'order__invoice_address', 'order') if not self.checkinlist.all_products: qs = qs.filter(item__in=self.checkinlist.limit_products.values_list('id', flat=True)) @@ -281,7 +285,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): return Response({ 'status': 'incomplete', 'require_attention': op.item.checkin_attention or op.order.checkin_attention, - 'position': OrderPositionSerializer(op, context=self.get_serializer_context()).data, + 'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data, 'questions': [ QuestionSerializer(q).data for q in e.questions ] @@ -291,13 +295,13 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet): 'status': 'error', 'reason': e.code, 'require_attention': op.item.checkin_attention or op.order.checkin_attention, - 'position': OrderPositionSerializer(op, context=self.get_serializer_context()).data + 'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data }, status=400) else: return Response({ 'status': 'ok', 'require_attention': op.item.checkin_attention or op.order.checkin_attention, - 'position': OrderPositionSerializer(op, context=self.get_serializer_context()).data + 'position': CheckinListOrderPositionSerializer(op, context=self.get_serializer_context()).data }, status=201) def get_object(self): diff --git a/src/pretix/control/views/checkin.py b/src/pretix/control/views/checkin.py index a6f0b189b1..d260279c18 100644 --- a/src/pretix/control/views/checkin.py +++ b/src/pretix/control/views/checkin.py @@ -100,6 +100,7 @@ class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView): 'list': self.list.pk, 'web': True }, user=request.user) + op.order.touch() messages.success(request, _('The selected check-ins have been reverted.')) else: @@ -247,7 +248,7 @@ class CheckinListDelete(EventPermissionRequiredMixin, DeleteView): self.object = self.get_object() success_url = self.get_success_url() self.object.checkins.all().delete() - self.object.log_action(action='pretix.event.orders.deleted', user=request.user) + self.object.log_action(action='pretix.event.checkinlists.deleted', user=request.user) self.object.delete() messages.success(self.request, _('The selected list has been deleted.')) return HttpResponseRedirect(success_url) diff --git a/src/tests/api/test_checkin.py b/src/tests/api/test_checkin.py index d14d68b60c..a2be41d3f8 100644 --- a/src/tests/api/test_checkin.py +++ b/src/tests/api/test_checkin.py @@ -69,6 +69,8 @@ def order(event, item, other_item, taxrule): TEST_ORDERPOSITION1_RES = { "id": 1, + "require_attention": False, + "order__status": "p", "order": "FOO", "positionid": 1, "item": 1, @@ -92,6 +94,8 @@ TEST_ORDERPOSITION1_RES = { TEST_ORDERPOSITION2_RES = { "id": 2, + "require_attention": False, + "order__status": "p", "order": "FOO", "positionid": 2, "item": 1, @@ -372,6 +376,14 @@ def test_list_all_items_positions(token_client, organizer, event, clist, clist_a assert resp.status_code == 200 assert [] == resp.data['results'] + resp = token_client.get('/api/v1/organizers/{}/events/{}/checkinlists/{}/positions/?ignore_status=true'.format( + organizer.slug, event.slug, clist_all.pk + )) + assert resp.status_code == 200 + p1['order__status'] = 'n' + p2['order__status'] = 'n' + assert [p2, p1] == resp.data['results'] + @pytest.mark.django_db def test_list_limited_items_positions(token_client, organizer, event, clist, item, order):