Re-structure some querying on cart and order pages to reduce load

This commit is contained in:
Raphael Michel
2020-12-13 16:29:17 +01:00
parent 5308099d84
commit 571fef4ed8
7 changed files with 48 additions and 39 deletions

View File

@@ -902,7 +902,7 @@ class Order(LockModel, LoggedModel):
@property @property
def positions_with_tickets(self): def positions_with_tickets(self):
for op in self.positions.all(): for op in self.positions.select_related('item'):
if not op.generate_ticket: if not op.generate_ticket:
continue continue
yield op yield op
@@ -1155,7 +1155,7 @@ class AbstractPosition(models.Model):
(2) questions: a list of Question objects, extended by an 'answer' property (2) questions: a list of Question objects, extended by an 'answer' property
""" """
self.answ = {} self.answ = {}
for a in self.answers.all(): for a in getattr(self, 'answerlist', self.answers.all()): # use prefetch_related cache from get_cart
self.answ[a.question_id] = a self.answ[a.question_id] = a
# We need to clone our question objects, otherwise we will override the cached # We need to clone our question objects, otherwise we will override the cached

View File

@@ -673,7 +673,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
return ctx return ctx
class PaymentStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): class PaymentStep(CartMixin, TemplateFlowStep):
priority = 200 priority = 200
identifier = "payment" identifier = "payment"
template_name = "pretixpresale/event/checkout_payment.html" template_name = "pretixpresale/event/checkout_payment.html"

View File

@@ -313,7 +313,7 @@
{% endif %} {% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% if order.user_change_allowed or order.user_cancel_allowed %} {% if user_change_allowed or user_cancel_allowed %}
<div class="panel panel-primary panel-cancellation"> <div class="panel panel-primary panel-cancellation">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title"> <h3 class="panel-title">
@@ -321,7 +321,7 @@
</h3> </h3>
</div> </div>
<ul class="list-group"> <ul class="list-group">
{% if order.user_change_allowed %} {% if user_change_allowed %}
<li class="list-group-item"> <li class="list-group-item">
<p> <p>
{% blocktrans trimmed %} {% blocktrans trimmed %}
@@ -335,7 +335,7 @@
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% if order.user_cancel_allowed %} {% if user_cancel_allowed %}
<li class="list-group-item"> <li class="list-group-item">
{% if order.status == "p" and order.total != 0 %} {% if order.status == "p" and order.total != 0 %}
{% if order.user_cancel_fee >= order.total %} {% if order.user_cancel_fee >= order.total %}

View File

@@ -5,14 +5,14 @@ from functools import wraps
from itertools import groupby from itertools import groupby
from django.conf import settings from django.conf import settings
from django.db.models import Prefetch, Sum from django.db.models import Prefetch, Sum, Exists, OuterRef
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now from django.utils.timezone import now
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import ( from pretix.base.models import (
CartPosition, InvoiceAddress, OrderPosition, QuestionAnswer, CartPosition, InvoiceAddress, OrderPosition, QuestionAnswer, ItemAddOn, Question, QuestionOption,
) )
from pretix.base.services.cart import get_fees from pretix.base.services.cart import get_fees
from pretix.helpers.cookies import set_cookie_without_samesite from pretix.helpers.cookies import set_cookie_without_samesite
@@ -68,12 +68,15 @@ class CartMixin:
prefetch.append(Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options'))) prefetch.append(Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options')))
cartpos = queryset.order_by( cartpos = queryset.order_by(
'item__category__position', 'item__category_id', 'item__position', 'item__name', 'variation__value' 'item__category__position', 'item__category_id', 'item__position', 'item__name',
'variation__value'
).select_related( ).select_related(
'item', 'variation', 'addon_to', 'subevent', 'subevent__event', 'subevent__event__organizer', 'seat' 'item', 'variation', 'addon_to', 'subevent', 'subevent__event',
'subevent__event__organizer', 'seat'
).prefetch_related( ).prefetch_related(
*prefetch *prefetch
) )
else: else:
cartpos = self.positions cartpos = self.positions
@@ -123,7 +126,7 @@ class CartMixin:
or pos.pk in has_addons \ or pos.pk in has_addons \
or pos.addon_to_id \ or pos.addon_to_id \
or pos.item.issue_giftcard \ or pos.item.issue_giftcard \
or (answers and (has_attendee_data or pos.item.questions.exists())): or (answers and (has_attendee_data or bool(pos.item.questions.all()))): # do not use .exists() to re-use prefetch cache
return ( return (
# standalone positions are grouped by main product position id, addons below them also sorted by position id # standalone positions are grouped by main product position id, addons below them also sorted by position id
i, addon_penalty, pos.pk, i, addon_penalty, pos.pk,
@@ -147,7 +150,8 @@ class CartMixin:
group.total = group.count * group.price group.total = group.count * group.price
group.net_total = group.count * group.net_price group.net_total = group.count * group.net_price
group.has_questions = answers and k[0] != "" group.has_questions = answers and k[0] != ""
group.tax_rule = group.item.tax_rule if not hasattr(group, 'tax_rule'):
group.tax_rule = group.item.tax_rule
group.bundle_sum = group.price + sum(a.price for a in has_addons[group.pk]) group.bundle_sum = group.price + sum(a.price for a in has_addons[group.pk])
group.bundle_sum_net = group.net_price + sum(a.net_price for a in has_addons[group.pk]) group.bundle_sum_net = group.net_price + sum(a.net_price for a in has_addons[group.pk])
@@ -214,6 +218,8 @@ def cart_exists(request):
def get_cart(request): def get_cart(request):
from pretix.presale.views.cart import get_or_create_cart_id from pretix.presale.views.cart import get_or_create_cart_id
qqs = request.event.questions.all()
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
if not hasattr(request, '_cart_cache'): if not hasattr(request, '_cart_cache'):
cart_id = get_or_create_cart_id(request, create=False) cart_id = get_or_create_cart_id(request, create=False)
@@ -222,11 +228,36 @@ def get_cart(request):
else: else:
request._cart_cache = CartPosition.objects.filter( request._cart_cache = CartPosition.objects.filter(
cart_id=cart_id, event=request.event cart_id=cart_id, event=request.event
).annotate(
has_addon_choices=Exists(
ItemAddOn.objects.filter(
base_item_id=OuterRef('item_id')
)
)
).order_by( ).order_by(
'item', 'variation' 'item__category__position', 'item__category_id', 'item__position', 'item__name', 'variation__value'
).select_related( ).select_related(
'item', 'variation', 'subevent', 'subevent__event', 'subevent__event__organizer', 'item', 'variation', 'subevent', 'subevent__event', 'subevent__event__organizer',
'item__tax_rule', 'addon_to' 'item__tax_rule', 'addon_to'
).select_related(
'addon_to'
).prefetch_related(
'addons', 'addons__item', 'addons__variation',
Prefetch('answers',
QuestionAnswer.objects.prefetch_related('options'),
to_attr='answerlist'),
Prefetch('item__questions',
qqs.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
).select_related('dependency_question'),
to_attr='questions_to_ask')
) )
for cp in request._cart_cache: for cp in request._cart_cache:
cp.event = request.event # Populate field with known value to save queries cp.event = request.event # Populate field with known value to save queries

View File

@@ -408,7 +408,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['ev'] = self.subevent or self.request.event context['ev'] = self.subevent or self.request.event
context['subevent'] = self.subevent context['subevent'] = self.subevent
context['cart'] = self.get_cart() context['cart'] = self.get_cart()
context['has_addon_choices'] = get_cart(self.request).filter(item__addons__isnull=False).exists() context['has_addon_choices'] = any(cp.has_addon_choices for cp in get_cart(self.request))\
if self.subevent: if self.subevent:
context['frontpage_text'] = str(self.subevent.frontpage_text) context['frontpage_text'] = str(self.subevent.frontpage_text)

View File

@@ -245,6 +245,8 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin,
).exclude( ).exclude(
provider__in=('offsetting', 'reseller', 'boxoffice', 'manual') provider__in=('offsetting', 'reseller', 'boxoffice', 'manual')
) )
ctx['user_change_allowed'] = self.order.user_change_allowed
ctx['user_cancel_allowed'] = self.order.user_cancel_allowed
for r in ctx['refunds']: for r in ctx['refunds']:
if r.provider == 'giftcard': if r.provider == 'giftcard':
gc = GiftCard.objects.get(pk=r.info_data.get('gift_card')) gc = GiftCard.objects.get(pk=r.info_data.get('gift_card'))

View File

@@ -1,7 +1,5 @@
from django.db.models import Prefetch
from django.utils.functional import cached_property from django.utils.functional import cached_property
from pretix.base.models import Question, QuestionAnswer, QuestionOption
from pretix.base.views.mixins import BaseQuestionsViewMixin from pretix.base.views.mixins import BaseQuestionsViewMixin
from pretix.presale.forms.checkout import QuestionsForm from pretix.presale.forms.checkout import QuestionsForm
from pretix.presale.views import get_cart from pretix.presale.views import get_cart
@@ -13,27 +11,5 @@ class QuestionsViewMixin(BaseQuestionsViewMixin):
@cached_property @cached_property
def _positions_for_questions(self): def _positions_for_questions(self):
qqs = self.request.event.questions.all() cart = get_cart(self.request)
if self.only_user_visible:
qqs = qqs.filter(ask_during_checkin=False, hidden=False)
cart = get_cart(self.request).select_related(
'addon_to'
).prefetch_related(
'addons', 'addons__item', 'addons__variation',
Prefetch('answers',
QuestionAnswer.objects.prefetch_related('options'),
to_attr='answerlist'),
Prefetch('item__questions',
qqs.prefetch_related(
Prefetch('options', QuestionOption.objects.prefetch_related(Prefetch(
# This prefetch statement is utter bullshit, but it actually prevents Django from doing
# a lot of queries since ModelChoiceIterator stops trying to be clever once we have
# a prefetch lookup on this query...
'question',
Question.objects.none(),
to_attr='dummy'
)))
).select_related('dependency_question'),
to_attr='questions_to_ask')
)
return sorted(list(cart), key=self._keyfunc) return sorted(list(cart), key=self._keyfunc)