Added database cache autofill feature.

This refs #47 and #45.
This commit is contained in:
Jannis Leidel 2014-11-21 20:08:20 +01:00
parent 16bb8d50e6
commit 8b0ed361d6
6 changed files with 72 additions and 29 deletions

View file

@ -125,17 +125,9 @@ you need to install this library, too. E.g.::
Alternatively follow the backend specific installation instructions above. Alternatively follow the backend specific installation instructions above.
The database backend has the ability to automatically cache the config The database backend has the ability to automatically cache the config
values and clear them when saving. You need to set the following setting values and clear them when saving. Assuming you have a ``CACHES`` setting set
to enable this feature:: you need to set the the ``CONSTANCE_DATABASE_CACHE_BACKEND`` 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.::
CACHES = { CACHES = {
'default': { 'default': {
@ -145,6 +137,14 @@ the ``CACHES`` setting. E.g.::
} }
CONSTANCE_DATABASE_CACHE_BACKEND = 'default' 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 Just like the Redis backend you can set an optional prefix that is used during
database interactions. To keep backward compatibility it defaults to ``''`` database interactions. To keep backward compatibility it defaults to ``''``
(an empty string). To use something else do this:: (an empty string). To use something else do this::
@ -229,6 +229,13 @@ Screenshots
Changelog Changelog
--------- ---------
v1.0 (unreleased)
~~~~~~~~~~~~~~~~~
* Added new autofill feature for the database backend cache which is enabled
by default.
v0.6 (2013/04/12) v0.6 (2013/04/12)
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

View file

@ -1,7 +1,7 @@
from constance.config import Config from constance.config import Config
try: try:
from django.apps import AppConfig from django.apps import AppConfig # noqa
except ImportError: except ImportError:
config = Config() config = Config()
else: else:

View file

@ -2,6 +2,7 @@ from django.apps import AppConfig
from constance.config import Config from constance.config import Config
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
class ConstanceConfig(AppConfig): class ConstanceConfig(AppConfig):
name = 'constance' name = 'constance'
verbose_name = _('Constance') verbose_name = _('Constance')

View file

@ -10,32 +10,49 @@ except ImportError:
from constance.backends import Backend from constance.backends import Backend
from constance import settings 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): class DatabaseBackend(Backend):
def __init__(self): def __init__(self):
from constance.backends.database.models import Constance from constance.backends.database.models import Constance
self._model = Constance self._model = Constance
self._prefix = settings.DATABASE_PREFIX self._prefix = settings.DATABASE_PREFIX
self._autofill_timeout = settings.DATABASE_CACHE_AUTOFILL_TIMEOUT
self._autofill_cachekey = 'autofilled'
if not self._model._meta.installed: if not self._model._meta.installed:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"The constance.backends.database app isn't installed " "The constance.backends.database app isn't installed "
"correctly. Make sure it's in your INSTALLED_APPS setting.") "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. # Clear simple cache.
post_save.connect(self.clear, sender=self._model) post_save.connect(self.clear, sender=self._model)
def add_prefix(self, key): def add_prefix(self, key):
return "%s%s" % (self._prefix, 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): def mget(self, keys):
if not keys: if not keys:
return return
@ -46,17 +63,18 @@ class DatabaseBackend(Backend):
def get(self, key): def get(self, key):
key = self.add_prefix(key) key = self.add_prefix(key)
value = None if self._cache:
if db_cache: value = self._cache.get(key)
value = db_cache.get(key) else:
value = None
if value is None: if value is None:
try: try:
value = self._model._default_manager.get(key=key).value value = self._model._default_manager.get(key=key).value
except self._model.DoesNotExist: except self._model.DoesNotExist:
pass pass
else: else:
if db_cache: if self._cache:
db_cache.add(key, value) self._cache.add(key, value)
return value return value
def set(self, key, value): def set(self, key, value):
@ -68,5 +86,9 @@ class DatabaseBackend(Backend):
constance.save() constance.save()
def clear(self, sender, instance, created, **kwargs): def clear(self, sender, instance, created, **kwargs):
if db_cache and not created: if self._cache and not created:
db_cache.delete_many(self.add_prefix(k) for k in settings.CONFIG.keys()) 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()

View file

@ -22,6 +22,10 @@ REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION',
DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND', DATABASE_CACHE_BACKEND = getattr(settings, 'CONSTANCE_DATABASE_CACHE_BACKEND',
None) None)
DATABASE_CACHE_AUTOFILL_TIMEOUT = getattr(settings,
'CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT',
60 * 60 * 24)
DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '') DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '')
SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True) SUPERUSER_ONLY = getattr(settings, 'CONSTANCE_SUPERUSER_ONLY', True)

View file

@ -116,3 +116,12 @@ CONSTANCE_CONFIG = {
} }
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' 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'