From 1a40215e91b736505b30b6825e2cf893355da561 Mon Sep 17 00:00:00 2001 From: Richard Schreiber Date: Wed, 3 Dec 2025 15:37:40 +0100 Subject: [PATCH] Fix N+1 queries in API (#5684) * Fix N+1 query in API quotas list * fix membership N+1 * fix vouchers N+1 budget_used * rename and reuse Voucher.annotate_budget_used_orders to budget_used * fix flake8 --- src/pretix/api/views/item.py | 2 +- src/pretix/api/views/organizer.py | 2 +- src/pretix/api/views/voucher.py | 8 +++++++- src/pretix/base/models/vouchers.py | 4 ++-- .../control/templates/pretixcontrol/vouchers/index.html | 2 +- src/pretix/control/views/vouchers.py | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/pretix/api/views/item.py b/src/pretix/api/views/item.py index 443a5157f4..a84fa589ac 100644 --- a/src/pretix/api/views/item.py +++ b/src/pretix/api/views/item.py @@ -567,7 +567,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet): write_permission = 'can_change_items' def get_queryset(self): - return self.request.event.quotas.all() + return self.request.event.quotas.select_related('subevent').prefetch_related('items', 'variations').all() def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()).distinct() diff --git a/src/pretix/api/views/organizer.py b/src/pretix/api/views/organizer.py index f600086e1c..f084a16794 100644 --- a/src/pretix/api/views/organizer.py +++ b/src/pretix/api/views/organizer.py @@ -721,7 +721,7 @@ class MembershipViewSet(viewsets.ModelViewSet): def get_queryset(self): return Membership.objects.filter( customer__organizer=self.request.organizer - ) + ).select_related('customer') def get_serializer_context(self): ctx = super().get_serializer_context() diff --git a/src/pretix/api/views/voucher.py b/src/pretix/api/views/voucher.py index c0c0c10169..2d6243b248 100644 --- a/src/pretix/api/views/voucher.py +++ b/src/pretix/api/views/voucher.py @@ -19,6 +19,7 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # + from django.db import transaction from django.db.models import F, Q from django.utils.timezone import now @@ -64,8 +65,13 @@ class VoucherViewSet(viewsets.ModelViewSet): permission = 'can_view_vouchers' write_permission = 'can_change_vouchers' + @scopes_disabled() # we have an event check here, and we can save some performance on subqueries def get_queryset(self): - return self.request.event.vouchers.select_related('seat').all() + return Voucher.annotate_budget_used( + self.request.event.vouchers + ).select_related( + 'item', 'quota', 'seat', 'variation' + ) @transaction.atomic() def create(self, request, *args, **kwargs): diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index a1531e9871..3b4e919c91 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -623,7 +623,7 @@ class Voucher(LoggedModel): return max(1, self.min_usages - self.redeemed) @classmethod - def annotate_budget_used_orders(cls, qs): + def annotate_budget_used(cls, qs): opq = OrderPosition.objects.filter( voucher_id=OuterRef('pk'), voucher_budget_use__isnull=False, @@ -632,7 +632,7 @@ class Voucher(LoggedModel): Order.STATUS_PENDING ] ).order_by().values('voucher_id').annotate(s=Sum('voucher_budget_use')).values('s') - return qs.annotate(budget_used_orders=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=13, decimal_places=2)), Decimal('0.00'))) + return qs.annotate(budget_used=Coalesce(Subquery(opq, output_field=models.DecimalField(max_digits=13, decimal_places=2)), Decimal('0.00'))) def budget_used(self): ops = OrderPosition.objects.filter( diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/index.html b/src/pretix/control/templates/pretixcontrol/vouchers/index.html index befc9f0575..4549cd46b7 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/index.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/index.html @@ -165,7 +165,7 @@ {% if v.budget|default_if_none:"NONE" != "NONE" %}
- {{ v.budget_used_orders|money:request.event.currency }} / {{ v.budget|money:request.event.currency }} + {{ v.budget_used|money:request.event.currency }} / {{ v.budget|money:request.event.currency }} {% endif %} diff --git a/src/pretix/control/views/vouchers.py b/src/pretix/control/views/vouchers.py index e7308b9d4f..5bdabb2770 100644 --- a/src/pretix/control/views/vouchers.py +++ b/src/pretix/control/views/vouchers.py @@ -87,7 +87,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView): @scopes_disabled() # we have an event check here, and we can save some performance on subqueries def get_queryset(self): - qs = Voucher.annotate_budget_used_orders(self.request.event.vouchers.exclude( + qs = Voucher.annotate_budget_used(self.request.event.vouchers.exclude( Exists(WaitingListEntry.objects.filter(voucher_id=OuterRef('pk'))) ).select_related( 'item', 'variation', 'seat'