mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Implement event-based caching
This commit is contained in:
@@ -0,0 +1,73 @@
|
|||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from django.core.cache import caches
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, event, cache='default'):
|
||||||
|
self.cache = caches[cache]
|
||||||
|
self.prefix = self._build_prefix()
|
||||||
|
self.prefixkey = 'event:%d' % self.event.pk
|
||||||
|
|
||||||
|
def _prefix_key(self, original_key):
|
||||||
|
# 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)
|
||||||
|
if prefix is None:
|
||||||
|
prefix = int(time.time())
|
||||||
|
self.cache.set(self.prefixkey, prefix)
|
||||||
|
key = 'event:%d:%d:%s' % (self.event.pk, 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
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
try:
|
||||||
|
prefix = self.cache.incr(self.prefixkey, 1)
|
||||||
|
except ValueError:
|
||||||
|
prefix = int(time.time())
|
||||||
|
self.cache.set(self.prefixkey, prefix)
|
||||||
|
|
||||||
|
def set(self, key, value, timeout=300):
|
||||||
|
return self.cache.set(self._prefix_key(key), value, timeout)
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
return self.cache.get(self._prefix_key(key))
|
||||||
|
|
||||||
|
def get_many(self, keys):
|
||||||
|
return self.cache.get_many([self._prefix_key(key) for key in keys])
|
||||||
|
|
||||||
|
def set_many(self, values, timeout=300):
|
||||||
|
newvalues = {}
|
||||||
|
for i in values.items():
|
||||||
|
newvalues[self._prefix_key(i[0])] = i[1]
|
||||||
|
return self.cache.set_many([newvalues], timeout)
|
||||||
|
|
||||||
|
def delete(self, key):
|
||||||
|
return self.cache.delete(self._prefix_key(key))
|
||||||
|
|
||||||
|
def delete_many(self, keys):
|
||||||
|
return self.cache.delete_many([self._prefix_key(key) for key in keys])
|
||||||
|
|
||||||
|
def incr(self, key, by=1):
|
||||||
|
return self.cache.incr(self._prefix_key(key), by)
|
||||||
|
|
||||||
|
def decr(self, key, by=1):
|
||||||
|
return self.cache.decr(self._prefix_key(key), by)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -284,6 +284,10 @@ class Event(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
def get_date_from_display(self):
|
def get_date_from_display(self):
|
||||||
return _date(
|
return _date(
|
||||||
self.date_from,
|
self.date_from,
|
||||||
@@ -298,6 +302,10 @@ class Event(models.Model):
|
|||||||
"DATETIME_FORMAT" if self.show_times else "DATE_FORMAT"
|
"DATETIME_FORMAT" if self.show_times else "DATE_FORMAT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_cache(self):
|
||||||
|
from tixlbase.cache import EventRelatedCache
|
||||||
|
return EventRelatedCache(self)
|
||||||
|
|
||||||
|
|
||||||
class EventPermission(models.Model):
|
class EventPermission(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -353,6 +361,11 @@ class ItemCategory(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.event:
|
||||||
|
self.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Property(models.Model):
|
class Property(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -377,6 +390,11 @@ class Property(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.event:
|
||||||
|
self.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class PropertyValue(models.Model):
|
class PropertyValue(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -405,6 +423,11 @@ class PropertyValue(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: %s" % (self.prop.name, self.value)
|
return "%s: %s" % (self.prop.name, self.value)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.prop:
|
||||||
|
self.prop.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Question(models.Model):
|
class Question(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -446,6 +469,11 @@ class Question(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.question
|
return self.question
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.event:
|
||||||
|
self.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Item(models.Model):
|
class Item(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -529,6 +557,11 @@ class Item(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.event:
|
||||||
|
self.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
self.deleted = True
|
self.deleted = True
|
||||||
self.active = False
|
self.active = False
|
||||||
@@ -569,9 +602,6 @@ class Item(models.Model):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_cache(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class ItemVariation(models.Model):
|
class ItemVariation(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -614,6 +644,11 @@ class ItemVariation(models.Model):
|
|||||||
verbose_name = _("Item variation")
|
verbose_name = _("Item variation")
|
||||||
verbose_name_plural = _("Item variations")
|
verbose_name_plural = _("Item variations")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.item:
|
||||||
|
self.item.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BaseRestriction(models.Model):
|
class BaseRestriction(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -621,6 +656,12 @@ class BaseRestriction(models.Model):
|
|||||||
of Items or ItemVariations. This model is just an abstract base class to be
|
of Items or ItemVariations. This model is just an abstract base class to be
|
||||||
extended by restriction plugins.
|
extended by restriction plugins.
|
||||||
"""
|
"""
|
||||||
|
event = models.ForeignKey(
|
||||||
|
Event,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="restrictions_%(app_label)s_%(class)s",
|
||||||
|
verbose_name=_("Event"),
|
||||||
|
)
|
||||||
items = models.ManyToManyField(
|
items = models.ManyToManyField(
|
||||||
Item,
|
Item,
|
||||||
related_name="restrictions_%(app_label)s_%(class)s",
|
related_name="restrictions_%(app_label)s_%(class)s",
|
||||||
@@ -634,3 +675,8 @@ class BaseRestriction(models.Model):
|
|||||||
abstract = True
|
abstract = True
|
||||||
verbose_name = _("Restriction")
|
verbose_name = _("Restriction")
|
||||||
verbose_name_plural = _("Restrictions")
|
verbose_name_plural = _("Restrictions")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.event:
|
||||||
|
self.event.get_cache().clear()
|
||||||
|
return super().save(self, *args, **kwargs)
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
def test_nothing(self):
|
def test_nothing(self):
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertTrue('available' not in result[0] or result[0]['available'] is True)
|
self.assertTrue('available' not in result[0] or result[0]['available'] is True)
|
||||||
@@ -48,8 +48,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r.items.add(self.item)
|
r.items.add(self.item)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertIn('available', result[0])
|
self.assertIn('available', result[0])
|
||||||
@@ -65,8 +65,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r.items.add(self.item)
|
r.items.add(self.item)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertIn('available', result[0])
|
self.assertIn('available', result[0])
|
||||||
@@ -87,8 +87,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r2.items.add(self.item)
|
r2.items.add(self.item)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertIn('available', result[0])
|
self.assertIn('available', result[0])
|
||||||
@@ -110,8 +110,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r2.items.add(self.item)
|
r2.items.add(self.item)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertIn('available', result[0])
|
self.assertIn('available', result[0])
|
||||||
@@ -133,8 +133,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r2.items.add(self.item)
|
r2.items.add(self.item)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertIn('available', result[0])
|
self.assertIn('available', result[0])
|
||||||
@@ -156,8 +156,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r2.items.add(self.item)
|
r2.items.add(self.item)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 1)
|
self.assertEqual(len(result), 1)
|
||||||
self.assertIn('available', result[0])
|
self.assertIn('available', result[0])
|
||||||
@@ -179,8 +179,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r1.variations.add(v1)
|
r1.variations.add(v1)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 3)
|
self.assertEqual(len(result), 3)
|
||||||
for v in result:
|
for v in result:
|
||||||
@@ -219,8 +219,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r3.variations.add(v2)
|
r3.variations.add(v2)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 3)
|
self.assertEqual(len(result), 3)
|
||||||
for v in result:
|
for v in result:
|
||||||
@@ -261,8 +261,8 @@ class TimeRestrictionTest(TestCase):
|
|||||||
r3.variations.add(v2)
|
r3.variations.add(v2)
|
||||||
result = signals.availability_handler(
|
result = signals.availability_handler(
|
||||||
None, item=self.item,
|
None, item=self.item,
|
||||||
variations=self.item.get_all_variations(),
|
variations=self.event.get_all_variations(),
|
||||||
context=None, cache=self.item.get_cache()
|
context=None, cache=self.event.get_cache()
|
||||||
)
|
)
|
||||||
self.assertEqual(len(result), 3)
|
self.assertEqual(len(result), 3)
|
||||||
for v in result:
|
for v in result:
|
||||||
|
|||||||
Reference in New Issue
Block a user