Source code for djconfig.conf
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
from django.apps import apps
from django import forms
from django.db import models
from django.conf import settings
__all__ = [
"Config",
"config",
"register",
"register"]
class _ConfigFormBase(forms.Form):
def save(self):
raise NotImplementedError()
def _check_backend():
"""
Check :py:class:`djconfig.middleware.DjConfigMiddleware`\
is registered into ``settings.MIDDLEWARE_CLASSES``
"""
# Django 1.10 does not allow
# both settings to be set
middleware = set(
getattr(settings, 'MIDDLEWARE', None) or
getattr(settings, 'MIDDLEWARE_CLASSES', None) or
[])
# Deprecated alias
if "djconfig.middleware.DjConfigLocMemMiddleware" in middleware:
return
if "djconfig.middleware.DjConfigMiddleware" in middleware:
return
raise ValueError(
"djconfig.middleware.DjConfigMiddleware "
"is required but it was not found in "
"MIDDLEWARE_CLASSES nor in MIDDLEWARE")
def _deserialize(value, field):
assert isinstance(field, forms.Field)
if isinstance(field, forms.ModelMultipleChoiceField):
return json.loads(value, encoding='utf8')
return value
def _unlazify(value):
if isinstance(value, models.QuerySet):
return list(value)
return value
[docs]class Config(object):
"""
Contain registry of config forms and\
cache of key-value matching the forms field-value.
All methods are private to avoid clashing\
with the dynamic attributes.
This should be usually accessed through :py:data:`config`
"""
def __init__(self):
self._registry = set()
self._cache = {}
def __getattr__(self, key):
"""
Map cache data to config attributes
:return: The cache value for the accessed key/attribute
"""
try:
return self._cache[key]
except KeyError:
raise AttributeError('Attribute "%s" not found in config.' % key)
def _register(self, form_class, check_middleware=True):
"""
Register a config form into the registry
:param object form_class: The form class to register.\
Must be an instance of :py:class:`djconfig.forms.ConfigForm`
:param bool check_middleware: Check\
:py:class:`djconfig.middleware.DjConfigMiddleware`\
is registered into ``settings.MIDDLEWARE_CLASSES``. Default True
"""
if not issubclass(form_class, _ConfigFormBase):
raise ValueError(
"The form does not inherit from `forms.ConfigForm`")
self._registry.add(form_class)
if check_middleware:
_check_backend()
def _reload(self):
"""
Gets every registered form's field value.\
If a field name is found in the db, it will load it from there.\
Otherwise, the initial value from the field form is used
"""
ConfigModel = apps.get_model('djconfig.Config')
cache = {}
data = dict(
ConfigModel.objects
.all()
.values_list('key', 'value'))
# populate cache with initial form values,
# then with cleaned database values,
# then with raw database file/image paths
for form_class in self._registry:
empty_form = form_class()
cache.update({
name: field.initial
for name, field in empty_form.fields.items()})
form = form_class(data={
name: _deserialize(data[name], field)
for name, field in empty_form.fields.items()
if name in data and not isinstance(field, forms.FileField)})
form.is_valid()
cache.update({
name: _unlazify(value)
for name, value in form.cleaned_data.items()
if name in data})
# files are special because they don't have an initial value
# and the POSTED data must contain the file. So, we keep
# the stored path as is
# TODO: see if serialize/deserialize/unlazify can be used for this instead
cache.update({
name: data[name]
for name, field in empty_form.fields.items()
if name in data and isinstance(field, forms.FileField)})
cache['_updated_at'] = data.get('_updated_at')
self._cache = cache
def _reload_maybe(self):
"""
Reload the config if the config\
model has been updated. This is called\
once on every request by the middleware.\
Should not be called directly.
"""
ConfigModel = apps.get_model('djconfig.Config')
data = dict(
ConfigModel.objects
.filter(key='_updated_at')
.values_list('key', 'value'))
if (not hasattr(self, '_updated_at') or
self._updated_at != data.get('_updated_at')):
self._reload()
# Unit test helpers
def _reset(self):
self._registry = set()
self._cache = {}
def _set(self, key, value):
self._cache[key] = value
def _set_many(self, items):
self._cache.update({
key: value
for key, value in items.items()})
config = Config()
# Public methods
register = config._register
reload_maybe = config._reload_maybe