Generalized EventRelatedCache to ObjectRelatedCache

This commit is contained in:
Raphael Michel
2015-10-18 12:17:01 +02:00
parent 651d82c128
commit d7d6e74c04
2 changed files with 46 additions and 27 deletions

View File

@@ -2,29 +2,14 @@ import hashlib
import time import time
from django.core.cache import caches from django.core.cache import caches
from django.db.models import Model
from pretix.base.models import Event
class EventRelatedCache: class NamespacedCache:
"""
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.
The EventRelatedCache instance itself is stateless, all state is def __init__(self, prefixkey, cache: str='default'):
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)
self.cache = caches[cache] self.cache = caches[cache]
self.event = event self.prefixkey = prefixkey
self.prefixkey = 'event:%s' % self.event.pk
def _prefix_key(self, original_key: str) -> str: def _prefix_key(self, original_key: str) -> str:
# Race conditions can happen here, but should be very very rare. # Race conditions can happen here, but should be very very rare.
@@ -36,15 +21,14 @@ class EventRelatedCache:
if prefix is None: if prefix is None:
prefix = int(time.time()) prefix = int(time.time())
self.cache.set(self.prefixkey, prefix) 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 if len(key) > 200: # Hash long keys, as memcached has a length limit
# TODO: Use a more efficient, non-cryptographic hash algorithm # TODO: Use a more efficient, non-cryptographic hash algorithm
key = hashlib.sha256(key.encode("UTF-8")).hexdigest() key = hashlib.sha256(key.encode("UTF-8")).hexdigest()
return key return key
@staticmethod def _strip_prefix(self, key: str) -> str:
def _strip_prefix(key: str) -> str: return key.split(":", 2 + self.prefixkey.count(":"))[-1]
return key.split(":", 3)[-1] if 'event:' in key else key
def clear(self): def clear(self):
try: try:
@@ -86,3 +70,22 @@ class EventRelatedCache:
def close(self): # NOQA def close(self): # NOQA
pass 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)

View File

@@ -260,6 +260,11 @@ class Organizer(Versionable):
def __str__(self): def __str__(self):
return self.name return self.name
def save(self, *args, **kwargs):
obj = super().save(*args, **kwargs)
self.get_cache().clear()
return obj
@cached_property @cached_property
def settings(self) -> SettingsProxy: def settings(self) -> SettingsProxy:
""" """
@@ -267,6 +272,17 @@ class Organizer(Versionable):
""" """
return SettingsProxy(self, type=OrganizerSetting) 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): class OrganizerPermission(Versionable):
""" """
@@ -414,16 +430,16 @@ class Event(Versionable):
"DATETIME_FORMAT" if self.settings.show_times else "DATE_FORMAT" "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 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 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. 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 @cached_property
def settings(self) -> SettingsProxy: def settings(self) -> SettingsProxy: