diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 1193a589b9..424ef42474 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -1721,9 +1721,47 @@ List of all order positions .. http:get:: /api/v1/organizers/(organizer)/orderpositions/ - Returns a list of all order positions. - It works in the same way as :http:get:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/` - but does not make use of the event parameter, operating across all order positions of an organizer. + Returns a list of all order positions within all events of a given organizer (with sufficient access permissions). + + Supported query parameters and output format of this endpoint are identical to the list endpoint within an event, + with the exception that the ``pdf_data`` parameter is not supported here. + + **Example request**: + + .. sourcecode:: http + + GET /api/v1/organizers/bigevents/orderpositions/ HTTP/1.1 + Host: pretix.eu + Accept: application/json, text/javascript + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + X-Page-Generated: 2017-12-01T10:00:00Z + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id:": 23442 + "code": "ABC12", + "event": "sampleconf", + ... + } + ] + } + + :param organizer: The ``slug`` field of the organizer to fetch + :statuscode 200: no error + :statuscode 401: Authentication failure + :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. + Fetching individual positions @@ -1827,11 +1865,6 @@ Fetching individual positions :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 404: The requested order position does not exist. -.. http:get:: /api/v1/organizers/(organizer)/orderpositions/(id)/ - - Returns information on one order position, identified by its internal ID. - It works in the same way as :http:get:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)` - but does not make use of the event parameter, operating across all order positions of an organizer. .. _`order-position-ticket-download`: @@ -1885,12 +1918,6 @@ Order position ticket download :statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few seconds. -.. http:get:: /api/v1/organizers/(organizer)/orderpositions/(id)/download/(output)/ - - Download tickets for one order position, identified by its internal ID. - It works in the same way as :http:get:`/api/v1/organizers/(organizer)/orderpositions/(id)/download/(output)/` - but does not make use of the event parameter, operating across all order positions of an organizer. - .. _rest-orderpositions-manipulate: Manipulating individual positions @@ -1983,12 +2010,6 @@ Manipulating individual positions :statuscode 401: Authentication failure :statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order. -.. http:patch:: /api/v1/organizers/(organizer)/orderpositions/(id)/ - - Updates specific fields of an order position. - It works in the same way as :http:patch:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/` - but does not make use of the event parameter, operating across all order positions of an organizer. - .. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/ Adds a new position to an order. Currently, only the following fields are supported: @@ -2068,11 +2089,6 @@ Manipulating individual positions :statuscode 401: Authentication failure :statuscode 403: The requested organizer/event does not exist **or** you have no permission to create this position. -.. http:post:: /api/v1/organizers/(organizer)/orderpositions/ - - Adds a new position to an order. - It works in the same way as :http:post:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/` - but does not make use of the event parameter, operating across all order positions of an organizer. .. http:delete:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/ @@ -2102,11 +2118,6 @@ Manipulating individual positions :statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource. :statuscode 404: The requested order position does not exist. -.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/ - - Cancels an order position, identified by its internal ID. - It works in the same way as :http:delete:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/` - but does not make use of the event parameter, operating across all order positions of an organizer. .. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/add_block/ @@ -2146,11 +2157,6 @@ Manipulating individual positions :statuscode 401: Authentication failure :statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position. -.. http:post:: /api/v1/organizers/(organizer)/orderpositions/(id)/add_block/ - - Blocks an order position from being used. - It works in the same way as :http:post:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/add_block/` - but does not make use of the event parameter, operating across all order positions of an organizer. .. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/remove_block/ @@ -2190,12 +2196,6 @@ Manipulating individual positions :statuscode 401: Authentication failure :statuscode 403: The requested organizer/event does not exist **or** you have no permission to update this order position. -.. http:post:: /api/v1/organizers/(organizer)/orderpositions/(id)/remove_block/ - - Unblocks an order position from being used. - It works in the same way as :http:post:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/remove_block/` - but does not make use of the event parameter, operating across all order positions of an organizer. - .. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/printlog/ Creates a print log, stating that this ticket has been printed. @@ -2249,12 +2249,6 @@ Manipulating individual positions :statuscode 409: The file is not yet ready and will now be prepared. Retry the request after waiting for a few seconds. -.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/printlog/ - - Creates a print log, stating that this ticket has been printed. - It works in the same way as :http:post:`/api/v1/organizers/(organizer)/events/(event)/orderpositions/(id)/printlog/` - but does not make use of the event parameter, operating across all order positions of an organizer. - Changing order contents ----------------------- diff --git a/src/pretix/api/urls.py b/src/pretix/api/urls.py index 21662413ba..2df593a50c 100644 --- a/src/pretix/api/urls.py +++ b/src/pretix/api/urls.py @@ -67,7 +67,7 @@ orga_router.register(r'invoices', order.InvoiceViewSet) orga_router.register(r'scheduled_exports', exporters.ScheduledOrganizerExportViewSet) orga_router.register(r'exporters', exporters.OrganizerExportersViewSet, basename='exporters') orga_router.register(r'transactions', order.OrganizerTransactionViewSet) -orga_router.register(r'orderpositions', order.OrderPositionViewSet) +orga_router.register(r'orderpositions', order.OrganizerPositionViewSet, basename='orderpositions') team_router = routers.DefaultRouter() team_router.register(r'members', organizer.TeamMemberViewSet) @@ -84,7 +84,7 @@ event_router.register(r'discounts', discount.DiscountViewSet) event_router.register(r'quotas', item.QuotaViewSet) event_router.register(r'vouchers', voucher.VoucherViewSet) event_router.register(r'orders', order.EventOrderViewSet) -event_router.register(r'orderpositions', order.OrderPositionViewSet) +event_router.register(r'orderpositions', order.EventOrderPositionViewSet) event_router.register(r'transactions', order.TransactionViewSet) event_router.register(r'invoices', order.InvoiceViewSet) event_router.register(r'revokedsecrets', order.RevokedSecretViewSet, basename='revokedsecrets') diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index 4e6f61e348..a3569caa86 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -41,12 +41,11 @@ from django.utils.translation import gettext as _ from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_scopes import scopes_disabled from PIL import Image -from rest_framework import serializers, status, viewsets +from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action from rest_framework.exceptions import ( APIException, NotFound, PermissionDenied, ValidationError, ) -from rest_framework.mixins import CreateModelMixin from rest_framework.permissions import SAFE_METHODS from rest_framework.response import Response @@ -1065,7 +1064,7 @@ with scopes_disabled(): } -class OrderPositionViewSet(viewsets.ModelViewSet): +class OrderPositionViewSetMixin: serializer_class = OrderPositionSerializer queryset = OrderPosition.all.none() filter_backends = (DjangoFilterBackend, RichOrderingFilter) @@ -1087,10 +1086,8 @@ class OrderPositionViewSet(viewsets.ModelViewSet): def get_serializer_context(self): ctx = super().get_serializer_context() - if hasattr(self.request, 'event'): - ctx['event'] = self.request.event - ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true' ctx['check_quotas'] = self.request.query_params.get('check_quotas', 'true').lower() == 'true' + ctx['pdf_data'] = False return ctx def get_queryset(self): @@ -1099,22 +1096,6 @@ class OrderPositionViewSet(viewsets.ModelViewSet): else: qs = OrderPosition.objects - perm = self.permission if self.request.method in SAFE_METHODS else self.write_permission - - if hasattr(self.request, 'event'): - qs = qs.filter(order__event=self.request.event) - else: - if isinstance(self.request.auth, (TeamAPIToken, Device)): - qs = qs.filter( - order__event__organizer=self.request.organizer, - order__event__in=self.request.auth.get_events_with_permission(perm, request=self.request) - ) - elif self.request.user.is_authenticated: - qs = qs.filter( - order__event__organizer=self.request.organizer, - order__event__in=self.request.auth.get_events_with_permission(perm, request=self.request) - ) - if self.request.query_params.get('pdf_data', 'false').lower() == 'true': prefetch_related_objects([self.request.organizer], 'meta_properties') prefetch_related_objects( @@ -1184,6 +1165,40 @@ class OrderPositionViewSet(viewsets.ModelViewSet): return prov raise NotFound('Unknown output provider.') + +class OrganizerPositionViewSet(OrderPositionViewSetMixin, viewsets.ReadOnlyModelViewSet): + def get_queryset(self): + qs = super().get_queryset() + + perm = self.permission if self.request.method in SAFE_METHODS else self.write_permission + + if isinstance(self.request.auth, (TeamAPIToken, Device)): + qs = qs.filter( + order__event__organizer=self.request.organizer, + order__event__in=self.request.auth.get_events_with_permission(perm, request=self.request) + ) + elif self.request.user.is_authenticated: + qs = qs.filter( + order__event__organizer=self.request.organizer, + order__event__in=self.request.auth.get_events_with_permission(perm, request=self.request) + ) + + return qs + + +class EventOrderPositionViewSet(OrderPositionViewSetMixin, viewsets.ModelViewSet): + def get_serializer_context(self): + ctx = super().get_serializer_context() + if hasattr(self.request, 'event'): + ctx['event'] = self.request.event + ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false').lower() == 'true' + return ctx + + def get_queryset(self): + qs = super().get_queryset() + qs = qs.filter(order__event=self.request.event) + return qs + @action(detail=True, methods=['POST'], url_name='price_calc') def price_calc(self, request, *args, **kwargs): """ @@ -1587,7 +1602,7 @@ class OrderPositionViewSet(viewsets.ModelViewSet): return Response(self.get_serializer_class()(instance=serializer.instance, context=self.get_serializer_context()).data) -class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): +class PaymentViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet): serializer_class = OrderPaymentSerializer queryset = OrderPayment.objects.none() permission = 'can_view_orders' @@ -1760,7 +1775,7 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): return self.retrieve(request, [], **kwargs) -class RefundViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet): +class RefundViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet): serializer_class = OrderRefundSerializer queryset = OrderRefund.objects.none() permission = 'can_view_orders' diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 0bca7532fe..21f95ef422 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -1022,17 +1022,21 @@ def test_refund_cancel(token_client, organizer, event, order): assert resp.status_code == 400 -@pytest.mark.parametrize("endpoint_template, response_code", [('/api/v1/organizers/{}/events/{}/orderpositions/', 403),('/api/v1/organizers/{}/orderpositions/', 200)]) +@pytest.mark.parametrize( + "endpoint_template, response_code", + [('/api/v1/organizers/{}/events/{}/orderpositions/', 403), ('/api/v1/organizers/{}/orderpositions/', 200)] +) @pytest.mark.django_db -def test_orderposition_list_limited_read(endpoint_template, response_code, limited_token_client, organizer, device, event, order, item, subevent, subevent2, question): - endpoint = endpoint_template.format(organizer.slug, event.slug) +def test_orderposition_list_limited_read( + endpoint_template, response_code, limited_token_client, organizer, device, event, order, item, subevent, subevent2, question +): + endpoint = endpoint_template.format(organizer.slug, event.slug) i2 = copy.copy(item) i2.pk = None i2.save() with scopes_disabled(): var = item.variations.create(value="Children") - var2 = item.variations.create(value="Children") res = copy.copy(TEST_ORDERPOSITION_RES) op = order.positions.first() op.variation = var @@ -1052,10 +1056,13 @@ def test_orderposition_list_limited_read(endpoint_template, response_code, limit assert resp.json() == {'detail': 'You do not have permission to perform this action.'} -@pytest.mark.parametrize("endpoint_template", [('/api/v1/organizers/{}/events/{}/orderpositions/'),('/api/v1/organizers/{}/orderpositions/')]) +@pytest.mark.parametrize( + "endpoint_template", + [('/api/v1/organizers/{}/events/{}/orderpositions/'), ('/api/v1/organizers/{}/orderpositions/')] +) @pytest.mark.django_db def test_orderposition_list(endpoint_template, token_client, organizer, device, event, order, item, subevent, subevent2, question, django_assert_num_queries): - endpoint = endpoint_template.format(organizer.slug, event.slug) + endpoint = endpoint_template.format(organizer.slug, event.slug) i2 = copy.copy(item) i2.pk = None @@ -1078,57 +1085,57 @@ def test_orderposition_list(endpoint_template, token_client, organizer, device, assert resp.status_code == 200 assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?order__status=n') + resp = token_client.get(endpoint + '?order__status=n') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?order__status=p') + resp = token_client.get(endpoint + '?order__status=p') assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?item={}'.format(item.pk)) + resp = token_client.get(endpoint + '?item={}'.format(item.pk)) assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?item__in={},{}'.format(item.pk, i2.pk)) + resp = token_client.get(endpoint + '?item__in={},{}'.format(item.pk, i2.pk)) assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?item={}'.format( i2.pk)) + resp = token_client.get(endpoint + '?item={}'.format(i2.pk)) assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?variation={}'.format(var.pk)) + resp = token_client.get(endpoint + '?variation={}'.format(var.pk)) assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?variation={}'.format(var2.pk)) + resp = token_client.get(endpoint + '?variation={}'.format(var2.pk)) assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?attendee_name=Peter') + resp = token_client.get(endpoint + '?attendee_name=Peter') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?attendee_name=peter') + resp = token_client.get(endpoint + '?attendee_name=peter') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?attendee_name=Mark') + resp = token_client.get(endpoint + '?attendee_name=Mark') assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?secret=z3fsn8jyufm5kpk768q69gkbyr5f4h6w') + resp = token_client.get(endpoint + '?secret=z3fsn8jyufm5kpk768q69gkbyr5f4h6w') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?secret=abc123') + resp = token_client.get(endpoint + '?secret=abc123') assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?pseudonymization_id=ABCDEFGHKL') + resp = token_client.get(endpoint + '?pseudonymization_id=ABCDEFGHKL') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?pseudonymization_id=FOO') + resp = token_client.get(endpoint + '?pseudonymization_id=FOO') assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?search=FO') + resp = token_client.get(endpoint + '?search=FO') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?search=z3fsn8j') + resp = token_client.get(endpoint + '?search=z3fsn8j') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?search=Peter') + resp = token_client.get(endpoint + '?search=Peter') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?search=5f4h6w') + resp = token_client.get(endpoint + '?search=5f4h6w') assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?order=FOO') + resp = token_client.get(endpoint + '?order=FOO') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?order=BAR') + resp = token_client.get(endpoint + '?order=BAR') assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?has_checkin=false') + resp = token_client.get(endpoint + '?has_checkin=false') assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?has_checkin=true') + resp = token_client.get(endpoint + '?has_checkin=true') assert [] == resp.data['results'] with scopes_disabled(): @@ -1147,32 +1154,32 @@ def test_orderposition_list(endpoint_template, token_client, organizer, device, }] if '/events/' in endpoint: with django_assert_num_queries(16): - resp = token_client.get(endpoint+'?has_checkin=true') + resp = token_client.get(endpoint + '?has_checkin=true') else: with django_assert_num_queries(15): - resp = token_client.get(endpoint+'?has_checkin=true') + resp = token_client.get(endpoint + '?has_checkin=true') assert [res] == resp.data['results'] op.subevent = subevent op.save() res['subevent'] = subevent.pk - resp = token_client.get(endpoint+'?subevent={}'.format(subevent.pk)) + resp = token_client.get(endpoint + '?subevent={}'.format(subevent.pk)) assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?subevent__in={},{}'.format(subevent.pk, subevent2.pk)) + resp = token_client.get(endpoint + '?subevent__in={},{}'.format(subevent.pk, subevent2.pk)) assert [res] == resp.data['results'] - resp = token_client.get(endpoint+'?subevent={}'.format(subevent.pk + 1)) + resp = token_client.get(endpoint + '?subevent={}'.format(subevent.pk + 1)) assert [] == resp.data['results'] - resp = token_client.get(endpoint+'?include_canceled_positions=false') + resp = token_client.get(endpoint + '?include_canceled_positions=false') assert len(resp.data['results']) == 1 - resp = token_client.get(endpoint+'?include_canceled_positions=true') + resp = token_client.get(endpoint + '?include_canceled_positions=true') assert len(resp.data['results']) == 2 -@pytest.mark.parametrize("endpoint_template", [('/api/v1/organizers/{}/events/{}/orderpositions/'),('/api/v1/organizers/{}/orderpositions/')]) + @pytest.mark.django_db -def test_orderposition_detail(endpoint_template, token_client, organizer, event, order, item, question): - endpoint = endpoint_template.format(organizer.slug, event.slug) +def test_orderposition_detail(token_client, organizer, event, order, item, question): + endpoint = '/api/v1/organizers/{}/events/{}/orderpositions/'.format(organizer.slug, event.slug) res = dict(TEST_ORDERPOSITION_RES) with scopes_disabled(): @@ -1188,27 +1195,27 @@ def test_orderposition_detail(endpoint_template, token_client, organizer, event, order.status = 'p' order.save() event.settings.ticketoutput_pdf__enabled = True - resp = token_client.get(endpoint+'{}/'.format(op.pk)) + resp = token_client.get(endpoint + '{}/'.format(op.pk)) assert len(resp.data['downloads']) == 1 -@pytest.mark.parametrize("endpoint_template", [('/api/v1/organizers/{}/events/{}/orderpositions/'),('/api/v1/organizers/{}/orderpositions/')]) + @pytest.mark.django_db -def test_orderposition_detail_canceled(endpoint_template, token_client, organizer, event, order, item, question): - endpoint = endpoint_template.format(organizer.slug, event.slug) +def test_orderposition_detail_canceled(token_client, organizer, event, order, item, question): + endpoint = '/api/v1/organizers/{}/events/{}/orderpositions/'.format(organizer.slug, event.slug) with scopes_disabled(): op = order.all_positions.filter(canceled=True).first() - resp = token_client.get(endpoint+'{}/'.format(op.pk)) + resp = token_client.get(endpoint + '{}/'.format(op.pk)) assert resp.status_code == 404 - resp = token_client.get(endpoint+'{}/?include_canceled_positions=true'.format(op.pk)) + resp = token_client.get(endpoint + '{}/?include_canceled_positions=true'.format(op.pk)) assert resp.status_code == 200 -@pytest.mark.parametrize("endpoint_template", [('/api/v1/organizers/{}/events/{}/orderpositions/'),('/api/v1/organizers/{}/orderpositions/')]) + @pytest.mark.django_db -def test_orderposition_delete(endpoint_template, token_client, organizer, event, order, item, question): - endpoint = endpoint_template.format(organizer.slug, event.slug) +def test_orderposition_delete(token_client, organizer, event, order, item, question): + endpoint = '/api/v1/organizers/{}/events/{}/orderpositions/'.format(organizer.slug, event.slug) with scopes_disabled(): op = order.positions.first() - resp = token_client.delete(endpoint+'{}/'.format(op.pk)) + resp = token_client.delete(endpoint + '{}/'.format(op.pk)) assert resp.status_code == 400 assert resp.data == ['This operation would leave the order empty. Please cancel the order itself instead.'] @@ -1227,7 +1234,7 @@ def test_orderposition_delete(endpoint_template, token_client, organizer, event, order.save() assert order.positions.count() == 2 - resp = token_client.delete(endpoint+'{}/'.format(op2.pk)) + resp = token_client.delete(endpoint + '{}/'.format(op2.pk)) assert resp.status_code == 204 with scopes_disabled(): assert order.positions.count() == 1 @@ -1235,13 +1242,13 @@ def test_orderposition_delete(endpoint_template, token_client, organizer, event, order.refresh_from_db() assert order.total == Decimal('23.25') -@pytest.mark.parametrize("endpoint_template", [('/api/v1/organizers/{}/events/{}/orderpositions/'),('/api/v1/organizers/{}/orderpositions/')]) + @pytest.mark.django_db -def test_orderposition_printlog(endpoint_template, token_client, team, organizer, event, order, item, question): - endpoint = endpoint_template.format(organizer.slug, event.slug) +def test_orderposition_printlog(token_client, team, organizer, event, order, item, question): + endpoint = '/api/v1/organizers/{}/events/{}/orderpositions/'.format(organizer.slug, event.slug) with scopes_disabled(): op = order.positions.first() - resp = token_client.post(endpoint+'{}/printlog/'.format(op.pk), data={ + resp = token_client.post(endpoint + '{}/printlog/'.format(op.pk), data={ "datetime": "2023-09-04T12:23:45+02:00", "source": "pretixscan", "type": "badge", @@ -1258,23 +1265,6 @@ def test_orderposition_printlog(endpoint_template, token_client, team, organizer assert l.api_token.team == team assert l.datetime.isoformat() == "2023-09-04T10:23:45+00:00" -@pytest.mark.parametrize("endpoint_template, response_code", [ - ('/api/v1/organizers/{}/events/{}/orderpositions/', 403), - ('/api/v1/organizers/{}/orderpositions/', 404)]) -@pytest.mark.django_db -def test_orderposition_printlog(endpoint_template, response_code, limited_token_client, team, organizer, event, order, item, question): - endpoint = endpoint_template.format(organizer.slug, event.slug) - with scopes_disabled(): - op = order.positions.first() - resp = limited_token_client.post(endpoint+'{}/printlog/'.format(op.pk), data={ - "datetime": "2023-09-04T12:23:45+02:00", - "source": "pretixscan", - "type": "badge", - "info": { - "cashier": 1234, - } - }, format='json') - assert resp.status_code == response_code @pytest.mark.django_db def test_order_mark_paid_pending(token_client, organizer, event, order):