Abstracted constance backends to and added a cached database backend. Moved around the settings code and simplified the module loading.

This commit is contained in:
Jannis Leidel 2010-12-01 00:20:23 +01:00
parent 64bef7d88b
commit eea0d2fdf9
7 changed files with 170 additions and 40 deletions

View file

@ -6,18 +6,15 @@ from django import forms
from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.options import csrf_protect_m
from django.conf import settings
from django.conf.urls.defaults import patterns, url
from django.core.urlresolvers import reverse
from django.forms import fields
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from django.utils.functional import update_wrapper
from django.utils.formats import localize
from django.utils.translation import ugettext_lazy as _
from constance import config
from constance import config, settings
NUMERIC_WIDGET = forms.TextInput(attrs={'size': 10})
@ -42,7 +39,7 @@ FIELDS = {
class ConstanceForm(forms.Form):
def __init__(self, *args, **kwargs):
super(ConstanceForm, self).__init__(*args, **kwargs)
for name, (default, help_text) in settings.CONSTANCE_CONFIG.items():
for name, (default, help_text) in settings.CONFIG.items():
field_class, kwargs = FIELDS[type(default)]
self.fields[name] = field_class(label=name, **kwargs)
@ -65,7 +62,7 @@ class ConstanceAdmin(admin.ModelAdmin):
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
form = ConstanceForm(initial=dict((name, getattr(config, name))
for name in settings.CONSTANCE_CONFIG))
for name in settings.CONFIG))
if request.method == 'POST':
form = ConstanceForm(request.POST)
if form.is_valid():
@ -81,7 +78,7 @@ class ConstanceAdmin(admin.ModelAdmin):
'form': form,
'media': self.media + form.media,
}
for name, (default, help_text) in settings.CONSTANCE_CONFIG.iteritems():
for name, (default, help_text) in settings.CONFIG.iteritems():
value = getattr(config, name)
context['config'].append({
'name': name,

84
constance/backends.py Normal file
View file

@ -0,0 +1,84 @@
from django.core.exceptions import ImproperlyConfigured
from django.db.models.signals import post_save
from django.utils.functional import memoize
from constance import settings
from constance.utils import import_module_attr
try:
from cPickle import loads, dumps
except ImportError:
from pickle import loads, dumps
class Backend(object):
def __init__(self, prefix):
self._prefix = prefix
def get(self, key):
"""
Get the key from the backend store and return it.
Return None if not found.
"""
raise NotImplementedError
def set(self, key, value):
"""
Add the value to the backend store given the key.
"""
raise NotImplementedError
db_cache = {}
class DatabaseBackend(Backend):
def __init__(self, prefix):
super(DatabaseBackend, self).__init__(prefix)
from constance.models import Constance
if not Constance._meta.installed:
raise ImproperlyConfigured(
"The constance app isn't installed correctly. "
"Make sure it's listed in the INSTALLED_APPS setting.")
self._model = Constance
# Clear simple cache.
post_save.connect(self.clear, sender=self._model)
def _get(self, key):
try:
value = self._model._default_manager.get(key=key).value
except self._model.DoesNotExist:
return None
return value
get = memoize(_get, db_cache, 2)
def set(self, key, value):
constance, created = self._model._default_manager.get_or_create(key=key, defaults={'value': value})
if not created:
constance.value = value
constance.save()
def clear(self, sender, instance, created, **kwargs):
if not created:
db_cache.clear()
class RedisBackend(Backend):
def __init__(self, prefix):
super(RedisBackend, self).__init__(prefix)
connection_cls = settings.CONNECTION_CLASS
if connection_cls is not None:
self._rd = import_module_attr(connection_cls)()
else:
import redis
self._rd = redis.Redis(**settings.REDIS_CONNECTION)
def get(self, key):
value = self._rd.get("%s%s" % (self._prefix, key))
if value:
return loads(value)
return None
def set(self, key, value):
self._rd.set("%s%s" % (self._prefix, key), dumps(value))

View file

@ -1,44 +1,30 @@
import os
import redis
try:
from cPickle import loads, dumps
except ImportError:
from pickle import loads, dumps
def import_module(path):
package, module = path.rsplit('.', 1)
return getattr(__import__(package, None, None, [module]), module)
settings = import_module(os.getenv('CONSTANCE_SETTINGS_MODULE', 'django.conf.settings'))
from constance import settings
from constance.utils import import_module_attr
class Config(object):
"""
The global config wrapper that handles the backend.
"""
def __init__(self):
super(Config, self).__setattr__('_prefix', getattr(settings, 'CONSTANCE_PREFIX', 'constance:'))
try:
super(Config, self).__setattr__('_rd', import_module(settings.CONSTANCE_CONNECTION_CLASS)())
except AttributeError:
super(Config, self).__setattr__('_rd', redis.Redis(**settings.CONSTANCE_CONNECTION))
super(Config, self).__setattr__(
'_backend', import_module_attr(settings.BACKEND)(settings.PREFIX))
def __getattr__(self, key):
try:
default, help_text = settings.CONSTANCE_CONFIG[key]
default, help_text = settings.CONFIG[key]
except KeyError, e:
raise AttributeError(key)
result = self._rd.get("%s%s" % (self._prefix, key))
result = self._backend.get(key)
if result is None:
result = default
setattr(self, key, default)
return result
return loads(result)
return result
def __setattr__(self, key, value):
if key not in settings.CONSTANCE_CONFIG:
if key not in settings.CONFIG:
raise AttributeError(key)
self._rd.set("%s%s" % (self._prefix, key), dumps(value))
self._backend.set(key, value)
def __dir__(self):
return settings.CONSTANCE_CONFIG.keys()
return settings.CONFIG.iterkeys()

View file

@ -0,0 +1,25 @@
from constance import settings
if settings.BACKEND == 'constance.backends.DatabaseBackend':
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import ugettext_lazy as _
try:
from picklefield import PickledObjectField
except ImportError:
raise ImproperlyConfigured("Couldn't find the the 3rd party app "
"django-picklefield which is required for "
"the constance database backend.")
class Constance(models.Model):
key = models.TextField()
value = PickledObjectField()
class Meta:
verbose_name = _('constance')
verbose_name_plural = _('constances')
db_table = 'constance_config'

17
constance/settings.py Normal file
View file

@ -0,0 +1,17 @@
import os
from constance.utils import import_module_attr
settings = import_module_attr(
os.getenv('CONSTANCE_SETTINGS_MODULE', 'django.conf.settings')
)
PREFIX = getattr(settings, 'CONSTANCE_PREFIX', 'constance:')
BACKEND = getattr(settings, 'CONSTANCE_BACKEND', 'constance.backends.DatabaseBackend')
CONFIG = getattr(settings, 'CONSTANCE_CONFIG', {})
CONNECTION_CLASS = getattr(settings, 'CONSTANCE_CONNECTION_CLASS', None)
REDIS_CONNECTION = getattr(settings, 'CONSTANCE_REDIS_CONNECTION',
getattr(settings, 'CONSTANCE_CONNECTION', {}))

5
constance/utils.py Normal file
View file

@ -0,0 +1,5 @@
from django.utils.importlib import import_module
def import_module_attr(path):
package, module = path.rsplit('.', 1)
return getattr(import_module(package), module)

View file

@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*-
import sys
from datetime import datetime, date, time
from decimal import Decimal
@ -8,21 +9,19 @@ from django.conf import settings
from django.contrib import admin
from django.contrib.auth.models import User
from constance import config
from constance import settings
from constance.admin import Config
# Use django RequestFactory later on
from testproject.test_app.tests.helpers import FakeRequest
class TestStorage(TestCase):
def tearDown(self):
config._rd.clear()
class TestStorage(object):
def test_store(self):
# read defaults
del sys.modules['constance']
from constance import config
self.assertEquals(config.INT_VALUE, 1)
self.assertEquals(config.LONG_VALUE, 123456L)
self.assertEquals(config.BOOL_VALUE, True)
@ -59,6 +58,7 @@ class TestStorage(TestCase):
self.assertEquals(config.TIME_VALUE, time(1, 59, 0))
def test_nonexistent(self):
from constance import config
try:
config.NON_EXISTENT
except Exception, e:
@ -71,6 +71,22 @@ class TestStorage(TestCase):
pass
self.assertEquals(type(e), AttributeError)
class TestRedis(TestCase, TestStorage):
def setUp(self):
self.old_backend = settings.BACKEND
settings.BACKEND = 'constance.backends.RedisBackend'
def tearDown(self):
del sys.modules['constance']
from constance import config
config._backend._rd.clear()
settings.BACKEND = self.old_backend
import constance
constance.config = Config()
class TestDatabase(TestCase, TestStorage):
pass
class TestAdmin(TestCase):
model = Config