From 03133dc1fda29a00d670a3c6468de1b6738c5ad2 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 17 Oct 2017 18:00:07 +0200 Subject: [PATCH] Cache access to cache object --- doc/development/implementation/models.rst | 2 +- src/pretix/base/cache.py | 9 +++-- src/pretix/base/models/event.py | 45 ++++++++++++++++++++++- src/pretix/base/models/items.py | 40 +++++++++++++++----- src/pretix/base/models/organizer.py | 14 +++++++ src/pretix/base/models/tax.py | 10 +++++ src/pretix/base/models/vouchers.py | 4 +- src/pretix/control/forms/organizer.py | 4 +- src/pretix/multidomain/urlreverse.py | 2 +- src/pretix/plugins/statistics/signals.py | 2 +- src/pretix/plugins/statistics/views.py | 2 +- src/pretix/presale/views/event.py | 4 +- 12 files changed, 115 insertions(+), 23 deletions(-) diff --git a/doc/development/implementation/models.rst b/doc/development/implementation/models.rst index 12764a19a2..80a761a2f9 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 5c684c57c7..ab118174bb 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 254e248f4a..16b3d3e002 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 f13fb01495..d2b36bef64 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 efef151ccc..b7e5beba87 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 275e5cf950..addb305779 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 2f4b0e537c..6886ca59a4 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 dfa4527a41..797c10cd98 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 85b74ffad1..fdd9a4791a 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 a452b78065..8d5881a77f 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 ca8e1b8407..133f572b9b 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 e0155fd78a..84a4ce1e69 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: