mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Add database cache for quotas
This commit is contained in:
@@ -11,7 +11,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import payment # NOQA
|
||||
from . import exporters # NOQA
|
||||
from . import invoice # NOQA
|
||||
from .services import export, mail, tickets, cart, orders, invoices, cleanup, update_check # NOQA
|
||||
from .services import export, mail, tickets, cart, orders, invoices, cleanup, update_check, quotas # NOQA
|
||||
|
||||
try:
|
||||
from .celery_app import app as celery_app # NOQA
|
||||
|
||||
40
src/pretix/base/migrations/0078_auto_20171003_1650.py
Normal file
40
src/pretix/base/migrations/0078_auto_20171003_1650.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.5 on 2017-10-03 16:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0077_auto_20170829_1126'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='cached_availability_number',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='cached_availability_state',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quota',
|
||||
name='cached_availability_time',
|
||||
field=models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventmetaproperty',
|
||||
name='default',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='taxrule',
|
||||
name='eu_reverse_charge',
|
||||
field=models.BooleanField(default=False, help_text='Not recommended. Most events will NOT be qualified for reverse charge since the place of taxation is the location of the event. This option disables charging VAT for all customers outside the EU and for business customers in different EU countries who entered a valid EU VAT ID. Only enable this option after consulting a tax counsel. No warranty given for correct tax calculation. USE AT YOUR OWN RISK.', verbose_name='Use EU reverse charge taxation rules'),
|
||||
),
|
||||
]
|
||||
@@ -704,6 +704,9 @@ class Quota(LoggedModel):
|
||||
blank=True,
|
||||
verbose_name=_("Variations")
|
||||
)
|
||||
cached_availability_state = models.PositiveIntegerField(null=True, blank=True)
|
||||
cached_availability_number = models.PositiveIntegerField(null=True, blank=True)
|
||||
cached_availability_time = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Quota")
|
||||
@@ -718,11 +721,18 @@ class Quota(LoggedModel):
|
||||
self.event.get_cache().clear()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
clear_cache = kwargs.pop('clear_cache', True)
|
||||
super().save(*args, **kwargs)
|
||||
if self.event:
|
||||
if self.event and clear_cache:
|
||||
self.event.get_cache().clear()
|
||||
|
||||
def availability(self, now_dt: datetime=None, count_waitinglist=True, _cache=None) -> Tuple[int, int]:
|
||||
def cache_is_hot(self, now_dt=None):
|
||||
now_dt = now_dt or now()
|
||||
return self.cached_availability_time and (now_dt - self.cached_availability_time).total_seconds() < 120
|
||||
|
||||
def availability(
|
||||
self, now_dt: datetime=None, count_waitinglist=True, _cache=None, allow_cache=False
|
||||
) -> Tuple[int, int]:
|
||||
"""
|
||||
This method is used to determine whether Items or ItemVariations belonging
|
||||
to this quota should currently be available for sale.
|
||||
@@ -730,12 +740,26 @@ class Quota(LoggedModel):
|
||||
:returns: a tuple where the first entry is one of the ``Quota.AVAILABILITY_`` constants
|
||||
and the second is the number of available tickets.
|
||||
"""
|
||||
if allow_cache and self.cache_is_hot() and count_waitinglist:
|
||||
return self.cached_availability_state, self.cached_availability_number
|
||||
|
||||
if _cache and count_waitinglist is not _cache.get('_count_waitinglist', True):
|
||||
_cache.clear()
|
||||
|
||||
if _cache is not None and self.pk in _cache:
|
||||
return _cache[self.pk]
|
||||
now_dt = now_dt or now()
|
||||
res = self._availability(now_dt, count_waitinglist)
|
||||
|
||||
if count_waitinglist and not self.cache_is_hot(now_dt):
|
||||
self.cached_availability_state = res[0]
|
||||
self.cached_availability_number = res[1]
|
||||
self.cached_availability_time = now_dt
|
||||
self.save(
|
||||
update_fields=['cached_availability_state', 'cached_availability_number', 'cached_availability_time'],
|
||||
clear_cache=False
|
||||
)
|
||||
|
||||
if _cache is not None:
|
||||
_cache[self.pk] = res
|
||||
_cache['_count_waitinglist'] = count_waitinglist
|
||||
|
||||
34
src/pretix/base/services/quotas.py
Normal file
34
src/pretix/base/services/quotas.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import F, Max, OuterRef, Q, Subquery
|
||||
from django.dispatch import receiver
|
||||
|
||||
from pretix.base.models import LogEntry, Quota
|
||||
from pretix.celery_app import app
|
||||
|
||||
from ..signals import periodic_task
|
||||
|
||||
|
||||
@receiver(signal=periodic_task)
|
||||
def build_all_quota_caches(sender, **kwargs):
|
||||
refresh_quota_cashes.apply_async()
|
||||
|
||||
|
||||
@app.task
|
||||
def refresh_quota_cashes():
|
||||
last_activity = LogEntry.objects.filter(
|
||||
event=OuterRef('event_id'),
|
||||
).order_by().values('event').annotate(
|
||||
m=Max('datetime')
|
||||
).values(
|
||||
'm'
|
||||
)
|
||||
quotas = Quota.objects.annotate(
|
||||
last_activity=Subquery(last_activity, output_field=models.DateTimeField())
|
||||
).filter(
|
||||
Q(cached_availability_time__isnull=True) |
|
||||
Q(cached_availability_time__lt=F('last_activity') - timedelta(hours=1))
|
||||
)
|
||||
for q in quotas:
|
||||
q.availability()
|
||||
@@ -152,7 +152,7 @@ def quota_widgets(sender, subevent=None, **kwargs):
|
||||
widgets = []
|
||||
|
||||
for q in sender.quotas.filter(subevent=subevent):
|
||||
status, left = q.availability()
|
||||
status, left = q.availability(allow_cache=True)
|
||||
widgets.append({
|
||||
'content': NUM_WIDGET.format(num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e',
|
||||
text=_('{quota} left').format(quota=escape(q.name))),
|
||||
|
||||
Reference in New Issue
Block a user