mirror of
https://github.com/jazzband/django-constance.git
synced 2026-03-16 22:40:24 +00:00
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:
parent
64bef7d88b
commit
eea0d2fdf9
7 changed files with 170 additions and 40 deletions
|
|
@ -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
84
constance/backends.py
Normal 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))
|
||||
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
17
constance/settings.py
Normal 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
5
constance/utils.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue