forked from CGM_Public/pretix_original
Reduce number of redundant SQL queries
This commit is contained in:
@@ -28,48 +28,51 @@ def tuplesum(tuples: Iterable[Tuple]) -> Tuple:
|
||||
return tuple(map(mysum, zip(*list(tuples))))
|
||||
|
||||
|
||||
def dictsum(*dicts) -> dict:
|
||||
res = {}
|
||||
keys = set()
|
||||
for d in dicts:
|
||||
keys |= set(d.keys())
|
||||
for k in keys:
|
||||
res[k] = (sum(d[k][0] for d in dicts if k in d), sum(d[k][1] for d in dicts if k in d))
|
||||
return res
|
||||
|
||||
|
||||
def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]], Dict[str, Tuple[Decimal, Decimal]]]:
|
||||
items = event.items.all().select_related(
|
||||
'category', # for re-grouping
|
||||
).prefetch_related(
|
||||
'variations'
|
||||
).order_by('category__position', 'category_id', 'name')
|
||||
|
||||
num_total = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in (OrderPosition.objects
|
||||
.filter(order__event=event,
|
||||
order__status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED, Order.STATUS_PAID])
|
||||
.values('item', 'variation')
|
||||
.annotate(cnt=Count('id'), price=Sum('price')).order_by())
|
||||
}
|
||||
counters = OrderPosition.objects.filter(
|
||||
order__event=event
|
||||
).values(
|
||||
'item', 'variation', 'order__status'
|
||||
).annotate(cnt=Count('id'), price=Sum('price')).order_by()
|
||||
|
||||
num_canceled = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in (OrderPosition.objects
|
||||
.filter(order__event=event, order__status=Order.STATUS_CANCELED)
|
||||
.values('item', 'variation')
|
||||
.annotate(cnt=Count('id'), price=Sum('price')).order_by())
|
||||
for p in counters if p['order__status'] == Order.STATUS_CANCELED
|
||||
}
|
||||
num_refunded = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in (OrderPosition.objects
|
||||
.filter(order__event=event, order__status=Order.STATUS_REFUNDED)
|
||||
.values('item', 'variation')
|
||||
.annotate(cnt=Count('id'), price=Sum('price')).order_by())
|
||||
}
|
||||
num_pending = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in (OrderPosition.objects
|
||||
.filter(order__event=event,
|
||||
order__status__in=(Order.STATUS_PENDING, Order.STATUS_EXPIRED))
|
||||
.values('item', 'variation')
|
||||
.annotate(cnt=Count('id'), price=Sum('price')).order_by())
|
||||
for p in counters if p['order__status'] == Order.STATUS_REFUNDED
|
||||
}
|
||||
num_paid = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in (OrderPosition.objects
|
||||
.filter(order__event=event, order__status=Order.STATUS_PAID)
|
||||
.values('item', 'variation')
|
||||
.annotate(cnt=Count('id'), price=Sum('price')).order_by())
|
||||
for p in counters if p['order__status'] == Order.STATUS_PAID
|
||||
}
|
||||
num_s_pending = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in counters if p['order__status'] == Order.STATUS_PENDING
|
||||
}
|
||||
num_expired = {
|
||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||
for p in counters if p['order__status'] == Order.STATUS_EXPIRED
|
||||
}
|
||||
num_pending = dictsum(num_s_pending, num_expired)
|
||||
num_total = dictsum(num_pending, num_paid)
|
||||
|
||||
for item in items:
|
||||
item.all_variations = list(item.variations.all())
|
||||
@@ -120,41 +123,33 @@ def order_overview(event: Event) -> Tuple[List[Tuple[ItemCategory, List[Item]]],
|
||||
payment_cat_obj = DummyObject()
|
||||
payment_cat_obj.name = _('Payment method fees')
|
||||
payment_items = []
|
||||
num_total = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'])
|
||||
for o in (Order.objects
|
||||
.filter(event=event)
|
||||
.values('payment_provider')
|
||||
.annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by())
|
||||
}
|
||||
|
||||
counters = event.orders.values('payment_provider', 'status').annotate(
|
||||
cnt=Count('id'), payment_fee=Sum('payment_fee')
|
||||
).order_by()
|
||||
|
||||
num_canceled = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'])
|
||||
for o in (Order.objects
|
||||
.filter(event=event, status=Order.STATUS_CANCELED)
|
||||
.values('payment_provider')
|
||||
.annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by())
|
||||
for o in counters if o['status'] == Order.STATUS_CANCELED
|
||||
}
|
||||
num_refunded = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'])
|
||||
for o in (Order.objects
|
||||
.filter(event=event, status=Order.STATUS_REFUNDED)
|
||||
.values('payment_provider')
|
||||
.annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by())
|
||||
for o in counters if o['status'] == Order.STATUS_REFUNDED
|
||||
}
|
||||
num_pending = {
|
||||
num_s_pending = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'])
|
||||
for o in (Order.objects
|
||||
.filter(event=event, status__in=(Order.STATUS_PENDING, Order.STATUS_EXPIRED))
|
||||
.values('payment_provider')
|
||||
.annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by())
|
||||
for o in counters if o['status'] == Order.STATUS_PENDING
|
||||
}
|
||||
num_expired = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'])
|
||||
for o in counters if o['status'] == Order.STATUS_EXPIRED
|
||||
}
|
||||
num_paid = {
|
||||
o['payment_provider']: (o['cnt'], o['payment_fee'])
|
||||
for o in (Order.objects
|
||||
.filter(event=event, status=Order.STATUS_PAID)
|
||||
.values('payment_provider')
|
||||
.annotate(cnt=Count('id'), payment_fee=Sum('payment_fee')).order_by())
|
||||
for o in counters if o['status'] == Order.STATUS_PAID
|
||||
}
|
||||
num_pending = dictsum(num_s_pending, num_expired)
|
||||
num_total = dictsum(num_pending, num_paid)
|
||||
|
||||
provider_names = {}
|
||||
responses = register_payment_providers.send(event)
|
||||
|
||||
@@ -514,8 +514,15 @@ class SettingsSandbox:
|
||||
self._event.settings.set(self._convert_key(key), value)
|
||||
|
||||
|
||||
class GlobalSettingsObject:
|
||||
class GlobalSettingsObject():
|
||||
data_dict = None
|
||||
|
||||
def __init__(self):
|
||||
self.settings = SettingsProxy(self, type=GlobalSetting)
|
||||
self.setting_objects = GlobalSetting.objects
|
||||
self.slug = '_global'
|
||||
# This is a singleton-like object. Multiple objects can exist, but they share their state
|
||||
if GlobalSettingsObject.data_dict:
|
||||
self.__dict__ = GlobalSettingsObject.data_dict
|
||||
else:
|
||||
self.settings = SettingsProxy(self, type=GlobalSetting)
|
||||
self.setting_objects = GlobalSetting.objects
|
||||
self.slug = '_global'
|
||||
GlobalSettingsObject.data_dict = self.__dict__
|
||||
|
||||
@@ -31,26 +31,16 @@
|
||||
{% if quota.size == None %}{% trans "Infinite" %}{% else %}{{ quota.size }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-9">{% trans "Paid orders" %}</div>
|
||||
<div class="col-xs-3 text-right"> – {{ quota.count_paid_orders }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-9">{% trans "Pending orders" %}</div>
|
||||
<div class="col-xs-3 text-right"> – {{ quota.count_pending_orders }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-9">{% trans "Vouchers" %}</div>
|
||||
<div class="col-xs-3 text-right"> – {{ quota.count_blocking_vouchers }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-9">{% trans "Current user's carts" %}</div>
|
||||
<div class="col-xs-3 text-right"> – {{ quota.count_in_cart }}</div>
|
||||
</div>
|
||||
{% for row in quota_table_rows %}
|
||||
<div class="row">
|
||||
<div class="col-xs-9">{{ row.label }}</div>
|
||||
<div class="col-xs-3 text-right"> – {{ row.value }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="row">
|
||||
<div class="col-xs-9"><strong>{% trans "Current availability" %}</strong></div>
|
||||
<div class="col-xs-3 text-right">
|
||||
{% if quota.size == None %}{% trans "Infinite" %}{% else %}{{ quota.availability.1 }}{% endif %}
|
||||
{% if quota.size == None %}{% trans "Infinite" %}{% else %}{{ avail.1 }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -330,8 +330,8 @@ class QuestionMixin:
|
||||
can_order=False, can_delete=True, extra=0
|
||||
)
|
||||
return formsetclass(self.request.POST if self.request.method == "POST" else None,
|
||||
queryset=(QuestionOption.objects.filter(question=self.get_object())
|
||||
if self.get_object() else QuestionOption.objects.none()),
|
||||
queryset=(QuestionOption.objects.filter(question=self.object)
|
||||
if self.object else QuestionOption.objects.none()),
|
||||
event=self.request.event)
|
||||
|
||||
def save_formset(self, obj):
|
||||
@@ -423,8 +423,9 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
ctx['items'] = self.object.items.all()
|
||||
ctx['stats'] = self.get_answer_statistics()
|
||||
ctx['stats_json'] = json.dumps(self.get_answer_statistics())
|
||||
stats = self.get_answer_statistics()
|
||||
ctx['stats'] = stats
|
||||
ctx['stats_json'] = json.dumps(stats)
|
||||
return ctx
|
||||
|
||||
def get_object(self, queryset=None) -> Question:
|
||||
@@ -599,6 +600,10 @@ class QuotaView(ChartContainingView, DetailView):
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
ctx = super().get_context_data()
|
||||
|
||||
avail = self.object.availability()
|
||||
ctx['avail'] = avail
|
||||
|
||||
data = [
|
||||
{
|
||||
'label': ugettext('Paid orders'),
|
||||
@@ -617,10 +622,12 @@ class QuotaView(ChartContainingView, DetailView):
|
||||
'value': self.object.count_in_cart()
|
||||
}
|
||||
]
|
||||
ctx['quota_table_rows'] = list(data)
|
||||
|
||||
if self.object.size is not None:
|
||||
data.append({
|
||||
'label': ugettext('Current availability'),
|
||||
'value': self.object.availability()[1]
|
||||
'value': avail[1]
|
||||
})
|
||||
ctx['quota_chart_data'] = json.dumps(data)
|
||||
return ctx
|
||||
|
||||
@@ -20,8 +20,8 @@ class EventList(ListView):
|
||||
def get_queryset(self):
|
||||
return Event.objects.filter(
|
||||
permitted__id__exact=self.request.user.pk
|
||||
).prefetch_related(
|
||||
"organizer",
|
||||
).select_related("organizer").prefetch_related(
|
||||
"setting_objects", "organizer__setting_objects"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,33 +15,24 @@ class CartMixin:
|
||||
"""
|
||||
A list of this users cart position
|
||||
"""
|
||||
return list(CartPosition.objects.filter(
|
||||
cart_id=self.request.session.session_key, event=self.request.event
|
||||
).order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation'
|
||||
).prefetch_related(
|
||||
'item__questions', 'answers'
|
||||
))
|
||||
return list(get_cart(self.request))
|
||||
|
||||
def get_cart(self, answers=False, queryset=None, payment_fee=None, payment_fee_tax_rate=None, downloads=False):
|
||||
queryset = queryset or CartPosition.objects.filter(
|
||||
cart_id=self.request.session.session_key, event=self.request.event
|
||||
)
|
||||
if queryset:
|
||||
prefetch = []
|
||||
if answers:
|
||||
prefetch.append('item__questions')
|
||||
prefetch.append('answers')
|
||||
|
||||
prefetch = []
|
||||
if answers:
|
||||
prefetch.append('item__questions')
|
||||
prefetch.append('answers')
|
||||
|
||||
cartpos = queryset.order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation'
|
||||
).prefetch_related(
|
||||
*prefetch
|
||||
)
|
||||
cartpos = queryset.order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation'
|
||||
).prefetch_related(
|
||||
*prefetch
|
||||
)
|
||||
else:
|
||||
cartpos = self.positions
|
||||
|
||||
# Group items of the same variation
|
||||
# We do this by list manipulations instead of a GROUP BY query, as
|
||||
@@ -101,6 +92,20 @@ class CartMixin:
|
||||
return payment_fee
|
||||
|
||||
|
||||
def get_cart(request):
|
||||
if not hasattr(request, '_cart_cache'):
|
||||
request._cart_cache = CartPosition.objects.filter(
|
||||
cart_id=request.session.session_key, event=request.event
|
||||
).order_by(
|
||||
'item', 'variation'
|
||||
).select_related(
|
||||
'item', 'variation'
|
||||
).prefetch_related(
|
||||
'item__questions', 'answers'
|
||||
)
|
||||
return request._cart_cache
|
||||
|
||||
|
||||
class EventViewMixin:
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
@@ -4,15 +4,17 @@ from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import View
|
||||
|
||||
from pretix.base.models import CartPosition
|
||||
from pretix.multidomain.urlreverse import eventreverse
|
||||
from pretix.presale.checkoutflow import get_checkout_flow
|
||||
from pretix.presale.views import CartMixin
|
||||
|
||||
|
||||
class CheckoutView(CartMixin, View):
|
||||
class CheckoutView(View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
if not self.positions and "async_id" not in request.GET:
|
||||
has_cart = CartPosition.objects.filter(
|
||||
cart_id=self.request.session.session_key, event=self.request.event).exists()
|
||||
if not has_cart and "async_id" not in request.GET:
|
||||
messages.error(request, _("Your cart is empty"))
|
||||
return redirect(eventreverse(self.request.event, 'presale:event.index'))
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import sys
|
||||
|
||||
from django.db.models import Count, Q
|
||||
from django.db.models import Count, Prefetch, Q
|
||||
from django.utils.timezone import now
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from pretix.base.models import ItemVariation
|
||||
|
||||
from . import CartMixin, EventViewMixin
|
||||
|
||||
|
||||
@@ -21,48 +23,56 @@ def item_group_by_category(items):
|
||||
)
|
||||
|
||||
|
||||
def get_grouped_items(event):
|
||||
items = event.items.all().filter(
|
||||
Q(active=True)
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
|
||||
& Q(hide_without_voucher=False)
|
||||
).select_related(
|
||||
'category', # for re-grouping
|
||||
).prefetch_related(
|
||||
'quotas', 'variations__quotas', 'quotas__event', # for .availability()
|
||||
Prefetch('variations', to_attr='available_variations',
|
||||
queryset=ItemVariation.objects.filter(active=True, quotas__isnull=False).distinct()),
|
||||
).annotate(
|
||||
quotac=Count('quotas'),
|
||||
has_variations=Count('variations')
|
||||
).filter(
|
||||
quotac__gt=0
|
||||
).order_by('category__position', 'category_id', 'position', 'name')
|
||||
display_add_to_cart = False
|
||||
for item in items:
|
||||
if not item.has_variations:
|
||||
item.cached_availability = list(item.check_quotas())
|
||||
item.order_max = min(item.cached_availability[1]
|
||||
if item.cached_availability[1] is not None else sys.maxsize,
|
||||
int(event.settings.max_items_per_order))
|
||||
item.price = item.default_price
|
||||
display_add_to_cart = display_add_to_cart or item.order_max > 0
|
||||
else:
|
||||
for var in item.available_variations:
|
||||
var.cached_availability = list(var.check_quotas())
|
||||
var.order_max = min(var.cached_availability[1]
|
||||
if var.cached_availability[1] is not None else sys.maxsize,
|
||||
int(event.settings.max_items_per_order))
|
||||
display_add_to_cart = display_add_to_cart or var.order_max > 0
|
||||
var.price = var.default_price if var.default_price is not None else item.default_price
|
||||
if len(item.available_variations) > 0:
|
||||
item.min_price = min([v.price for v in item.available_variations])
|
||||
item.max_price = max([v.price for v in item.available_variations])
|
||||
|
||||
items = [item for item in items if len(item.available_variations) > 0 or not item.has_variations]
|
||||
return items, display_add_to_cart
|
||||
|
||||
|
||||
class EventIndex(EventViewMixin, CartMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# Fetch all items
|
||||
items = self.request.event.items.all().filter(
|
||||
Q(active=True)
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
|
||||
& Q(hide_without_voucher=False)
|
||||
).select_related(
|
||||
'category', # for re-grouping
|
||||
).prefetch_related(
|
||||
'quotas', 'variations__quotas', 'quotas__event' # for .availability()
|
||||
).annotate(quotac=Count('quotas')).filter(
|
||||
quotac__gt=0
|
||||
).order_by('category__position', 'category_id', 'position', 'name')
|
||||
display_add_to_cart = False
|
||||
for item in items:
|
||||
item.available_variations = list(item.variations.filter(active=True, quotas__isnull=False).distinct())
|
||||
item.has_variations = item.variations.exists()
|
||||
if not item.has_variations:
|
||||
item.cached_availability = list(item.check_quotas())
|
||||
item.order_max = min(item.cached_availability[1]
|
||||
if item.cached_availability[1] is not None else sys.maxsize,
|
||||
int(self.request.event.settings.max_items_per_order))
|
||||
item.price = item.default_price
|
||||
display_add_to_cart = display_add_to_cart or item.order_max > 0
|
||||
else:
|
||||
for var in item.available_variations:
|
||||
var.cached_availability = list(var.check_quotas())
|
||||
var.order_max = min(var.cached_availability[1]
|
||||
if var.cached_availability[1] is not None else sys.maxsize,
|
||||
int(self.request.event.settings.max_items_per_order))
|
||||
display_add_to_cart = display_add_to_cart or var.order_max > 0
|
||||
var.price = var.default_price if var.default_price is not None else item.default_price
|
||||
if len(item.available_variations) > 0:
|
||||
item.min_price = min([v.price for v in item.available_variations])
|
||||
item.max_price = max([v.price for v in item.available_variations])
|
||||
|
||||
items = [item for item in items if len(item.available_variations) > 0 or not item.has_variations]
|
||||
items, display_add_to_cart = get_grouped_items(self.request.event)
|
||||
|
||||
# Regroup those by category
|
||||
context['items_by_category'] = item_group_by_category(items)
|
||||
|
||||
@@ -34,7 +34,7 @@ class OrderDetailMixin:
|
||||
@cached_property
|
||||
def order(self):
|
||||
try:
|
||||
order = Order.objects.get(event=self.request.event, code=self.kwargs['order'])
|
||||
order = self.request.event.orders.get(code=self.kwargs['order'])
|
||||
if order.secret.lower() == self.kwargs['secret'].lower():
|
||||
return order
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user