Orders: Fix N+1 query issues on order detail and change page

There is one I couldn't fix: Loading the list of tax rules for every
select box on the OrderChange page. Unfortunately, Django has a
cache-breaking .all() in ModelChoiceField and that would need nasty
patching that didn't feel worth it
This commit is contained in:
Raphael Michel
2026-07-16 16:52:21 +02:00
parent 2660dfe1bc
commit 093fc8d7de
3 changed files with 36 additions and 14 deletions
+1 -3
View File
@@ -562,9 +562,7 @@ class OrderPositionChangeForm(forms.Form):
if instance.addon_to_id:
del self.fields['operation_split']
if not instance.seat and not (
instance.item.seat_category_mappings.filter(subevent=instance.subevent).exists()
):
if not instance.seat and not instance._seat_allowed:
del self.fields['seat']
choices = [
@@ -833,7 +833,7 @@
<strong>{% trans "Pending total" %}</strong>
</div>
<div class="col-md-3 col-xs-6 col-md-offset-5 price">
<strong>{{ order.pending_sum|money:event.currency }}</strong>
<strong>{{ pending_sum|money:event.currency }}</strong>
</div>
<div class="clearfix"></div>
</div>
+34 -10
View File
@@ -81,7 +81,7 @@ from pretix.base.i18n import language
from pretix.base.models import (
CachedFile, CachedTicket, Checkin, Invoice, InvoiceAddress, Item,
ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
ScheduledEventExport, generate_secret,
ScheduledEventExport, generate_secret, SeatCategoryMapping,
)
from pretix.base.models.orders import (
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
@@ -564,10 +564,11 @@ class OrderDetail(OrderView):
})
ctx['display_locale'] = dict(settings.LANGUAGES)[self.object.locale or self.request.event.settings.locale]
ctx['overpaid'] = self.order.pending_sum * -1
pending_sum = self.order.pending_sum
ctx['overpaid'] = pending_sum * -1
ctx['download_buttons'] = self.download_buttons
ctx['payment_refund_sum'] = self.order.payment_refund_sum
ctx['pending_sum'] = self.order.pending_sum
ctx['pending_sum'] = pending_sum
ctx['uncancelled_invoice'] = self.order.invoices.exclude(
Exists(self.order.invoices.filter(refers=OuterRef('pk'), is_cancellation=True))
).exclude(is_cancellation=True).first()
@@ -603,6 +604,7 @@ class OrderDetail(OrderView):
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options').select_related('question')),
Prefetch('all_checkins', queryset=Checkin.all.select_related('list').order_by('datetime')),
Prefetch('print_logs', queryset=PrintLog.objects.select_related('device').order_by('datetime')),
Prefetch('subevent', queryset=self.request.event.subevents.all()),
).order_by('positionid')
positions = []
@@ -1981,24 +1983,46 @@ class OrderChange(OrderView):
def items(self):
return self.request.event.items.prefetch_related('variations', 'tax_rule').all()
@cached_property
def tax_rules(self):
return self.request.event.tax_rules.all()
@cached_property
def fees(self):
fees = list(self.order.fees.all())
for f in fees:
f.form = OrderFeeChangeForm(prefix='of-{}'.format(f.pk), instance=f,
data=self.request.POST if self.request.method == "POST" else None)
f.form = OrderFeeChangeForm(
prefix='of-{}'.format(f.pk),
instance=f,
tax_rules=self.tax_rules,
data=self.request.POST if self.request.method == "POST" else None
)
return fees
@cached_property
def positions(self):
positions = list(self.order.positions.select_related(
'item', 'item__tax_rule', 'used_membership', 'used_membership__membership_type', 'tax_rule',
'seat', 'subevent',
).prefetch_related('granted_memberships'))
'seat',
).prefetch_related(
Prefetch(
'subevent',
queryset=self.request.event.subevents.all(),
),
'granted_memberships',
'issued_gift_cards',
'addons',
).annotate(
_seat_allowed=Exists(SeatCategoryMapping.objects.filter(subevent=OuterRef("subevent"), product=OuterRef("item")))
))
for p in positions:
p.form = OrderPositionChangeForm(prefix='op-{}'.format(p.pk), instance=p, items=self.items,
initial={'seat': p.seat.seat_guid if p.seat else None},
data=self.request.POST if self.request.method == "POST" else None)
p.form = OrderPositionChangeForm(
prefix='op-{}'.format(p.pk),
instance=p,
items=self.items,
initial={'seat': p.seat.seat_guid if p.seat else None},
data=self.request.POST if self.request.method == "POST" else None
)
return positions
def get_context_data(self, **kwargs):