diff --git a/doc/development/implementation/models.rst b/doc/development/implementation/models.rst index 12764a19a..80a761a2f 100644 --- a/doc/development/implementation/models.rst +++ b/doc/development/implementation/models.rst @@ -21,7 +21,7 @@ Organizers and events :members: .. autoclass:: pretix.base.models.Event - :members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, get_cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, active_subevents, invoice_renderer, settings + :members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running, cache, lock, get_plugins, get_mail_backend, payment_term_last, get_payment_providers, get_invoice_renderers, active_subevents, invoice_renderer, settings .. autoclass:: pretix.base.models.SubEvent :members: get_date_from_display, get_time_from_display, get_date_to_display, get_date_range_display, presale_has_ended, presale_is_running diff --git a/src/pretix/base/cache.py b/src/pretix/base/cache.py index 5c684c57c..ab118174b 100644 --- a/src/pretix/base/cache.py +++ b/src/pretix/base/cache.py @@ -11,17 +11,19 @@ class NamespacedCache: def __init__(self, prefixkey: str, cache: str='default'): self.cache = caches[cache] self.prefixkey = prefixkey + self._last_prefix = None - def _prefix_key(self, original_key: str) -> str: + def _prefix_key(self, original_key: str, known_prefix=None) -> str: # Race conditions can happen here, but should be very very rare. # We could only handle this by going _really_ lowlevel using # memcached's `add` keyword instead of `set`. # See also: # https://code.google.com/p/memcached/wiki/NewProgrammingTricks#Namespacing - prefix = self.cache.get(self.prefixkey) + prefix = known_prefix or self.cache.get(self.prefixkey) if prefix is None: prefix = int(time.time()) self.cache.set(self.prefixkey, prefix) + self._last_prefix = prefix key = '%s:%d:%s' % (self.prefixkey, prefix, original_key) if len(key) > 200: # Hash long keys, as memcached has a length limit # TODO: Use a more efficient, non-cryptographic hash algorithm @@ -32,6 +34,7 @@ class NamespacedCache: return key.split(":", 2 + self.prefixkey.count(":"))[-1] def clear(self) -> None: + self._last_prefix = None try: prefix = self.cache.incr(self.prefixkey, 1) except ValueError: @@ -42,7 +45,7 @@ class NamespacedCache: return self.cache.set(self._prefix_key(key), value, timeout) def get(self, key: str) -> str: - return self.cache.get(self._prefix_key(key)) + return self.cache.get(self._prefix_key(key, known_prefix=self._last_prefix)) def get_many(self, keys: List[str]) -> Dict[str, str]: values = self.cache.get_many([self._prefix_key(key) for key in keys]) diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 254e248f4..16b3d3e00 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -264,7 +264,7 @@ class Event(EventMixin, LoggedModel): def save(self, *args, **kwargs): obj = super().save(*args, **kwargs) - self.get_cache().clear() + self.cache.clear() return obj def get_plugins(self) -> "list[str]": @@ -281,6 +281,19 @@ class Event(EventMixin, LoggedModel): Django's built-in cache backends, but puts you into an isolated environment for this event, so you don't have to prefix your cache keys. In addition, the cache is being cleared every time the event or one of its related objects change. + + .. deprecated:: 1.9 + Use the property ``cache`` instead. + """ + return self.cache + + @cached_property + def cache(self): + """ + Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to + Django's built-in cache backends, but puts you into an isolated environment for + this event, so you don't have to prefix your cache keys. In addition, the cache + is being cleared every time the event or one of its related objects change. """ from pretix.base.cache import ObjectRelatedCache @@ -578,6 +591,16 @@ class SubEvent(EventMixin, LoggedModel): data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()}) return data + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.event: + self.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.event: + self.event.cache.clear() + def generate_invite_token(): return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits) @@ -677,6 +700,16 @@ class EventMetaValue(LoggedModel): class Meta: unique_together = ('event', 'property') + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.event: + self.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.event: + self.event.cache.clear() + class SubEventMetaValue(LoggedModel): """ @@ -697,3 +730,13 @@ class SubEventMetaValue(LoggedModel): class Meta: unique_together = ('subevent', 'property') + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.subevent: + self.subevent.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.subevent: + self.subevent.event.cache.clear() diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index f13fb0149..d2b36bef6 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -66,12 +66,12 @@ class ItemCategory(LoggedModel): def delete(self, *args, **kwargs): super().delete(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() @property def sortkey(self): @@ -104,6 +104,16 @@ class SubEventItem(models.Model): item = models.ForeignKey('Item', on_delete=models.CASCADE) price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.subevent: + self.subevent.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.subevent: + self.subevent.event.cache.clear() + class SubEventItemVariation(models.Model): """ @@ -121,6 +131,16 @@ class SubEventItemVariation(models.Model): variation = models.ForeignKey('ItemVariation', on_delete=models.CASCADE) price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.subevent: + self.subevent.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.subevent: + self.subevent.event.cache.clear() + class Item(LoggedModel): """ @@ -290,12 +310,12 @@ class Item(LoggedModel): def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() def delete(self, *args, **kwargs): super().delete(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() def tax(self, price=None, base_price_is='auto'): price = price if price is not None else self.default_price @@ -418,12 +438,12 @@ class ItemVariation(models.Model): def delete(self, *args, **kwargs): super().delete(*args, **kwargs) if self.item: - self.item.event.get_cache().clear() + self.item.event.cache.clear() def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.item: - self.item.event.get_cache().clear() + self.item.event.cache.clear() def check_quotas(self, ignored_quotas=None, count_waitinglist=True, subevent=None, _cache=None) -> Tuple[int, int]: """ @@ -595,12 +615,12 @@ class Question(LoggedModel): def delete(self, *args, **kwargs): super().delete(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() def save(self, *args, **kwargs): super().save(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() @property def sortkey(self): @@ -719,13 +739,13 @@ class Quota(LoggedModel): def delete(self, *args, **kwargs): super().delete(*args, **kwargs) if self.event: - self.event.get_cache().clear() + self.event.cache.clear() def save(self, *args, **kwargs): clear_cache = kwargs.pop('clear_cache', True) super().save(*args, **kwargs) if self.event and clear_cache: - self.event.get_cache().clear() + self.event.cache.clear() def rebuild_cache(self, now_dt=None): self.cached_availability_time = None diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index efef151cc..b7e5beba8 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -3,6 +3,7 @@ import string from django.core.validators import RegexValidator from django.db import models from django.utils.crypto import get_random_string +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from pretix.base.models.base import LoggedModel @@ -62,6 +63,19 @@ class Organizer(LoggedModel): Django's built-in cache backends, but puts you into an isolated environment for this organizer, so you don't have to prefix your cache keys. In addition, the cache is being cleared every time the organizer changes. + + .. deprecated:: 1.9 + Use the property ``cache`` instead. + """ + return self.cache + + @cached_property + def cache(self): + """ + Returns an :py:class:`ObjectRelatedCache` object. This behaves equivalent to + Django's built-in cache backends, but puts you into an isolated environment for + this organizer, so you don't have to prefix your cache keys. In addition, the cache + is being cleared every time the organizer changes. """ from pretix.base.cache import ObjectRelatedCache diff --git a/src/pretix/base/models/tax.py b/src/pretix/base/models/tax.py index 275e5cf95..addb30577 100644 --- a/src/pretix/base/models/tax.py +++ b/src/pretix/base/models/tax.py @@ -182,3 +182,13 @@ class TaxRule(LoggedModel): # Consumer in different EU country / invalid VAT return True + + def delete(self, *args, **kwargs): + super().delete(*args, **kwargs) + if self.event: + self.event.cache.clear() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + if self.event: + self.event.cache.clear() diff --git a/src/pretix/base/models/vouchers.py b/src/pretix/base/models/vouchers.py index 2f4b0e537..6886ca59a 100644 --- a/src/pretix/base/models/vouchers.py +++ b/src/pretix/base/models/vouchers.py @@ -315,11 +315,11 @@ class Voucher(LoggedModel): def save(self, *args, **kwargs): self.code = self.code.upper() super().save(*args, **kwargs) - self.event.get_cache().set('vouchers_exist', True) + self.event.cache.set('vouchers_exist', True) def delete(self, using=None, keep_parents=False): super().delete(using, keep_parents) - self.event.get_cache().delete('vouchers_exist') + self.event.cache.delete('vouchers_exist') def is_in_cart(self) -> bool: """ diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index dfa4527a4..797c10cd9 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -68,7 +68,9 @@ class OrganizerUpdateForm(OrganizerForm): KnownDomain.objects.create(organizer=instance, domainname=self.cleaned_data['domain']) elif current_domain: current_domain.delete() - instance.get_cache().clear() + instance.cache.clear() + for ev in instance.events.all(): + ev.cache.clear() return instance diff --git a/src/pretix/multidomain/urlreverse.py b/src/pretix/multidomain/urlreverse.py index 85b74ffad..fdd9a4791 100644 --- a/src/pretix/multidomain/urlreverse.py +++ b/src/pretix/multidomain/urlreverse.py @@ -59,7 +59,7 @@ def eventreverse(obj, name, kwargs=None): c = None if not kwargs: - c = obj.get_cache() + c = obj.cache url = c.get('urlrev_{}'.format(name)) if url: return url diff --git a/src/pretix/plugins/statistics/signals.py b/src/pretix/plugins/statistics/signals.py index a452b7806..8d5881a77 100644 --- a/src/pretix/plugins/statistics/signals.py +++ b/src/pretix/plugins/statistics/signals.py @@ -25,7 +25,7 @@ def control_nav_import(sender, request=None, **kwargs): def clear_cache(sender, *args, **kwargs): - cache = sender.get_cache() + cache = sender.cache cache.delete('statistics_obd_data') cache.delete('statistics_obp_data') cache.delete('statistics_rev_data') diff --git a/src/pretix/plugins/statistics/views.py b/src/pretix/plugins/statistics/views.py index ca8e1b840..133f572b9 100644 --- a/src/pretix/plugins/statistics/views.py +++ b/src/pretix/plugins/statistics/views.py @@ -32,7 +32,7 @@ class IndexView(EventPermissionRequiredMixin, ChartContainingView, TemplateView) except SubEvent.DoesNotExist: pass - cache = self.request.event.get_cache() + cache = self.request.event.cache ckey = str(subevent.pk) if subevent else 'all' # Orders by day diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index e0155fd78..84a4ce1e6 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -175,10 +175,10 @@ class EventIndex(EventViewMixin, CartMixin, TemplateView): context['subevent'] = self.subevent context['cart'] = self.get_cart() context['has_addon_choices'] = get_cart(self.request).filter(item__addons__isnull=False).exists() - vouchers_exist = self.request.event.get_cache().get('vouchers_exist') + vouchers_exist = self.request.event.cache.get('vouchers_exist') if vouchers_exist is None: vouchers_exist = self.request.event.vouchers.exists() - self.request.event.get_cache().set('vouchers_exist', vouchers_exist) + self.request.event.cache.set('vouchers_exist', vouchers_exist) context['vouchers_exist'] = vouchers_exist context['ev'] = self.subevent or self.request.event if self.subevent: