From f6fa9b4b16b4c9e7e5b55dcbf38dc33a52b9ca2b Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 22 Jul 2022 17:11:49 +0200 Subject: [PATCH] Fix high query load in pdf_data endpoints --- src/pretix/api/views/order.py | 64 +++++++++++++++++++++++++------- src/pretix/base/models/event.py | 5 ++- src/tests/api/test_checkinrpc.py | 2 +- src/tests/api/test_orders.py | 16 ++++---- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/pretix/api/views/order.py b/src/pretix/api/views/order.py index af9b602ac5..231e1a8b89 100644 --- a/src/pretix/api/views/order.py +++ b/src/pretix/api/views/order.py @@ -63,9 +63,10 @@ from pretix.api.serializers.orderchange import ( ) from pretix.base.i18n import language from pretix.base.models import ( - CachedCombinedTicket, CachedTicket, Checkin, Device, Event, Invoice, - InvoiceAddress, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund, - Quota, SubEvent, TaxRule, TeamAPIToken, generate_secret, + CachedCombinedTicket, CachedTicket, Checkin, Device, EventMetaValue, + Invoice, InvoiceAddress, ItemMetaValue, Order, OrderFee, OrderPayment, + OrderPosition, OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule, + TeamAPIToken, generate_secret, ) from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret from pretix.base.payment import PaymentException @@ -215,14 +216,27 @@ class OrderViewSet(viewsets.ModelViewSet): else: opq = OrderPosition.objects if request.query_params.get('pdf_data', 'false') == 'true': + prefetch_related_objects([request.organizer], 'meta_properties') + prefetch_related_objects( + [request.event], + Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'), to_attr='meta_values_cached'), + 'questions', + 'item_meta_properties', + ) return Prefetch( 'positions', opq.all().prefetch_related( Prefetch('checkins', queryset=Checkin.objects.all()), - 'item', 'variation', 'answers', 'answers__options', 'answers__question', - 'item__category', 'addon_to', 'seat', + Prefetch('item', queryset=self.request.event.items.prefetch_related( + Prefetch('meta_values', ItemMetaValue.objects.select_related('property'), to_attr='meta_values_cached') + )), + 'variation', 'answers', 'answers__options', 'answers__question', + 'item__category', + Prefetch('subevent', queryset=self.request.event.subevents.prefetch_related( + Prefetch('meta_values', to_attr='meta_values_cached', queryset=SubEventMetaValue.objects.select_related('property')) + )), Prefetch('addons', opq.select_related('item', 'variation', 'seat')) - ) + ).select_related('seat', 'addon_to') ) else: return Prefetch( @@ -945,25 +959,47 @@ class OrderPositionViewSet(viewsets.ModelViewSet): qs = qs.filter(order__event=self.request.event) if self.request.query_params.get('pdf_data', 'false') == 'true': + prefetch_related_objects([self.request.organizer], 'meta_properties') + prefetch_related_objects( + [self.request.event], + Prefetch('meta_values', queryset=EventMetaValue.objects.select_related('property'), to_attr='meta_values_cached'), + 'questions', + 'item_meta_properties', + ) qs = qs.prefetch_related( Prefetch('checkins', queryset=Checkin.objects.all()), - 'answers', 'answers__options', 'answers__question', + Prefetch('item', queryset=self.request.event.items.prefetch_related( + Prefetch('meta_values', ItemMetaValue.objects.select_related('property'), + to_attr='meta_values_cached') + )), + 'variation', 'answers', 'answers__options', 'answers__question', + 'item__category', Prefetch('addons', qs.select_related('item', 'variation')), - Prefetch('order', Order.objects.select_related('invoice_address').prefetch_related( - Prefetch( - 'event', - Event.objects.select_related('organizer') - ), + Prefetch('subevent', queryset=self.request.event.subevents.prefetch_related( + Prefetch('meta_values', to_attr='meta_values_cached', + queryset=SubEventMetaValue.objects.select_related('property')) + )), + Prefetch('order', self.request.event.orders.select_related('invoice_address').prefetch_related( Prefetch( 'positions', qs.prefetch_related( - 'item', 'variation', 'answers', 'answers__options', 'answers__question', Prefetch('checkins', queryset=Checkin.objects.all()), + Prefetch('item', queryset=self.request.event.items.prefetch_related( + Prefetch('meta_values', ItemMetaValue.objects.select_related('property'), + to_attr='meta_values_cached') + )), + 'variation', 'answers', 'answers__options', 'answers__question', + 'item__category', 'addon_to', 'seat', + Prefetch('subevent', queryset=self.request.event.subevents.prefetch_related( + Prefetch('meta_values', to_attr='meta_values_cached', + queryset=SubEventMetaValue.objects.select_related('property')) + )), + Prefetch('addons', qs.select_related('item', 'variation', 'seat')) ) ) )) ).select_related( - 'item', 'variation', 'item__category', 'addon_to', 'seat' + 'addon_to', 'seat' ) else: qs = qs.prefetch_related( diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index ce0bc927fa..1441de7184 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -1448,7 +1448,10 @@ class SubEvent(EventMixin, LoggedModel): @property def meta_data(self): data = self.event.meta_data - data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()}) + if hasattr(self, 'meta_values_cached'): + data.update({v.property.name: v.value for v in self.meta_values_cached}) + else: + data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()}) return data @property diff --git a/src/tests/api/test_checkinrpc.py b/src/tests/api/test_checkinrpc.py index 4c5ff6a7b9..ce908d32cf 100644 --- a/src/tests/api/test_checkinrpc.py +++ b/src/tests/api/test_checkinrpc.py @@ -898,7 +898,7 @@ def test_checkin_only_permission(token_client, event, team, organizer, clist_all assert resp.status_code == 200 assert resp.data['count'] > 0 - # With all permissions, I can not request PDF data during checkin + # With limited permissions, I can not request PDF data during checkin resp = _redeem(token_client, organizer, [clist_all], p.secret, {}, '?pdf_data=true') assert resp.status_code == 201 assert not resp.data['position'].get('pdf_data') diff --git a/src/tests/api/test_orders.py b/src/tests/api/test_orders.py index 67845d9f20..81c0096d22 100644 --- a/src/tests/api/test_orders.py +++ b/src/tests/api/test_orders.py @@ -1657,7 +1657,7 @@ def test_revoked_secret_list(token_client, organizer, event): @pytest.mark.django_db -def test_pdf_data(token_client, organizer, event, order): +def test_pdf_data(token_client, organizer, event, order, django_assert_max_num_queries): # order detail resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/{}/?pdf_data=true'.format( organizer.slug, event.slug, order.code @@ -1671,9 +1671,10 @@ def test_pdf_data(token_client, organizer, event, order): assert not resp.data['positions'][0].get('pdf_data') # order list - resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format( - organizer.slug, event.slug - )) + with django_assert_max_num_queries(29): + resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format( + organizer.slug, event.slug + )) assert resp.status_code == 200 assert resp.data['results'][0]['positions'][0].get('pdf_data') resp = token_client.get('/api/v1/organizers/{}/events/{}/orders/'.format( @@ -1683,9 +1684,10 @@ def test_pdf_data(token_client, organizer, event, order): assert not resp.data['results'][0]['positions'][0].get('pdf_data') # position list - resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/?pdf_data=true'.format( - organizer.slug, event.slug - )) + with django_assert_max_num_queries(32): + resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/?pdf_data=true'.format( + organizer.slug, event.slug + )) assert resp.status_code == 200 assert resp.data['results'][0].get('pdf_data') resp = token_client.get('/api/v1/organizers/{}/events/{}/orderpositions/'.format(