From d7d6e74c040c6b8d2db0f6455c4b5e12c7cfcdbd Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 18 Oct 2015 12:17:01 +0200 Subject: [PATCH] Generalized EventRelatedCache to ObjectRelatedCache --- src/pretix/base/cache.py | 49 +++++++++++++++++++++------------------ src/pretix/base/models.py | 24 +++++++++++++++---- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/src/pretix/base/cache.py b/src/pretix/base/cache.py index 52cdbb36a6..c913a13bbe 100644 --- a/src/pretix/base/cache.py +++ b/src/pretix/base/cache.py @@ -2,29 +2,14 @@ import hashlib import time from django.core.cache import caches - -from pretix.base.models import Event +from django.db.models import Model -class EventRelatedCache: - """ - This object behaves exactly like the cache implementations by Django - but with one important difference: It stores all keys related to a - certain event, so you pass an event when creating this object and if - you store data in this cache, it is only stored for this event. The - main purpose of this is to be able to flush all cached data related - to this event at once. +class NamespacedCache: - The EventRelatedCache instance itself is stateless, all state is - stored in the cache backend, so you can instantiate this class as many - times as you want. - """ - - def __init__(self, event: Event, cache: str='default'): - assert isinstance(event, Event) + def __init__(self, prefixkey, cache: str='default'): self.cache = caches[cache] - self.event = event - self.prefixkey = 'event:%s' % self.event.pk + self.prefixkey = prefixkey def _prefix_key(self, original_key: str) -> str: # Race conditions can happen here, but should be very very rare. @@ -36,15 +21,14 @@ class EventRelatedCache: if prefix is None: prefix = int(time.time()) self.cache.set(self.prefixkey, prefix) - key = 'event:%s:%d:%s' % (self.event.pk, prefix, original_key) + 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 key = hashlib.sha256(key.encode("UTF-8")).hexdigest() return key - @staticmethod - def _strip_prefix(key: str) -> str: - return key.split(":", 3)[-1] if 'event:' in key else key + def _strip_prefix(self, key: str) -> str: + return key.split(":", 2 + self.prefixkey.count(":"))[-1] def clear(self): try: @@ -86,3 +70,22 @@ class EventRelatedCache: def close(self): # NOQA pass + + +class ObjectRelatedCache(NamespacedCache): + """ + This object behaves exactly like the cache implementations by Django + but with one important difference: It stores all keys related to a + certain object, so you pass an object when creating this object and if + you store data in this cache, it is only stored for this object. The + main purpose of this is to be able to flush all cached data related + to this object at once. + + The ObjectRelatedCache instance itself is stateless, all state is + stored in the cache backend, so you can instantiate this class as many + times as you want. + """ + + def __init__(self, obj, cache: str='default'): + assert isinstance(obj, Model) + super().__init__('%s:%s' % (obj._meta.object_name, obj.pk), cache) diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index 8b01a307d9..fcc63e71d2 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -260,6 +260,11 @@ class Organizer(Versionable): def __str__(self): return self.name + def save(self, *args, **kwargs): + obj = super().save(*args, **kwargs) + self.get_cache().clear() + return obj + @cached_property def settings(self) -> SettingsProxy: """ @@ -267,6 +272,17 @@ class Organizer(Versionable): """ return SettingsProxy(self, type=OrganizerSetting) + def get_cache(self) -> "pretix.base.cache.ObjectRelatedCache": + """ + 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 + + return ObjectRelatedCache(self) + class OrganizerPermission(Versionable): """ @@ -414,16 +430,16 @@ class Event(Versionable): "DATETIME_FORMAT" if self.settings.show_times else "DATE_FORMAT" ) - def get_cache(self) -> "pretix.base.cache.EventRelatedCache": + def get_cache(self) -> "pretix.base.cache.ObjectRelatedCache": """ - Returns an :py:class:`EventRelatedCache` object. This behaves equivalent to + 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 EventRelatedCache + from pretix.base.cache import ObjectRelatedCache - return EventRelatedCache(self) + return ObjectRelatedCache(self) @cached_property def settings(self) -> SettingsProxy: