diff --git a/doc/api/resources/orders.rst b/doc/api/resources/orders.rst index 8066fa993f..94d6161a78 100644 --- a/doc/api/resources/orders.rst +++ b/doc/api/resources/orders.rst @@ -142,6 +142,10 @@ last_modified datetime Last modificati The ``order.fees.id`` attribute has been added. +.. versionchanged:: 4.15 + + The ``include`` query parameter has been added. + .. _order-position-resource: @@ -458,6 +462,7 @@ List of all orders :query datetime subevent_after: Only return orders that contain a ticket for a subevent taking place after the given date. This is an exclusive after, and it considers the **end** of the subevent (or its start, if the end is not set). :query datetime subevent_before: Only return orders that contain a ticket for a subevent taking place after the given date. This is an exclusive before, and it considers the **start** of the subevent. :query string exclude: Exclude a field from the output, e.g. ``fees`` or ``positions.downloads``. Can be used as a performance optimization. Can be passed multiple times. + :query string include: Include only the given field in the output, e.g. ``fees`` or ``positions.downloads``. Can be used as a performance optimization. Can be passed multiple times. ``include`` is applied before ``exclude``, so ``exclude`` takes precedence. :param organizer: The ``slug`` field of the organizer to fetch :param event: The ``slug`` field of the event to fetch :resheader X-Page-Generated: The server time at the beginning of the operation. If you're using this API to fetch diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py index b0cf2294e4..e99030c565 100644 --- a/src/pretix/api/serializers/order.py +++ b/src/pretix/api/serializers/order.py @@ -622,6 +622,23 @@ class OrderSerializer(I18nAwareModelSerializer): if not self.context['pdf_data']: self.fields['positions'].child.fields.pop('pdf_data', None) + includes = set(self.context['include']) + if includes: + for fname, field in list(self.fields.items()): + if fname in includes: + continue + elif hasattr(field, 'child'): + found_any = False + for childfname, childfield in list(field.child.fields.items()): + if f'{fname}.{childfname}' not in includes: + field.child.fields.pop(childfname) + else: + found_any = True + if not found_any: + self.fields.pop(fname) + else: + self.fields.pop(fname) + for exclude_field in self.context['exclude']: p = exclude_field.split('.') if p[0] in self.fields: diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index a87a2c463e..f7e29b5637 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -191,6 +191,7 @@ class OrderViewSet(viewsets.ModelViewSet): ctx['event'] = self.request.event ctx['pdf_data'] = self.request.query_params.get('pdf_data', 'false') == 'true' ctx['exclude'] = self.request.query_params.getlist('exclude') + ctx['include'] = self.request.query_params.getlist('include') return ctx def get_queryset(self): diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 5db12f3491..c6022a093d 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -442,6 +442,64 @@ def test_order_detail(token_client, organizer, event, order, item, taxrule, ques assert len(resp.data['fees']) == 2 +@pytest.mark.django_db +def test_include_exclude_fields(token_client, organizer, event, order, item, taxrule, question): + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?exclude=positions.secret'.format( + organizer.slug, event.slug, order.code + )) + assert resp.status_code == 200 + assert 'email' in resp.data + assert 'url' in resp.data + assert 'positions' in resp.data + assert 'subevent' in resp.data['positions'][0] + assert 'secret' not in resp.data['positions'][0] + + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?exclude=positions'.format( + organizer.slug, event.slug, order.code + )) + assert resp.status_code == 200 + assert 'email' in resp.data + assert 'url' in resp.data + assert 'positions' not in resp.data + + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?exclude=email&exclude=url'.format( + organizer.slug, event.slug, order.code + )) + assert resp.status_code == 200 + assert 'email' not in resp.data + assert 'url' not in resp.data + assert 'positions' in resp.data + assert 'secret' in resp.data['positions'][0] + + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?include=email'.format( + organizer.slug, event.slug, order.code + )) + assert resp.status_code == 200 + assert 'email' in resp.data + assert 'url' not in resp.data + assert 'positions' not in resp.data + + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?include=email&include=positions&exclude=positions.secret'.format( + organizer.slug, event.slug, order.code + )) + assert resp.status_code == 200 + assert 'email' in resp.data + assert 'url' not in resp.data + assert 'positions' in resp.data + assert 'subevent' in resp.data['positions'][0] + assert 'secret' not in resp.data['positions'][0] + + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?include=email&include=positions.subevent'.format( + organizer.slug, event.slug, order.code + )) + assert resp.status_code == 200 + assert 'email' in resp.data + assert 'url' not in resp.data + assert 'positions' in resp.data + assert 'subevent' in resp.data['positions'][0] + assert 'secret' not in resp.data['positions'][0] + + @pytest.mark.django_db def test_payment_list(token_client, organizer, event, order): resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(organizer.slug, event.slug,