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
This commit is contained in:
Richard Schreiber
2025-12-03 15:37:40 +01:00
committed by GitHub
parent d3fde85c39
commit 1a40215e91
6 changed files with 13 additions and 7 deletions

View File

@@ -567,7 +567,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
write_permission = 'can_change_items' write_permission = 'can_change_items'
def get_queryset(self): 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): def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()).distinct() queryset = self.filter_queryset(self.get_queryset()).distinct()

View File

@@ -721,7 +721,7 @@ class MembershipViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
return Membership.objects.filter( return Membership.objects.filter(
customer__organizer=self.request.organizer customer__organizer=self.request.organizer
) ).select_related('customer')
def get_serializer_context(self): def get_serializer_context(self):
ctx = super().get_serializer_context() ctx = super().get_serializer_context()

View File

@@ -19,6 +19,7 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see # You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
# #
from django.db import transaction from django.db import transaction
from django.db.models import F, Q from django.db.models import F, Q
from django.utils.timezone import now from django.utils.timezone import now
@@ -64,8 +65,13 @@ class VoucherViewSet(viewsets.ModelViewSet):
permission = 'can_view_vouchers' permission = 'can_view_vouchers'
write_permission = 'can_change_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): 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() @transaction.atomic()
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):

View File

@@ -623,7 +623,7 @@ class Voucher(LoggedModel):
return max(1, self.min_usages - self.redeemed) return max(1, self.min_usages - self.redeemed)
@classmethod @classmethod
def annotate_budget_used_orders(cls, qs): def annotate_budget_used(cls, qs):
opq = OrderPosition.objects.filter( opq = OrderPosition.objects.filter(
voucher_id=OuterRef('pk'), voucher_id=OuterRef('pk'),
voucher_budget_use__isnull=False, voucher_budget_use__isnull=False,
@@ -632,7 +632,7 @@ class Voucher(LoggedModel):
Order.STATUS_PENDING Order.STATUS_PENDING
] ]
).order_by().values('voucher_id').annotate(s=Sum('voucher_budget_use')).values('s') ).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): def budget_used(self):
ops = OrderPosition.objects.filter( ops = OrderPosition.objects.filter(

View File

@@ -165,7 +165,7 @@
{% if v.budget|default_if_none:"NONE" != "NONE" %} {% if v.budget|default_if_none:"NONE" != "NONE" %}
<br> <br>
<small class="text-muted"> <small class="text-muted">
{{ 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 }}
</small> </small>
{% endif %} {% endif %}
</td> </td>

View File

@@ -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 @scopes_disabled() # we have an event check here, and we can save some performance on subqueries
def get_queryset(self): 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'))) Exists(WaitingListEntry.objects.filter(voucher_id=OuterRef('pk')))
).select_related( ).select_related(
'item', 'variation', 'seat' 'item', 'variation', 'seat'