Refactor quota calculation (#1668)

This commit is contained in:
Raphael Michel
2020-05-07 09:34:27 +02:00
committed by GitHub
parent feb7f419d3
commit e117545b3f
15 changed files with 550 additions and 200 deletions

View File

@@ -59,7 +59,7 @@
</td>
<td>
<ul>
{% for item in q.items.all %}
{% for item in cached_items%}
{% if not item.has_variations %}
<li><a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=item.id %}">{{ item }}</a></li>
{% endif %}
@@ -74,7 +74,7 @@
<td>{{ q.subevent.name }} {{ q.subevent.get_date_range_display }}</td>
{% endif %}
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.availability closed=q.closed %}</td>
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.cached_avail closed=q.closed %}</td>
<td class="text-right flip">
<a href="{% url "control:event.items.quotas.edit" organizer=request.event.organizer.slug event=request.event.slug quota=q.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ q.id }}"

View File

@@ -25,6 +25,7 @@ from pretix.base.models import (
Item, ItemVariation, Order, OrderPosition, OrderRefund, RequiredAction,
SubEvent, Voucher, WaitingListEntry,
)
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.timeline import timeline_for_event
from pretix.control.forms.event import CommentForm
from pretix.control.signals import (
@@ -199,10 +200,20 @@ def waitinglist_widgets(sender, subevent=None, lazy=False, **kwargs):
@receiver(signal=event_dashboard_widgets)
def quota_widgets(sender, subevent=None, lazy=False, **kwargs):
widgets = []
quotas = sender.quotas.filter(subevent=subevent)
for q in sender.quotas.filter(subevent=subevent):
quotas_to_compute = [
q for q in quotas
if not q.cache_is_hot(now() + timedelta(seconds=5))
]
qa = QuotaAvailability()
if quotas_to_compute:
qa.queue(*quotas_to_compute)
qa.compute()
for q in quotas:
if not lazy:
status, left = q.availability(allow_cache=True)
status, left = qa.results[q] if q in qa.results else q.availability(allow_cache=True)
widgets.append({
'content': None if lazy else NUM_WIDGET.format(
num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',

View File

@@ -31,6 +31,7 @@ from pretix.base.models import (
)
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemAddOn, ItemBundle, ItemMetaValue
from pretix.base.services.quotas import QuotaAvailability
from pretix.base.services.tickets import invalidate_cache
from pretix.base.signals import quota_availability
from pretix.control.forms.item import (
@@ -643,9 +644,7 @@ class QuotaList(PaginationMixin, ListView):
template_name = 'pretixcontrol/items/quotas.html'
def get_queryset(self):
qs = Quota.objects.filter(
event=self.request.event
).prefetch_related(
qs = self.request.event.quotas.prefetch_related(
Prefetch(
"items",
queryset=Item.objects.annotate(
@@ -654,13 +653,28 @@ class QuotaList(PaginationMixin, ListView):
to_attr="cached_items"
),
"variations",
"variations__item"
"variations__item",
Prefetch(
"subevent",
queryset=self.request.event.subevents.all()
)
)
if self.request.GET.get("subevent", "") != "":
s = self.request.GET.get("subevent", "")
qs = qs.filter(subevent_id=s)
return qs
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
qa = QuotaAvailability()
qa.queue(*ctx['quotas'])
qa.compute()
for quota in ctx['quotas']:
qa.cached_avail = qa.results[quota]
return ctx
class QuotaCreate(EventPermissionRequiredMixin, CreateView):
model = Quota
@@ -719,28 +733,30 @@ class QuotaView(ChartContainingView, DetailView):
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data()
avail = self.object.availability()
ctx['avail'] = avail
qa = QuotaAvailability(full_results=True)
qa.queue(self.object)
qa.compute()
ctx['avail'] = qa.results[self.object]
data = [
{
'label': gettext('Paid orders'),
'value': self.object.count_paid_orders(),
'value': qa.count_paid_orders[self.object],
'sum': True,
},
{
'label': gettext('Pending orders'),
'value': self.object.count_pending_orders(),
'value': qa.count_pending_orders[self.object],
'sum': True,
},
{
'label': gettext('Vouchers and waiting list reservations'),
'value': self.object.count_blocking_vouchers(),
'value': qa.count_vouchers[self.object],
'sum': True,
},
{
'label': gettext('Current user\'s carts'),
'value': self.object.count_in_cart(),
'value': qa.count_cart[self.object],
'sum': True,
},
]
@@ -756,14 +772,14 @@ class QuotaView(ChartContainingView, DetailView):
})
data.append({
'label': gettext('Waiting list (pending)'),
'value': self.object.count_waiting_list_pending(),
'value': qa.count_waitinglist[self.object],
'sum': False,
})
if self.object.size is not None:
data.append({
'label': gettext('Currently for sale'),
'value': avail[1],
'value': ctx['avail'][1],
'sum': False,
'strong': True
})
@@ -786,7 +802,17 @@ class QuotaView(ChartContainingView, DetailView):
ctx['has_ignore_vouchers'] = Voucher.objects.filter(
Q(allow_ignore_quota=True) &
Q(Q(valid_until__isnull=True) | Q(valid_until__gte=now())) &
Q(Q(self.object._position_lookup) | Q(quota=self.object)) &
Q(
(
( # Orders for items which do not have any variations
Q(variation__isnull=True) &
Q(item_id__in=Quota.items.through.objects.filter(quota_id=self.object.pk).values_list('item_id', flat=True))
) | ( # Orders for items which do have any variations
Q(variation__in=Quota.variations.through.objects.filter(quota_id=self.object.pk).values_list(
'itemvariation_id', flat=True))
)
) | Q(quota=self.object)
) &
Q(redeemed__lt=F('max_usages'))
).exists()
if self.object.closed:

View File

@@ -18,6 +18,7 @@ from i18nfield.strings import LazyI18nString
from pretix.base.forms import SafeSessionWizardView
from pretix.base.i18n import language
from pretix.base.models import Event, EventMetaValue, Organizer, Quota, Team
from pretix.base.services.quotas import QuotaAvailability
from pretix.control.forms.event import (
EventWizardBasicsForm, EventWizardCopyForm, EventWizardFoundationForm,
)
@@ -82,19 +83,27 @@ class EventList(PaginationMixin, ListView):
self.filter_form[k] for k in self.filter_form.fields if k.startswith('meta_')
]
quotas = []
for s in ctx['events']:
s.first_quotas = s.first_quotas[:4]
for q in s.first_quotas:
q.cached_avail = (
(q.cached_availability_state, q.cached_availability_number)
if q.cached_availability_time is not None
else q.availability(allow_cache=True)
quotas += list(s.first_quotas)
qa = QuotaAvailability(early_out=False)
for q in quotas:
if q.cached_availability_time is None or q.cached_availability_paid_orders is None:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = (
qa.results[q] if q in qa.results
else (q.cached_availability_state, q.cached_availability_number)
)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
return ctx
@cached_property

View File

@@ -24,6 +24,7 @@ from pretix.base.models.items import (
ItemVariation, Quota, SubEventItem, SubEventItemVariation,
)
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
from pretix.base.services.quotas import QuotaAvailability
from pretix.control.forms.checkin import CheckinListForm
from pretix.control.forms.filter import SubEventFilterForm
from pretix.control.forms.item import QuotaForm
@@ -68,19 +69,28 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
quotas = []
for s in ctx['subevents']:
s.first_quotas = s.first_quotas[:4]
for q in s.first_quotas:
q.cached_avail = (
(q.cached_availability_state, q.cached_availability_number)
if q.cached_availability_time is not None
else q.availability(allow_cache=True)
quotas += list(s.first_quotas)
qa = QuotaAvailability(early_out=False)
for q in quotas:
if q.cached_availability_time is None or q.cached_availability_paid_orders is None:
qa.queue(q)
qa.compute()
for q in quotas:
q.cached_avail = (
qa.results[q] if q in qa.results
else (q.cached_availability_state, q.cached_availability_number)
)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
if q.size is not None:
q.percent_paid = min(
100,
round(q.cached_availability_paid_orders / q.size * 100) if q.size > 0 else 100
)
return ctx
@cached_property