From 8b0ed361d67f5e55461bee72b9ae17abbfa0fc08 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 21 Nov 2014 20:08:20 +0100 Subject: [PATCH] Added database cache autofill feature. This refs #47 and #45. --- README.rst | 29 ++++++++----- constance/__init__.py | 2 +- constance/apps.py | 1 + constance/backends/database/__init__.py | 56 +++++++++++++++++-------- constance/settings.py | 4 ++ example/cheeseshop/settings.py | 9 ++++ 6 files changed, 72 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index ea0e4f1..937c31f 100644 --- a/README.rst +++ b/README.rst @@ -125,17 +125,9 @@ you need to install this library, too. E.g.:: Alternatively follow the backend specific installation instructions above. The database backend has the ability to automatically cache the config -values and clear them when saving. You need to set the following setting -to enable this feature:: - - CONSTANCE_DATABASE_CACHE_BACKEND = 'memcached://127.0.0.1:11211/' - -.. note:: This won't work with a cache backend that doesn't support - cross-process caching, because correct cache invalidation - can't be guaranteed. - -Starting in Django 1.3 you can alternatively use the name of an entry of -the ``CACHES`` setting. E.g.:: +values and clear them when saving. Assuming you have a ``CACHES`` setting set +you need to set the the ``CONSTANCE_DATABASE_CACHE_BACKEND`` setting to enable +this feature:: CACHES = { 'default': { @@ -145,6 +137,14 @@ the ``CACHES`` setting. E.g.:: } CONSTANCE_DATABASE_CACHE_BACKEND = 'default' +.. note:: This won't work with a cache backend that doesn't support + cross-process caching, because correct cache invalidation + can't be guaranteed. + +.. note:: By default Constance will autofill the cache on startup and after + saving any of the config values. If you want to disable the cache simply + set the ``CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT`` setting to ``None``. + Just like the Redis backend you can set an optional prefix that is used during database interactions. To keep backward compatibility it defaults to ``''`` (an empty string). To use something else do this:: @@ -229,6 +229,13 @@ Screenshots Changelog --------- +v1.0 (unreleased) +~~~~~~~~~~~~~~~~~ + +* Added new autofill feature for the database backend cache which is enabled + by default. + + v0.6 (2013/04/12) ~~~~~~~~~~~~~~~~~ diff --git a/constance/__init__.py b/constance/__init__.py index 52b7b0c..3b2e246 100644 --- a/constance/__init__.py +++ b/constance/__init__.py @@ -1,7 +1,7 @@ from constance.config import Config try: - from django.apps import AppConfig + from django.apps import AppConfig # noqa except ImportError: config = Config() else: diff --git a/constance/apps.py b/constance/apps.py index 264e545..f6fd7fb 100644 --- a/constance/apps.py +++ b/constance/apps.py @@ -2,6 +2,7 @@ from django.apps import AppConfig from constance.config import Config from django.utils.translation import ugettext_lazy as _ + class ConstanceConfig(AppConfig): name = 'constance' verbose_name = _('Constance') diff --git a/constance/backends/database/__init__.py b/constance/backends/database/__init__.py index ff544a2..f3429ef 100644 --- a/constance/backends/database/__init__.py +++ b/constance/backends/database/__init__.py @@ -10,32 +10,49 @@ except ImportError: from constance.backends import Backend from constance import settings -db_cache = None -if settings.DATABASE_CACHE_BACKEND: - db_cache = get_cache(settings.DATABASE_CACHE_BACKEND) - if isinstance(db_cache, LocMemCache): - raise ImproperlyConfigured( - "The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a " - "subclass of Django's local-memory backend (%r). Please set " - "it to a backend that supports cross-process caching." - % settings.DATABASE_CACHE_BACKEND) - class DatabaseBackend(Backend): def __init__(self): from constance.backends.database.models import Constance self._model = Constance self._prefix = settings.DATABASE_PREFIX + self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT + self._autofill_cachekey = 'autofilled' + if not self._model._meta.installed: raise ImproperlyConfigured( "The constance.backends.database app isn't installed " "correctly. Make sure it's in your INSTALLED_APPS setting.") + + if settings.DATABASE_CACHE_BACKEND: + self._cache = get_cache(settings.DATABASE_CACHE_BACKEND) + if isinstance(self._cache, LocMemCache): + raise ImproperlyConfigured( + "The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a " + "subclass of Django's local-memory backend (%r). Please set " + "it to a backend that supports cross-process caching." + % settings.DATABASE_CACHE_BACKEND) + else: + self._cache = None + self.autofill() # Clear simple cache. post_save.connect(self.clear, sender=self._model) def add_prefix(self, key): return "%s%s" % (self._prefix, key) + def autofill(self): + if not self._autofill_timeout or not self._cache: + return + full_cachekey = self.add_prefix(self._autofill_cachekey) + if self._cache.get(full_cachekey): + return + autofill_values = {} + autofill_values[full_cachekey] = 1 + for key, value in self.mget(settings.CONFIG.keys()): + autofill_values[self.add_prefix(key)] = value + self._cache.set_many(autofill_values, timeout=self._autofill_timeout) + def mget(self, keys): if not keys: return @@ -46,17 +63,18 @@ class DatabaseBackend(Backend): def get(self, key): key = self.add_prefix(key) - value = None - if db_cache: - value = db_cache.get(key) + if self._cache: + value = self._cache.get(key) + else: + value = None if value is None: try: value = self._model._default_manager.get(key=key).value except self._model.DoesNotExist: pass else: - if db_cache: - db_cache.add(key, value) + if self._cache: + self._cache.add(key, value) return value def set(self, key, value): @@ -68,5 +86,9 @@ class DatabaseBackend(Backend): constance.save() def clear(self, sender, instance, created, **kwargs): - if db_cache and not created: - db_cache.delete_many(self.add_prefix(k) for k in settings.CONFIG.keys()) + if self._cache and not created: + keys = [self.add_prefix(k) + for k in settings.CONFIG.keys()] + keys.append(self.add_prefix(self._autofill_cachekey)) + self._cache.delete_many(keys) + self.autofill() diff --git a/constance/settings.py b/constance/settings.py index a53ac4a..4cb5d2f 100644 --- a/constance/settings.py +++ b/constance/settings.py @@ -22,6 +22,10 @@ REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION', DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND', None) +DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings, + 'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT', + 60 * 60 * 24) + DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '') SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True) diff --git a/example/cheeseshop/settings.py b/example/cheeseshop/settings.py index 7d4a7c3..e69e60e 100644 --- a/example/cheeseshop/settings.py +++ b/example/cheeseshop/settings.py @@ -116,3 +116,12 @@ CONSTANCE_CONFIG = { } CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' + + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211', + } +} +CONSTANCE_DATABASE_CACHE_BACKEND = 'default'