mirror of
https://github.com/jazzband/django-configurations.git
synced 2026-03-16 22:20:27 +00:00
Added a configuration values system.
This also adds some advanced features like a setup classmethod to the Configuration class. Reorganized and extended the documentation.
This commit is contained in:
parent
6db9fdbf30
commit
1a54847375
15 changed files with 1464 additions and 234 deletions
24
README.rst
24
README.rst
|
|
@ -58,6 +58,7 @@ of the appropriate starter functions, e.g. a typical **manage.py** using
|
|||
django-configurations would look like this:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10
|
||||
|
||||
#!/usr/bin/env python
|
||||
|
||||
|
|
@ -72,13 +73,14 @@ django-configurations would look like this:
|
|||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
Notice in line 9 we don't use the common tool
|
||||
Notice in line 10 we don't use the common tool
|
||||
``django.core.management.execute_from_command_line`` but instead
|
||||
``configurations.management.execute_from_command_line``.
|
||||
|
||||
The same applies to your **wsgi.py** file, e.g.:
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 6
|
||||
|
||||
import os
|
||||
|
||||
|
|
@ -95,16 +97,26 @@ function but instead ``configurations.wsgi.get_wsgi_application``.
|
|||
That's it! You can now use your project with ``manage.py`` and your favorite
|
||||
WSGI enabled server.
|
||||
|
||||
**Alternatively** you can use a special Django project template that is a copy
|
||||
of the one included in Django 1.5.x. The following example assumes you're using
|
||||
pip_ to install dependencies.::
|
||||
Project templates
|
||||
-----------------
|
||||
|
||||
You can use a special Django project template that is a copy of the one
|
||||
included in Django 1.5.x. The following examples assumes you're using pip_
|
||||
to install packages.
|
||||
|
||||
First install Django and django-configurations::
|
||||
|
||||
# first install Django and django-configurations
|
||||
pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.5.x/requirements.txt
|
||||
# then create your new Django project with the provided template
|
||||
|
||||
Then create your new Django project with the provided template::
|
||||
|
||||
django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.5.x.zip
|
||||
|
||||
Now you have a default Django 1.5.x project in the ``mysite`` directory that uses
|
||||
django-configurations.
|
||||
|
||||
See the repository of the template for more information:
|
||||
|
||||
https://github.com/jezdez/django-configurations/tree/templates/1.5.x
|
||||
|
||||
.. _pip: http://pip-installer.org/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from django.conf import global_settings
|
|||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from .utils import uppercase_attributes
|
||||
from .values import Value, setup_value
|
||||
|
||||
__all__ = ['Configuration']
|
||||
|
||||
|
|
@ -70,6 +71,12 @@ class Configuration(six.with_metaclass(ConfigurationBase)):
|
|||
def post_setup(cls):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def setup(cls):
|
||||
for name, value in uppercase_attributes(cls).items():
|
||||
if isinstance(value, Value):
|
||||
setup_value(cls, name, value)
|
||||
|
||||
|
||||
class Settings(Configuration):
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE
|
|||
from django.utils.decorators import available_attrs
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from .utils import uppercase_attributes
|
||||
|
||||
from .utils import uppercase_attributes, reraise
|
||||
from .values import Value, setup_value
|
||||
|
||||
installed = False
|
||||
|
||||
|
|
@ -133,22 +133,6 @@ class ConfigurationImporter(object):
|
|||
return None
|
||||
|
||||
|
||||
def reraise(exc, prefix=None, suffix=None):
|
||||
args = exc.args
|
||||
if not args:
|
||||
args = ('',)
|
||||
if prefix is None:
|
||||
prefix = ''
|
||||
elif not prefix.endswith((':', ': ')):
|
||||
prefix = prefix + ': '
|
||||
if suffix is None:
|
||||
suffix = ''
|
||||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||
suffix = '(' + suffix + ')'
|
||||
exc.args = ('%s %s %s' % (prefix, exc.args[0], suffix),) + args[1:]
|
||||
raise
|
||||
|
||||
|
||||
class ConfigurationLoader(object):
|
||||
|
||||
def __init__(self, name, location):
|
||||
|
|
@ -173,6 +157,11 @@ class ConfigurationLoader(object):
|
|||
except Exception as err:
|
||||
reraise(err, "While calling '{0}.pre_setup()'".format(cls_path))
|
||||
|
||||
try:
|
||||
cls.setup()
|
||||
except Exception as err:
|
||||
reraise(err, "While calling the '{0}.setup()'".format(cls_path))
|
||||
|
||||
try:
|
||||
obj = cls()
|
||||
except Exception as err:
|
||||
|
|
@ -192,6 +181,11 @@ class ConfigurationLoader(object):
|
|||
except Exception as err:
|
||||
reraise(err, "While calling '{0}.{1}'".format(cls_path,
|
||||
value))
|
||||
# in case a method returns a Value instance we have
|
||||
# to do the same as the Configuration.setup method
|
||||
if isinstance(value, Value):
|
||||
setup_value(mod, name, value)
|
||||
continue
|
||||
setattr(mod, name, value)
|
||||
|
||||
setattr(mod, 'CONFIGURATION', '{0}.{1}'.format(fullname, self.name))
|
||||
|
|
|
|||
292
configurations/tests/test_values.py
Normal file
292
configurations/tests/test_values.py
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
import decimal
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from mock import patch
|
||||
|
||||
from configurations.values import (Value, BooleanValue, IntegerValue,
|
||||
FloatValue, DecimalValue, ListValue,
|
||||
TupleValue, SetValue, DictValue,
|
||||
URLValue, EmailValue, IPValue,
|
||||
RegexValue, PathValue, SecretValue,
|
||||
DatabaseURLValue, EmailURLValue,
|
||||
CacheURLValue, BackendsValue,
|
||||
CastingMixin)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def env(**kwargs):
|
||||
with patch.dict(os.environ, clear=True, **kwargs):
|
||||
yield
|
||||
|
||||
|
||||
class FailingCasterValue(CastingMixin, Value):
|
||||
caster = 'non.existing.caster'
|
||||
|
||||
|
||||
class ValueTests(TestCase):
|
||||
|
||||
def test_value(self):
|
||||
value = Value('default')
|
||||
self.assertEqual(value.setup('TEST'), 'default')
|
||||
with env(DJANGO_TEST='override'):
|
||||
self.assertEqual(value.setup('TEST'), 'default')
|
||||
|
||||
@patch.dict(os.environ, clear=True, DJANGO_TEST='override')
|
||||
def test_env_var(self):
|
||||
value = Value('default', environ=True)
|
||||
self.assertEqual(value.setup('TEST'), 'override')
|
||||
self.assertNotEqual(value.setup('TEST'), value.default)
|
||||
self.assertEqual(value.to_python(os.environ['DJANGO_TEST']),
|
||||
value.setup('TEST'))
|
||||
|
||||
def test_value_reuse(self):
|
||||
value1 = Value('default', environ=True)
|
||||
value2 = Value(value1, environ=True)
|
||||
self.assertEqual(value1.setup('TEST1'), 'default')
|
||||
self.assertEqual(value2.setup('TEST2'), 'default')
|
||||
with env(DJANGO_TEST1='override1', DJANGO_TEST2='override2'):
|
||||
self.assertEqual(value1.setup('TEST1'), 'override1')
|
||||
self.assertEqual(value2.setup('TEST2'), 'override2')
|
||||
|
||||
def test_env_var_prefix(self):
|
||||
with patch.dict(os.environ, clear=True, ACME_TEST='override'):
|
||||
value = Value('default', environ=True, environ_prefix='ACME')
|
||||
self.assertEqual(value.setup('TEST'), 'override')
|
||||
|
||||
with patch.dict(os.environ, clear=True, TEST='override'):
|
||||
value = Value('default', environ=True, environ_prefix='')
|
||||
self.assertEqual(value.setup('TEST'), 'override')
|
||||
|
||||
def test_boolean_values_true(self):
|
||||
value = BooleanValue(False, environ=True)
|
||||
for truthy in value.true_values:
|
||||
with env(DJANGO_TEST=truthy):
|
||||
self.assertTrue(value.setup('TEST'))
|
||||
|
||||
def test_boolean_values_faulty(self):
|
||||
self.assertRaises(ValueError, BooleanValue, 'false')
|
||||
|
||||
def test_boolean_values_false(self):
|
||||
value = BooleanValue(True, environ=True)
|
||||
for falsy in value.false_values:
|
||||
with env(DJANGO_TEST=falsy):
|
||||
self.assertFalse(value.setup('TEST'))
|
||||
|
||||
def test_boolean_values_nonboolean(self):
|
||||
value = BooleanValue(True, environ=True)
|
||||
with env(DJANGO_TEST='nonboolean'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_integer_values(self):
|
||||
value = IntegerValue(1, environ=True)
|
||||
with env(DJANGO_TEST='2'):
|
||||
self.assertEqual(value.setup('TEST'), 2)
|
||||
with env(DJANGO_TEST='noninteger'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_float_values(self):
|
||||
value = FloatValue(1.0, environ=True)
|
||||
with env(DJANGO_TEST='2.0'):
|
||||
self.assertEqual(value.setup('TEST'), 2.0)
|
||||
with env(DJANGO_TEST='noninteger'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_decimal_values(self):
|
||||
value = DecimalValue(decimal.Decimal(1), environ=True)
|
||||
with env(DJANGO_TEST='2'):
|
||||
self.assertEqual(value.setup('TEST'), decimal.Decimal(2))
|
||||
with env(DJANGO_TEST='nondecimal'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_failing_caster(self):
|
||||
self.assertRaises(ImproperlyConfigured, FailingCasterValue)
|
||||
|
||||
def test_list_values_default(self):
|
||||
value = ListValue(environ=True)
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), ['2', '2'])
|
||||
with env(DJANGO_TEST='2, 2 ,'):
|
||||
self.assertEqual(value.setup('TEST'), ['2', '2'])
|
||||
with env(DJANGO_TEST=''):
|
||||
self.assertEqual(value.setup('TEST'), [])
|
||||
|
||||
def test_list_values_separator(self):
|
||||
value = ListValue(environ=True, separator=':')
|
||||
with env(DJANGO_TEST='/usr/bin:/usr/sbin:/usr/local/bin'):
|
||||
self.assertEqual(value.setup('TEST'),
|
||||
['/usr/bin', '/usr/sbin', '/usr/local/bin'])
|
||||
|
||||
def test_List_values_converter(self):
|
||||
value = ListValue(environ=True, converter=int)
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), [2, 2])
|
||||
|
||||
value = ListValue(environ=True, converter=float)
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), [2.0, 2.0])
|
||||
|
||||
def test_list_values_custom_converter(self):
|
||||
value = ListValue(environ=True, converter=lambda x: x * 2)
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), ['22', '22'])
|
||||
|
||||
def test_list_values_converter_exception(self):
|
||||
value = ListValue(environ=True, converter=int)
|
||||
with env(DJANGO_TEST='2,b'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_tuple_values_default(self):
|
||||
value = TupleValue(environ=True)
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), ('2', '2'))
|
||||
with env(DJANGO_TEST='2, 2 ,'):
|
||||
self.assertEqual(value.setup('TEST'), ('2', '2'))
|
||||
with env(DJANGO_TEST=''):
|
||||
self.assertEqual(value.setup('TEST'), ())
|
||||
|
||||
def test_set_values_default(self):
|
||||
value = SetValue(environ=True)
|
||||
with env(DJANGO_TEST='2,2'):
|
||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
||||
with env(DJANGO_TEST='2, 2 ,'):
|
||||
self.assertEqual(value.setup('TEST'), set(['2', '2']))
|
||||
with env(DJANGO_TEST=''):
|
||||
self.assertEqual(value.setup('TEST'), set())
|
||||
|
||||
def test_dict_values_default(self):
|
||||
value = DictValue(environ=True)
|
||||
with env(DJANGO_TEST='{2: 2}'):
|
||||
self.assertEqual(value.setup('TEST'), {2: 2})
|
||||
expected = {2: 2, '3': '3', '4': [1, 2, 3]}
|
||||
with env(DJANGO_TEST="{2: 2, '3': '3', '4': [1, 2, 3]}"):
|
||||
self.assertEqual(value.setup('TEST'), expected)
|
||||
with env(DJANGO_TEST="""{
|
||||
2: 2,
|
||||
'3': '3',
|
||||
'4': [1, 2, 3],
|
||||
}"""):
|
||||
self.assertEqual(value.setup('TEST'), expected)
|
||||
with env(DJANGO_TEST=''):
|
||||
self.assertEqual(value.setup('TEST'), {})
|
||||
with env(DJANGO_TEST='spam'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_email_values(self):
|
||||
value = EmailValue('spam@eg.gs', environ=True)
|
||||
with env(DJANGO_TEST='spam@sp.am'):
|
||||
self.assertEqual(value.setup('TEST'), 'spam@sp.am')
|
||||
with env(DJANGO_TEST='spam'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_url_values(self):
|
||||
value = URLValue('http://eggs.spam', environ=True)
|
||||
with env(DJANGO_TEST='http://spam.eggs'):
|
||||
self.assertEqual(value.setup('TEST'), 'http://spam.eggs')
|
||||
with env(DJANGO_TEST='httb://spam.eggs'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_ip_values(self):
|
||||
value = IPValue('0.0.0.0', environ=True)
|
||||
with env(DJANGO_TEST='127.0.0.1'):
|
||||
self.assertEqual(value.setup('TEST'), '127.0.0.1')
|
||||
with env(DJANGO_TEST='::1'):
|
||||
self.assertEqual(value.setup('TEST'), '::1')
|
||||
with env(DJANGO_TEST='spam.eggs'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_regex_values(self):
|
||||
value = RegexValue('000--000', environ=True, regex=r'\d+--\d+')
|
||||
with env(DJANGO_TEST='123--456'):
|
||||
self.assertEqual(value.setup('TEST'), '123--456')
|
||||
with env(DJANGO_TEST='123456'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_path_values_with_check(self):
|
||||
value = PathValue(environ=True)
|
||||
with env(DJANGO_TEST='/'):
|
||||
self.assertEqual(value.setup('TEST'), '/')
|
||||
with env(DJANGO_TEST='~/'):
|
||||
self.assertEqual(value.setup('TEST'), os.path.expanduser('~'))
|
||||
with env(DJANGO_TEST='/does/not/exist'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_path_values_no_check(self):
|
||||
value = PathValue(environ=True, check_exists=False)
|
||||
with env(DJANGO_TEST='/'):
|
||||
self.assertEqual(value.setup('TEST'), '/')
|
||||
with env(DJANGO_TEST='~/spam/eggs'):
|
||||
self.assertEqual(value.setup('TEST'),
|
||||
os.path.join(os.path.expanduser('~'),
|
||||
'spam', 'eggs'))
|
||||
with env(DJANGO_TEST='/does/not/exist'):
|
||||
self.assertEqual(value.setup('TEST'), '/does/not/exist')
|
||||
|
||||
def test_secret_value(self):
|
||||
self.assertRaises(ValueError, SecretValue, 'default')
|
||||
value = SecretValue()
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
with env(DJANGO_SECRET_KEY='123'):
|
||||
self.assertEqual(value.setup('SECRET_KEY'), '123')
|
||||
|
||||
def test_database_url_value(self):
|
||||
value = DatabaseURLValue(environ=True)
|
||||
self.assertEqual(value.default, {})
|
||||
with env(DATABASE_URL='sqlite://'):
|
||||
self.assertEqual(value.setup('DATABASE_URL'), {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'HOST': None,
|
||||
'NAME': ':memory:',
|
||||
'PASSWORD': None,
|
||||
'PORT': None,
|
||||
'USER': None,
|
||||
}})
|
||||
|
||||
def test_email_url_value(self):
|
||||
value = EmailURLValue(environ=True)
|
||||
self.assertEqual(value.default, {})
|
||||
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:587'):
|
||||
self.assertEqual(value.setup('EMAIL_URL'), {
|
||||
'EMAIL_BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
|
||||
'EMAIL_FILE_PATH': '',
|
||||
'EMAIL_HOST': 'smtp.example.com',
|
||||
'EMAIL_HOST_PASSWORD': 'password',
|
||||
'EMAIL_HOST_USER': 'user@domain.com',
|
||||
'EMAIL_PORT': 587,
|
||||
'EMAIL_USE_TLS': True})
|
||||
with env(EMAIL_URL='console://'):
|
||||
self.assertEqual(value.setup('EMAIL_URL'), {
|
||||
'EMAIL_BACKEND': 'django.core.mail.backends.console.EmailBackend',
|
||||
'EMAIL_FILE_PATH': '',
|
||||
'EMAIL_HOST': None,
|
||||
'EMAIL_HOST_PASSWORD': None,
|
||||
'EMAIL_HOST_USER': None,
|
||||
'EMAIL_PORT': None,
|
||||
'EMAIL_USE_TLS': False})
|
||||
with env(EMAIL_URL='smtps://user@domain.com:password@smtp.example.com:wrong'):
|
||||
self.assertRaises(ValueError, value.setup, 'TEST')
|
||||
|
||||
def test_cache_url_value(self):
|
||||
value = CacheURLValue(environ=True)
|
||||
self.assertEqual(value.default, {})
|
||||
with env(CACHE_URL='redis://user@host:port/1'):
|
||||
self.assertEqual(value.setup('CACHE_URL'), {
|
||||
'default': {
|
||||
'BACKEND': 'redis_cache.cache.RedisCache',
|
||||
'KEY_PREFIX': '',
|
||||
'LOCATION': 'user@host:port:1'
|
||||
}})
|
||||
with env(CACHE_URL='wrong://user@host:port/1'):
|
||||
self.assertRaises(KeyError, value.setup, 'TEST')
|
||||
|
||||
def test_backend_list_value(self):
|
||||
backends = ['django.middleware.common.CommonMiddleware']
|
||||
value = BackendsValue(backends)
|
||||
self.assertEqual(value.setup('TEST'), backends)
|
||||
|
||||
backends = ['non.existing.Backend']
|
||||
self.assertRaises(ImproperlyConfigured, BackendsValue, backends)
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
import sys
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils import six
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
|
||||
def isuppercase(name):
|
||||
return name == name.upper() and not name.startswith('_')
|
||||
|
||||
|
|
@ -5,3 +12,50 @@ def isuppercase(name):
|
|||
def uppercase_attributes(obj):
|
||||
return dict((name, getattr(obj, name))
|
||||
for name in filter(isuppercase, dir(obj)))
|
||||
|
||||
|
||||
def import_by_path(dotted_path, error_prefix=''):
|
||||
"""
|
||||
Import a dotted module path and return the attribute/class designated by the
|
||||
last name in the path. Raise ImproperlyConfigured if something goes wrong.
|
||||
|
||||
Backported from Django 1.6.
|
||||
"""
|
||||
try:
|
||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured("{0}{1} doesn't look like "
|
||||
"a module path".format(error_prefix,
|
||||
dotted_path))
|
||||
try:
|
||||
module = import_module(module_path)
|
||||
except ImportError as err:
|
||||
msg = '{0}Error importing module {1}: "{2}"'.format(error_prefix,
|
||||
module_path,
|
||||
err)
|
||||
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
|
||||
sys.exc_info()[2])
|
||||
try:
|
||||
attr = getattr(module, class_name)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured('{0}Module "{1}" does not define a '
|
||||
'"{2}" attribute/class'.format(error_prefix,
|
||||
module_path,
|
||||
class_name))
|
||||
return attr
|
||||
|
||||
|
||||
def reraise(exc, prefix=None, suffix=None):
|
||||
args = exc.args
|
||||
if not args:
|
||||
args = ('',)
|
||||
if prefix is None:
|
||||
prefix = ''
|
||||
elif not prefix.endswith((':', ': ')):
|
||||
prefix = prefix + ': '
|
||||
if suffix is None:
|
||||
suffix = ''
|
||||
elif not (suffix.startswith('(') and suffix.endswith(')')):
|
||||
suffix = '(' + suffix + ')'
|
||||
exc.args = ('{0} {1} {2}'.format(prefix, exc.args[0], suffix),) + args[1:]
|
||||
raise
|
||||
|
|
|
|||
346
configurations/values.py
Normal file
346
configurations/values.py
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
import ast
|
||||
import copy
|
||||
import decimal
|
||||
import os
|
||||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
||||
from django.utils import six
|
||||
|
||||
from .utils import import_by_path
|
||||
|
||||
|
||||
def setup_value(target, name, value):
|
||||
actual_value = value.setup(name)
|
||||
if value.multiple:
|
||||
# overwriting the original Value class with the result
|
||||
setattr(target, name, actual_value)
|
||||
for multiple_name, multiple_value in actual_value.items():
|
||||
setattr(target, multiple_name, multiple_value)
|
||||
else:
|
||||
setattr(target, name, actual_value)
|
||||
|
||||
|
||||
class Value(object):
|
||||
"""
|
||||
A single settings value that is able to interpret env variables
|
||||
and implements a simple validation scheme.
|
||||
"""
|
||||
multiple = False
|
||||
|
||||
def __init__(self, default=None, environ=False, environ_name=None,
|
||||
environ_prefix='DJANGO', *args, **kwargs):
|
||||
if isinstance(default, Value):
|
||||
self.default = copy.copy(default.default)
|
||||
else:
|
||||
self.default = default
|
||||
self.environ = environ
|
||||
if environ_prefix and environ_prefix.endswith('_'):
|
||||
environ_prefix = environ_prefix[:-1]
|
||||
self.environ_prefix = environ_prefix
|
||||
self.environ_name = environ_name
|
||||
|
||||
def __repr__(self):
|
||||
return "<Value default: {0}>".format(self.default)
|
||||
|
||||
def setup(self, name):
|
||||
value = self.default
|
||||
if self.environ:
|
||||
if self.environ_name is None:
|
||||
environ_name = name.upper()
|
||||
else:
|
||||
environ_name = self.environ_name
|
||||
if self.environ_prefix:
|
||||
full_environ_name = '{0}_{1}'.format(self.environ_prefix,
|
||||
environ_name)
|
||||
else:
|
||||
full_environ_name = environ_name
|
||||
if full_environ_name in os.environ:
|
||||
value = self.to_python(os.environ[full_environ_name])
|
||||
return value
|
||||
|
||||
def to_python(self, value):
|
||||
"""
|
||||
Convert the given value of a environment variable into an
|
||||
appropriate Python representation of the value.
|
||||
This should be overriden when subclassing.
|
||||
"""
|
||||
return value
|
||||
|
||||
|
||||
class MultipleMixin(object):
|
||||
multiple = True
|
||||
|
||||
|
||||
class BooleanValue(Value):
|
||||
true_values = ('yes', 'y', 'true', '1')
|
||||
false_values = ('no', 'n', 'false', '0', '')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BooleanValue, self).__init__(*args, **kwargs)
|
||||
if self.default not in (True, False):
|
||||
raise ValueError('Default value {!r} is '
|
||||
'not a boolean value'.format(self.default))
|
||||
|
||||
def to_python(self, value):
|
||||
normalized_value = value.strip().lower()
|
||||
if normalized_value in self.true_values:
|
||||
return True
|
||||
elif normalized_value in self.false_values:
|
||||
return False
|
||||
else:
|
||||
raise ValueError('Cannot interpret '
|
||||
'boolean value {!r}'.format(value))
|
||||
|
||||
|
||||
class CastingMixin(object):
|
||||
exception = (TypeError, ValueError)
|
||||
message = 'Cannot interpret value {!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CastingMixin, self).__init__(*args, **kwargs)
|
||||
if isinstance(self.caster, six.string_types):
|
||||
self._caster = import_by_path(self.caster)
|
||||
elif callable(self.caster):
|
||||
self._caster = self.caster
|
||||
else:
|
||||
error = 'Cannot use caster of {0} ({1!r})'.format(self,
|
||||
self.caster)
|
||||
raise ImproperlyConfigured(error)
|
||||
|
||||
def to_python(self, value):
|
||||
try:
|
||||
return self._caster(value)
|
||||
except self.exception:
|
||||
raise ValueError(self.message.format(value))
|
||||
|
||||
|
||||
class IntegerValue(CastingMixin, Value):
|
||||
caster = int
|
||||
|
||||
|
||||
class FloatValue(CastingMixin, Value):
|
||||
caster = float
|
||||
|
||||
|
||||
class DecimalValue(CastingMixin, Value):
|
||||
caster = decimal.Decimal
|
||||
exception = decimal.InvalidOperation
|
||||
|
||||
|
||||
class ListValue(Value):
|
||||
converter = None
|
||||
message = 'Cannot interpret list item {!r} in list {!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.separator = kwargs.pop('separator', ',')
|
||||
converter = kwargs.pop('converter', None)
|
||||
if converter is not None:
|
||||
self.converter = converter
|
||||
super(ListValue, self).__init__(*args, **kwargs)
|
||||
# make sure the default is a list
|
||||
if self.default is None:
|
||||
self.default = []
|
||||
# initial conversion
|
||||
if self.converter is not None:
|
||||
self.default = [self.converter(value) for value in self.default]
|
||||
|
||||
def to_python(self, value):
|
||||
split_value = [v.strip() for v in value.strip().split(self.separator)]
|
||||
# removing empty items
|
||||
value_list = filter(None, split_value)
|
||||
if self.converter is None:
|
||||
return value_list
|
||||
|
||||
converted_values = []
|
||||
for list_value in value_list:
|
||||
try:
|
||||
converted_values.append(self.converter(list_value))
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError(self.message.format(list_value, value))
|
||||
return converted_values
|
||||
|
||||
|
||||
class BackendsValue(ListValue):
|
||||
|
||||
def converter(self, value):
|
||||
import_by_path(value)
|
||||
return value
|
||||
|
||||
|
||||
class TupleValue(ListValue):
|
||||
message = 'Cannot interpret tuple item {!r} in tuple {!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TupleValue, self).__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = ()
|
||||
else:
|
||||
self.default = tuple(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
return tuple(super(TupleValue, self).to_python(value))
|
||||
|
||||
|
||||
class SetValue(ListValue):
|
||||
message = 'Cannot interpret set item {!r} in set {!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SetValue, self).__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = set()
|
||||
else:
|
||||
self.default = set(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
return set(super(SetValue, self).to_python(value))
|
||||
|
||||
|
||||
class DictValue(Value):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DictValue, self).__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = dict(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(DictValue, self).to_python(value)
|
||||
if not value:
|
||||
return {}
|
||||
evaled_value = ast.literal_eval(value)
|
||||
if not isinstance(evaled_value, dict):
|
||||
raise ValueError('Cannot interpret dict value {!s}'.format(value))
|
||||
return evaled_value
|
||||
|
||||
|
||||
class ValidationMixin(object):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ValidationMixin, self).__init__(*args, **kwargs)
|
||||
if isinstance(self.validator, six.string_types):
|
||||
self._validator = import_by_path(self.validator)
|
||||
elif callable(self.validator):
|
||||
self._validator = self.validator
|
||||
else:
|
||||
error = 'Cannot use validator of {0} ({1!r})'.format(self,
|
||||
self.validator)
|
||||
raise ImproperlyConfigured(error)
|
||||
self.to_python(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
try:
|
||||
self._validator(value)
|
||||
except ValidationError:
|
||||
raise ValueError(self.message.format(value))
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
class EmailValue(ValidationMixin, Value):
|
||||
message = 'Cannot interpret email value {!r}'
|
||||
validator = 'django.core.validators.validate_email'
|
||||
|
||||
|
||||
class URLValue(ValidationMixin, Value):
|
||||
message = 'Cannot interpret URL value {!r}'
|
||||
validator = validators.URLValidator()
|
||||
|
||||
|
||||
class IPValue(ValidationMixin, Value):
|
||||
message = 'Cannot interpret IP value {!r}'
|
||||
validator = 'django.core.validators.validate_ipv46_address'
|
||||
|
||||
|
||||
class RegexValue(ValidationMixin, Value):
|
||||
message = "Regex doesn't match value {!r}"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
regex = kwargs.pop('regex', None)
|
||||
self.validator = validators.RegexValidator(regex=regex)
|
||||
super(RegexValue, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class PathValue(Value):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.check_exists = kwargs.pop('check_exists', True)
|
||||
super(PathValue, self).__init__(*args, **kwargs)
|
||||
|
||||
def setup(self, name):
|
||||
value = super(PathValue, self).setup(name)
|
||||
value = os.path.expanduser(value)
|
||||
if self.check_exists and not os.path.exists(value):
|
||||
raise ValueError('Path {!r} does not exist.'.format(value))
|
||||
return os.path.abspath(value)
|
||||
|
||||
|
||||
class SecretValue(Value):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['environ'] = True
|
||||
super(SecretValue, self).__init__(*args, **kwargs)
|
||||
if self.default is not None:
|
||||
raise ValueError('Secret values are only allowed to be '
|
||||
'set as environment variables')
|
||||
|
||||
def setup(self, name):
|
||||
value = super(SecretValue, self).setup(name)
|
||||
if not value:
|
||||
raise ValueError('Secret value {!r} is not set'.format(name))
|
||||
return value
|
||||
|
||||
|
||||
class DatabaseURLValue(CastingMixin, Value):
|
||||
caster = 'dj_database_url.parse'
|
||||
message = 'Cannot interpret database URL value {!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.alias = kwargs.pop('alias', 'default')
|
||||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', 'DATABASE_URL')
|
||||
super(DatabaseURLValue, self).__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = self.to_python(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(DatabaseURLValue, self).to_python(value)
|
||||
return {self.alias: value}
|
||||
|
||||
|
||||
class EmailURLValue(CastingMixin, MultipleMixin, Value):
|
||||
caster = 'dj_email_url.parse'
|
||||
message = 'Cannot interpret email URL value {!r}'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', 'EMAIL_URL')
|
||||
super(EmailURLValue, self).__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = self.to_python(self.default)
|
||||
|
||||
|
||||
class CacheURLValue(CastingMixin, Value):
|
||||
caster = 'django_cache_url.parse'
|
||||
message = 'Cannot interpret cache URL value {!r}'
|
||||
|
||||
def __init__(self, name='default', *args, **kwargs):
|
||||
self.alias = kwargs.pop('alias', 'default')
|
||||
kwargs.setdefault('environ', True)
|
||||
kwargs.setdefault('environ_prefix', None)
|
||||
kwargs.setdefault('environ_name', 'CACHE_URL')
|
||||
super(CacheURLValue, self).__init__(*args, **kwargs)
|
||||
if self.default is None:
|
||||
self.default = {}
|
||||
else:
|
||||
self.default = self.to_python(self.default)
|
||||
|
||||
def to_python(self, value):
|
||||
value = super(CacheURLValue, self).to_python(value)
|
||||
return {self.alias: value}
|
||||
1
docs/changes.rst
Normal file
1
docs/changes.rst
Normal file
|
|
@ -0,0 +1 @@
|
|||
.. include:: ../CHANGES.rst
|
||||
20
docs/conf.py
20
docs/conf.py
|
|
@ -48,14 +48,9 @@ copyright = u'2012-2013, Jannis Leidel and other contributors'
|
|||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
try:
|
||||
from configurations import __version__
|
||||
# The short X.Y version.
|
||||
version = '.'.join(__version__.split('.')[:2])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = __version__
|
||||
except ImportError:
|
||||
version = release = 'dev'
|
||||
version = '0.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
|
@ -291,4 +286,11 @@ epub_copyright = u'2012, Jannis Leidel'
|
|||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
intersphinx_mapping = {
|
||||
'python': ('http://docs.python.org/2.7', None),
|
||||
'sphinx': ('http://sphinx.pocoo.org/', None),
|
||||
'django': ('http://docs.djangoproject.com/en/dev/',
|
||||
'http://docs.djangoproject.com/en/dev/_objects/'),
|
||||
}
|
||||
|
||||
add_function_parentheses = add_module_names = False
|
||||
|
|
|
|||
77
docs/cookbook.rst
Normal file
77
docs/cookbook.rst
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
Cookbook
|
||||
========
|
||||
|
||||
Celery
|
||||
------
|
||||
|
||||
Given Celery's way to load Django settings in worker processes you should
|
||||
probably just add the following to the **begin** of your settings module::
|
||||
|
||||
from configurations import importer
|
||||
importer.install()
|
||||
|
||||
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities
|
||||
mentioned above.
|
||||
|
||||
FastCGI
|
||||
-------
|
||||
|
||||
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
|
||||
allowed to us Django's runfcgi_ management command (that would automatically
|
||||
handle the setup for your if you've followed the quickstart guide above), make
|
||||
sure to use something like the following script::
|
||||
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration')
|
||||
|
||||
from configurations.fastcgi import runfastcgi
|
||||
|
||||
runfastcgi(method='threaded', daemonize='true')
|
||||
|
||||
As you can see django-configurations provides a helper module
|
||||
``configurations.fastcgi`` that handles the setup of your configurations.
|
||||
|
||||
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
|
||||
|
||||
Envdir
|
||||
------
|
||||
|
||||
envdir_ is an effective way to set a large number of environment variables
|
||||
at once during startup of a command. This is great in combination with
|
||||
django-configuration's :class:`~configurations.values.Value` subclasses
|
||||
when enabling their ability to check environment variables for override
|
||||
values.
|
||||
|
||||
Imagine for example you want to set a few environment variables, all you
|
||||
have to do is to create a directory with files that have capitalized names
|
||||
and contain the values you want to set.
|
||||
|
||||
Example::
|
||||
|
||||
$ tree mysite_env/
|
||||
mysite_env/
|
||||
├── DJANGO_SETTINGS_MODULE
|
||||
├── DJANGO_DEBUG
|
||||
├── DJANGO_DATABASE_URL
|
||||
├── DJANGO_CACHE_URL
|
||||
└── PYTHONSTARTUP
|
||||
|
||||
0 directories, 3 files
|
||||
$ cat mysite_env/DJANGO_CACHE_URL
|
||||
redis://user@host:port/1
|
||||
$
|
||||
|
||||
Then, to enable the ``mysite_env`` environment variables, simply use the
|
||||
``envdir`` command line tool as a prefix for your program, e.g.::
|
||||
|
||||
$ envdir mysite_env python manage.py runserver
|
||||
|
||||
See envdir_ documentation for more information, e.g. using envdir_ from
|
||||
Python instead of from the command line.
|
||||
|
||||
.. _envdir: https://pypi.python.org/pypi/envdir
|
||||
208
docs/index.rst
208
docs/index.rst
|
|
@ -53,161 +53,16 @@ behind the scenes.
|
|||
|
||||
.. _`PEP 302`: http://www.python.org/dev/peps/pep-0302/
|
||||
|
||||
Usage patterns
|
||||
--------------
|
||||
Further documentation
|
||||
---------------------
|
||||
|
||||
There are various configuration patterns that can be implemented with
|
||||
django-configurations. The most common pattern is to have a base class
|
||||
and various subclasses based on the enviroment they are supposed to be
|
||||
used in, e.g. in production, staging and development.
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
Server specific settings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For example, imagine you have a base setting class in your **settings.py**
|
||||
file::
|
||||
|
||||
from configurations import Settings
|
||||
|
||||
class Base(Settings):
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
class Dev(Base):
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
class Prod(Base):
|
||||
TIME_ZONE = 'America/New_York'
|
||||
|
||||
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
|
||||
of the class names you've defined, e.g. on your production server it
|
||||
should be ``Prod``. In bash that would be::
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
export DJANGO_CONFIGURATION=Prod
|
||||
python manage.py runserver
|
||||
|
||||
Alternatively you can use the ``--configuration`` option when using Django
|
||||
management commands along the lines of Django's default ``--settings``
|
||||
command line option, e.g.::
|
||||
|
||||
python manage.py runserver --settings=mysite.settings --configuration=Prod
|
||||
|
||||
Global settings defaults
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Every ``configurations.Settings`` subclass will automatically contain
|
||||
Django's global settings as class attributes, so you can refer to them when
|
||||
setting other values, e.g.::
|
||||
|
||||
from configurations import Settings
|
||||
|
||||
class Prod(Settings):
|
||||
TEMPLATE_CONTEXT_PROCESSORS = Settings.TEMPLATE_CONTEXT_PROCESSORS + (
|
||||
'django.core.context_processors.request',
|
||||
)
|
||||
|
||||
@property
|
||||
def LANGUAGES(self):
|
||||
return Settings.LANGUAGES + (('tlh', 'Klingon'),)
|
||||
|
||||
Mixins
|
||||
^^^^^^
|
||||
|
||||
You might want to apply some configuration values for each and every
|
||||
project you're working on without having to repeat yourself. Just define
|
||||
a few mixin you re-use multiple times::
|
||||
|
||||
class FullPageCaching(object):
|
||||
USE_ETAGS = True
|
||||
|
||||
Then import that mixin class in your site settings module and use it with
|
||||
a Settings class::
|
||||
|
||||
from configurations import Settings
|
||||
|
||||
class Prod(Settings, FullPageCaching):
|
||||
DEBUG = False
|
||||
# ...
|
||||
|
||||
Pristine methods
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
In case one of your settings itself need to be a callable, you need to
|
||||
tell that django-configurations by using the ``pristinemethod`` decorator,
|
||||
e.g.::
|
||||
|
||||
from configurations import Settings, pristinemethod
|
||||
|
||||
class Prod(Settings):
|
||||
|
||||
@pristinemethod
|
||||
def ACCESS_FUNCTION(user):
|
||||
return user.is_staff
|
||||
|
||||
Lambdas work, too::
|
||||
|
||||
from configurations import Settings, pristinemethod
|
||||
|
||||
class Prod(Settings):
|
||||
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
|
||||
|
||||
Setup methods
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
If there is something required to be set up before or after the settings
|
||||
loading happens, please override the ``pre_setup`` or ``post_setup``
|
||||
class methods like so (don't forget to apply the Python ``@classmethod``
|
||||
decorator::
|
||||
|
||||
from configurations import Settings
|
||||
|
||||
class Prod(Settings):
|
||||
# ...
|
||||
|
||||
@classmethod
|
||||
def pre_setup(cls):
|
||||
if something.completely.different():
|
||||
cls.DEBUG = True
|
||||
|
||||
@classmethod
|
||||
def post_setup(cls):
|
||||
print("done setting up! \o/")
|
||||
|
||||
As you can see above the ``pre_setup`` method can also be used to
|
||||
programmatically change a class attribute of the settings class and it
|
||||
will be taken into account when doing the rest of the settings setup.
|
||||
Of course that won't work for ``post_setup`` since that's when the
|
||||
settings setup is already done.
|
||||
|
||||
In fact you can easily do something unrelated to settings, like
|
||||
connecting to a database::
|
||||
|
||||
from configurations import Settings
|
||||
|
||||
class Prod(Settings):
|
||||
# ...
|
||||
|
||||
@classmethod
|
||||
def post_setup(cls):
|
||||
import mango
|
||||
mango.connect('enterprise')
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
You could do the same by overriding the ``__init__`` method of your
|
||||
settings class but this may cause hard to debug errors because
|
||||
at the time the ``__init__`` method is called (during Django startup)
|
||||
the Django setting system isn't fully loaded yet.
|
||||
|
||||
So anything you do in ``__init__`` that may require
|
||||
``django.conf.settings`` or Django models there is a good chance it
|
||||
won't work. Use the ``post_setup`` method for that instead.
|
||||
patterns
|
||||
values
|
||||
cookbook
|
||||
changes
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
|
@ -224,54 +79,15 @@ Many thanks to those project that have previously solved these problems:
|
|||
.. _Pinax: http://pinaxproject.com
|
||||
.. _`django-classbasedsettings`: https://github.com/matthewwithanm/django-classbasedsettings
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
Celery
|
||||
^^^^^^
|
||||
|
||||
Given Celery's way to load Django settings in worker processes you should
|
||||
probably just add the following to the **begin** of your settings module::
|
||||
|
||||
from configurations import importer
|
||||
importer.install()
|
||||
|
||||
That has the same effect as using the ``manage.py`` or ``wsgi.py`` utilities
|
||||
mentioned above.
|
||||
|
||||
FastCGI
|
||||
^^^^^^^
|
||||
|
||||
In case you use FastCGI for deploying Django (you really shouldn't) and aren't
|
||||
allowed to us Django's runfcgi_ management command (that would automatically
|
||||
handle the setup for your if you've followed the quickstart guide above), make
|
||||
sure to use something like the following script::
|
||||
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
|
||||
os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteSettings')
|
||||
|
||||
from configurations.fastcgi import runfastcgi
|
||||
|
||||
runfastcgi(method='threaded', daemonize='true')
|
||||
|
||||
As you can see django-configurations provides a helper module
|
||||
``configurations.fastcgi`` that handles the setup of your configurations.
|
||||
|
||||
.. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/
|
||||
|
||||
Bugs and feature requests
|
||||
-------------------------
|
||||
|
||||
As always you mileage may vary, so please don't hesitate to send in feature
|
||||
requests and bug reports at the usual place:
|
||||
As always your mileage may vary, so please don't hesitate to send feature
|
||||
requests and bug reports:
|
||||
|
||||
https://github.com/jezdez/django-configurations/issues
|
||||
|
||||
Thanks!
|
||||
Thanks! Feel free to leave a tip, too:
|
||||
|
||||
.. include:: ../CHANGES.rst
|
||||
https://www.gittip.com/jezdez/
|
||||
|
|
|
|||
167
docs/patterns.rst
Normal file
167
docs/patterns.rst
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
Usage patterns
|
||||
==============
|
||||
|
||||
There are various configuration patterns that can be implemented with
|
||||
django-configurations. The most common pattern is to have a base class
|
||||
and various subclasses based on the enviroment they are supposed to be
|
||||
used in, e.g. in production, staging and development.
|
||||
|
||||
Server specific settings
|
||||
------------------------
|
||||
|
||||
For example, imagine you have a base setting class in your **settings.py**
|
||||
file::
|
||||
|
||||
from configurations import Configuration
|
||||
|
||||
class Base(Configuration):
|
||||
TIME_ZONE = 'Europe/Berlin'
|
||||
|
||||
class Dev(Base):
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
class Prod(Base):
|
||||
TIME_ZONE = 'America/New_York'
|
||||
|
||||
You can now set the ``DJANGO_CONFIGURATION`` environment variable to one
|
||||
of the class names you've defined, e.g. on your production server it
|
||||
should be ``Prod``. In bash that would be::
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=mysite.settings
|
||||
export DJANGO_CONFIGURATION=Prod
|
||||
python manage.py runserver
|
||||
|
||||
Alternatively you can use the ``--configuration`` option when using Django
|
||||
management commands along the lines of Django's default ``--settings``
|
||||
command line option, e.g.::
|
||||
|
||||
python manage.py runserver --settings=mysite.settings --configuration=Prod
|
||||
|
||||
Global settings defaults
|
||||
------------------------
|
||||
|
||||
Every ``configurations.Configuration`` subclass will automatically contain
|
||||
Django's global settings as class attributes, so you can refer to them when
|
||||
setting other values, e.g.::
|
||||
|
||||
from configurations import Configuration
|
||||
|
||||
class Prod(Configuration):
|
||||
TEMPLATE_CONTEXT_PROCESSORS = Configuration.TEMPLATE_CONTEXT_PROCESSORS + (
|
||||
'django.core.context_processors.request',
|
||||
)
|
||||
|
||||
@property
|
||||
def LANGUAGES(self):
|
||||
return Configuration.LANGUAGES + (('tlh', 'Klingon'),)
|
||||
|
||||
Configuration mixins
|
||||
--------------------
|
||||
|
||||
You might want to apply some configuration values for each and every
|
||||
project you're working on without having to repeat yourself. Just define
|
||||
a few mixin you re-use multiple times::
|
||||
|
||||
class FullPageCaching(object):
|
||||
USE_ETAGS = True
|
||||
|
||||
Then import that mixin class in your site settings module and use it with
|
||||
a ``Configuration`` class::
|
||||
|
||||
from configurations import Configuration
|
||||
|
||||
class Prod(Configuration, FullPageCaching):
|
||||
DEBUG = False
|
||||
# ...
|
||||
|
||||
Pristine methods
|
||||
----------------
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
In case one of your settings itself need to be a callable, you need to
|
||||
tell that django-configurations by using the ``pristinemethod`` decorator,
|
||||
e.g.::
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
|
||||
class Prod(Configuration):
|
||||
|
||||
@pristinemethod
|
||||
def ACCESS_FUNCTION(user):
|
||||
return user.is_staff
|
||||
|
||||
Lambdas work, too::
|
||||
|
||||
from configurations import Configuration, pristinemethod
|
||||
|
||||
class Prod(Configuration):
|
||||
ACCESS_FUNCTION = pristinemethod(lambda user: user.is_staff)
|
||||
|
||||
Setup methods
|
||||
-------------
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
If there is something required to be set up before, during or after the
|
||||
settings loading happens, please override the ``pre_setup``, ``setup`` or
|
||||
``post_setup`` class methods like so (don't forget to apply the Python
|
||||
``@classmethod`` decorator)::
|
||||
|
||||
import logging
|
||||
from configurations import Configuration
|
||||
|
||||
class Prod(Configuration):
|
||||
# ...
|
||||
|
||||
@classmethod
|
||||
def pre_setup(cls):
|
||||
if something.completely.different():
|
||||
cls.DEBUG = True
|
||||
|
||||
@classmethod
|
||||
def setup(cls):
|
||||
super(Prod, cls).setup()
|
||||
logging.info('production settings loaded: %s', cls)
|
||||
|
||||
@classmethod
|
||||
def post_setup(cls):
|
||||
logging.debug("done setting up! \o/")
|
||||
|
||||
As you can see above the ``pre_setup`` method can also be used to
|
||||
programmatically change a class attribute of the settings class and it
|
||||
will be taken into account when doing the rest of the settings setup.
|
||||
Of course that won't work for ``post_setup`` since that's when the
|
||||
settings setup is already done.
|
||||
|
||||
In fact you can easily do something unrelated to settings, like
|
||||
connecting to a database::
|
||||
|
||||
from configurations import Configuration
|
||||
|
||||
class Prod(Configuration):
|
||||
# ...
|
||||
|
||||
@classmethod
|
||||
def post_setup(cls):
|
||||
import mango
|
||||
mango.connect('enterprise')
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
You could do the same by overriding the ``__init__`` method of your
|
||||
settings class but this may cause hard to debug errors because
|
||||
at the time the ``__init__`` method is called (during Django startup)
|
||||
the Django setting system isn't fully loaded yet.
|
||||
|
||||
So anything you do in ``__init__`` that may require
|
||||
``django.conf.settings`` or Django models there is a good chance it
|
||||
won't work. Use the ``post_setup`` method for that instead.
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
|
||||
A new ``setup`` method was added to be able to handle the new
|
||||
:class:`~configurations.values.Value` classes and allow a in-between
|
||||
modification of the configuration values.
|
||||
458
docs/values.rst
Normal file
458
docs/values.rst
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
Values
|
||||
======
|
||||
|
||||
.. module:: configurations.values
|
||||
:synopsis: Optional value classes for high-level validation and behavior.
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
django-configurations allows you to optionally reduce the amount of validation
|
||||
and setup code in your **settings.py** by using ``Value`` classes. They have
|
||||
the ability to handle values from the process environment of your software
|
||||
(:data:`os.environ`) and work well in projects that follow the
|
||||
`Twelve-Factor methodology`_.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Here is an example (from a **settings.py**)::
|
||||
|
||||
from configurations import values
|
||||
|
||||
DEBUG = values.BooleanValue(True)
|
||||
|
||||
As you can see all you have to do is to wrap your settings value in a call
|
||||
to one of the included settings classes. When Django's process starts up
|
||||
it will automatically make sure the passed in value validates correctly --
|
||||
in the above case checks if the value is really a boolean.
|
||||
|
||||
You can safely use other :class:`~Value` instances as the default setting
|
||||
value::
|
||||
|
||||
from configurations import values
|
||||
|
||||
DEBUG = values.BooleanValue(True)
|
||||
TEMPLATE_DEBUG = values.BooleanValue(DEBUG)
|
||||
|
||||
See the list of built-in value classes for more information.
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
|
||||
To separate configuration from your application you should use environment
|
||||
variables to override settings values if needed. Unfortunately environment
|
||||
variables are string based so they are not easily mapped to the Python based
|
||||
settings system Django uses.
|
||||
|
||||
Luckily django-configurations' :class:`~Value` subclasses have the ability
|
||||
to handle environment variables for the most common use cases.
|
||||
|
||||
For example, imagine you'd like to override the ``TEMPLATE_DEBUG`` setting
|
||||
on your staging server to be able to debug a problem with your in-development
|
||||
code. You're using a web server that passes the environment variables from
|
||||
the shell it was started in to your Django WSGI process.
|
||||
|
||||
First make sure you set the ``environ`` option of the :class:`~Value` instance
|
||||
to ``True``::
|
||||
|
||||
from configurations import values
|
||||
|
||||
# ..
|
||||
TEMPLATE_DEBUG = values.BooleanValue(True, environ=True)
|
||||
|
||||
That will tell django-configurations to look for a environment variable
|
||||
named ``DJANGO_TEMPLATE_DEBUG`` when deciding which value of the ``DEBUG``
|
||||
setting to actually enable.
|
||||
|
||||
When you run your web server simply specify that environment variable
|
||||
(e.g. in your init script)::
|
||||
|
||||
DJANGO_TEMPLATE_DEBUG=true gunicorn mysite.wsgi:application
|
||||
|
||||
Since environment variables are string based the ``BooleanValue`` supports
|
||||
a series of possible formats for a boolean value (``true``, ``yes``,
|
||||
``y`` and ``1``, all in capital and lower case, and ``false``, ``no``,
|
||||
``n`` and ``0`` of course). So for example this will work, too::
|
||||
|
||||
DJANGO_TEMPLATE_DEBUG=no ./manage.py runserver
|
||||
|
||||
``Value`` class
|
||||
---------------
|
||||
|
||||
.. class:: Value(default, [environ=False, environ_name=None, environ_prefix='DJANGO'])
|
||||
|
||||
The ``Value`` class takes one required and several optional parameters.
|
||||
|
||||
:param default: the default value of the setting
|
||||
:param environ: toggle for environment use
|
||||
:param environ_name: name of environment variable to look for
|
||||
:param environ_prefix: prefix to use when looking for environment variable
|
||||
:type environ: bool
|
||||
:type environ_name: capitalized string or None
|
||||
:type environ_prefix: capitalized string
|
||||
|
||||
The ``default`` parameter is effectively the value the setting has
|
||||
right now in your ``settings.py``.
|
||||
|
||||
.. method:: setup(name)
|
||||
|
||||
:param name: the name of the setting
|
||||
:return: setting value
|
||||
|
||||
The ``setup`` method is called during startup of the Django process and
|
||||
implements the ability to check the environment variable. Its purpose is
|
||||
to return a value django-configrations is supposed to use when loading
|
||||
the settings. It'll be passed one parameter, the name of the
|
||||
:class:`~Value` instance as defined in the ``settings.py``. This is used
|
||||
for building the name of the environment variable.
|
||||
|
||||
.. method:: to_python(value)
|
||||
|
||||
:param value: the value of the setting as found in the process
|
||||
environment (:data:`os.environ`)
|
||||
:return: validated and "ready" setting value if found in process
|
||||
environment
|
||||
|
||||
The ``to_python`` method is only used when the ``environ`` parameter
|
||||
of the :class:`~Value` class is set to ``True`` and an environment
|
||||
variable with the appropriate name was found. It will be used to handle
|
||||
the string based environment variable values and returns the "ready"
|
||||
value to be returned by the ``setup`` method.
|
||||
|
||||
Built-ins
|
||||
---------
|
||||
|
||||
Type values
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. class:: BooleanValue
|
||||
|
||||
A :class:`~Value` subclass that checks and returns boolean values. Possible
|
||||
values for environment variables are:
|
||||
|
||||
- ``True`` values: ``'yes'``, ``'y'``, ``'true'``, ``'1'``
|
||||
- ``False`` values: ``'no'``, ``'n'``, ``'false'``, ``'0'``,
|
||||
``''`` (empty string)
|
||||
|
||||
::
|
||||
|
||||
DEBUG = values.BooleanValue(True)
|
||||
|
||||
.. class:: IntegerValue
|
||||
|
||||
A :class:`~Value` subclass that handles integer values.
|
||||
|
||||
::
|
||||
|
||||
MYSITE_CACHE_TIMEOUT = values.BooleanValue(3600)
|
||||
|
||||
.. class:: FloatValue
|
||||
|
||||
A :class:`~Value` subclass that handles float values.
|
||||
|
||||
::
|
||||
|
||||
MYSITE_TAX_RATE = values.FloatValue(11.9)
|
||||
|
||||
.. class:: DecimalValue
|
||||
|
||||
A :class:`~Value` subclass that handles Decimal values.
|
||||
|
||||
::
|
||||
|
||||
MYSITE_CONVERSION_RATE = values.DecimalValue(decimal.Decimal('4.56214'))
|
||||
|
||||
.. class:: ListValue(default, [separator=',', converter=None])
|
||||
|
||||
A :class:`~Value` subclass that handles list values.
|
||||
|
||||
:param separator: the separator to split environment variables with
|
||||
:param converter: the optional converter callable to apply for each list
|
||||
item
|
||||
|
||||
Simple example::
|
||||
|
||||
ALLOWED_HOSTS = ListValue(['mysite.com', 'mysite.biz'])
|
||||
|
||||
Use a custom converter to check for the given variables::
|
||||
|
||||
def check_monty_python(person):
|
||||
if not is_completely_different(person):
|
||||
raise ValueError('{0} is not a Monty Python member'.format(person))
|
||||
return person
|
||||
|
||||
MONTY_PYTHONS = ListValue(['John Cleese', 'Eric Idle'],
|
||||
converter=check_monty_python,
|
||||
environ=True)
|
||||
|
||||
You can override this list with an environment variable like this::
|
||||
|
||||
DJANGO_MONTY_PYTHONS="Terry Jones,Graham Chapman" gunicorn mysite.wsgi:application
|
||||
|
||||
Use a custom separator::
|
||||
|
||||
EMERGENCY_EMAILS = ListValue(['admin@mysite.net'],
|
||||
separator=';', environ=True)
|
||||
|
||||
And override it::
|
||||
|
||||
DJANGO_EMERGENCY_EMAILS="admin@mysite.net;manager@mysite.org;support@mysite.com" gunicorn mysite.wsgi:application
|
||||
|
||||
.. class:: TupleValue
|
||||
|
||||
A :class:`~Value` subclass that handles tuple values.
|
||||
|
||||
:param separator: the separator to split environment variables with
|
||||
:param converter: the optional converter callable to apply for each tuple
|
||||
item
|
||||
|
||||
See the :class:`~ListValue` examples above.
|
||||
|
||||
.. class:: SetValue
|
||||
|
||||
A :class:`~Value` subclass that handles set values.
|
||||
|
||||
:param separator: the separator to split environment variables with
|
||||
:param converter: the optional converter callable to apply for each set
|
||||
item
|
||||
|
||||
See the :class:`~ListValue` examples above.
|
||||
|
||||
.. class:: DictValue
|
||||
|
||||
Validator values
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. class:: EmailValue
|
||||
|
||||
A :class:`~Value` subclass that validates the value using the
|
||||
:data:`django:django.core.validators.validate_email` validator.
|
||||
|
||||
::
|
||||
|
||||
SUPPORT_EMAIL = values.EmailValue('support@mysite.com')
|
||||
|
||||
.. class:: URLValue
|
||||
|
||||
A :class:`~Value` subclass that validates the value using the
|
||||
:class:`django:django.core.validators.URLValidator` validator.
|
||||
|
||||
::
|
||||
|
||||
SUPPORT_URL = values.URLValue('https://support.mysite.com/')
|
||||
|
||||
.. class:: IPValue
|
||||
|
||||
A :class:`~Value` subclass that validates the value using the
|
||||
:data:`django:django.core.validators.validate_ipv46_address` validator.
|
||||
|
||||
::
|
||||
|
||||
LOADBALANCER_IP = values.IPValue('127.0.0.1')
|
||||
|
||||
.. class:: RegexValue(default, regex, [environ=False, environ_name=None, environ_prefix='DJANGO'])
|
||||
|
||||
A :class:`~Value` subclass that validates according a regular expression
|
||||
and uses the :class:`django:django.core.validators.RegexValidator`.
|
||||
|
||||
:param regex: the regular expression
|
||||
|
||||
::
|
||||
|
||||
DEFAULT_SKU = values.RegexValue('000-000-00', regex=r'\d{3}-\d{3}-\d{2}')
|
||||
|
||||
.. class:: PathValue(default, [check_exists=True, environ=False, environ_name=None, environ_prefix='DJANGO'])
|
||||
|
||||
A :class:`~Value` subclass that normalizes the given path using
|
||||
:func:`os.path.expanduser` and validates if it exists on the file system.
|
||||
|
||||
Takes an optional ``check_exists`` parameter to disable the check with
|
||||
:func:`os.path.exists`.
|
||||
|
||||
:param check_exists: toggle the file system check
|
||||
|
||||
::
|
||||
|
||||
BASE_DIR = values.PathValue('/opt/mysite/')
|
||||
|
||||
URL-based values
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. note::
|
||||
|
||||
The following URL-based :class:`~Value` subclasses behave slightly different
|
||||
to the rest of the classes here. They are inspired by the
|
||||
`Twelve-Factor methodology`_ and by default inspect the :data:`os.environ`
|
||||
using a previously established environment variable name, e.g.
|
||||
``DATABASE_URL``.
|
||||
|
||||
Each of them require having an external library installed, e.g. the
|
||||
:class:`~DatabaseURLValue` class depends on the package ``dj-database-url``.
|
||||
|
||||
.. class:: DatabaseURLValue(default, [alias='default', environ=True, environ_name='DATABASE_URL', environ_prefix=None])
|
||||
|
||||
A :class:`~Value` subclass that uses the `dj-database-url`_ app to
|
||||
convert a database configuration value stored in the ``DATABASE_URL``
|
||||
environment variable into an appropriate setting value. It's inspired by
|
||||
the `Twelve-Factor methodology`_.
|
||||
|
||||
By default this :class:`~Value` subclass looks for the ``DATABASE_URL``
|
||||
environment variable.
|
||||
|
||||
Takes an optional ``alias`` parameter to define which database alias to
|
||||
use for the ``DATABASES`` setting.
|
||||
|
||||
:param alias: which database alias to use
|
||||
|
||||
The other parameters have the following default values:
|
||||
|
||||
:param environ: ``True``
|
||||
:param environ_name: ``DATABASE_URL``
|
||||
:param environ_prefix: ``None``
|
||||
|
||||
::
|
||||
|
||||
DATABASES = values.DatabaseURLValue('postgres://myuser@localhost/mydb')
|
||||
|
||||
.. _`dj-database-url`: https://pypi.python.org/pypi/dj-database-url/
|
||||
|
||||
.. class:: CacheURLValue(default, [alias='default', environ=True, environ_name='CACHE_URL', environ_prefix=None])
|
||||
|
||||
A :class:`~Value` subclass that uses the `django-cache-url`_ app to
|
||||
convert a cache configuration value stored in the ``CACHE_URL``
|
||||
environment variable into an appropriate setting value. It's inspired by
|
||||
the `Twelve-Factor methodology`_.
|
||||
|
||||
By default this :class:`~Value` subclass looks for the ``CACHE_URL``
|
||||
environment variable.
|
||||
|
||||
Takes an optional ``alias`` parameter to define which database alias to
|
||||
use for the ``CACHES`` setting.
|
||||
|
||||
:param alias: which cache alias to use
|
||||
|
||||
The other parameters have the following default values:
|
||||
|
||||
:param environ: ``True``
|
||||
:param environ_name: ``CACHE_URL``
|
||||
:param environ_prefix: ``None``
|
||||
|
||||
::
|
||||
|
||||
CACHES = values.CacheURLValue('memcached://127.0.0.1:11211/')
|
||||
|
||||
.. _`django-cache-url`: https://pypi.python.org/pypi/django-cache-url/
|
||||
|
||||
.. class:: EmailURLValue(default, [environ=True, environ_name='EMAIL_URL', environ_prefix=None])
|
||||
|
||||
A :class:`~Value` subclass that uses the `dj-email-url`_ app to
|
||||
convert an email configuration value stored in the ``EMAIL_URL``
|
||||
environment variable into the appropriate settings. It's inspired by
|
||||
the `Twelve-Factor methodology`_.
|
||||
|
||||
By default this :class:`~Value` subclass looks for the ``EMAIL_URL``
|
||||
environment variable.
|
||||
|
||||
.. note::
|
||||
|
||||
This is a special value since email settings are divided into many
|
||||
different settings. `dj-email-url`_ supports all options though and
|
||||
simply returns a nested dictionary of settings instead of just one
|
||||
setting.
|
||||
|
||||
The parameters have the following default values:
|
||||
|
||||
:param environ: ``True``
|
||||
:param environ_name: ``EMAIL_URL``
|
||||
:param environ_prefix: ``None``
|
||||
|
||||
::
|
||||
|
||||
EMAIL_URL = values.EmailURLValue('console://')
|
||||
|
||||
.. _`dj-email-url`: https://pypi.python.org/pypi/dj-email-url/
|
||||
|
||||
Other values
|
||||
^^^^^^^^^^^^
|
||||
|
||||
.. class:: BackendsValue
|
||||
|
||||
A :class:`~ListValue` subclass that validates the given list of dotted
|
||||
import paths by trying to import them. In other words, this checks if
|
||||
the backends exist.
|
||||
|
||||
::
|
||||
|
||||
MIDDLEWARE_CLASSES = values.BackendsValue([
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
])
|
||||
|
||||
.. class:: SecretValue
|
||||
|
||||
A :class:`~Value` subclass that doesn't allow setting a default value
|
||||
during instantiation and force-enables the use of an environment variable
|
||||
to reduce the risk of accidentally storing secret values in the settings
|
||||
file.
|
||||
|
||||
:raises: ``ValueError`` when given a default value
|
||||
|
||||
::
|
||||
|
||||
SECRET_KEY = values.SecretValue()
|
||||
|
||||
Value mixins
|
||||
^^^^^^^^^^^^
|
||||
|
||||
.. class:: CastingMixin
|
||||
|
||||
A mixin to be used with one of the :class:`~Value` subclasses that
|
||||
requires a ``caster`` class attribute of one of the following types:
|
||||
|
||||
- dotted import path, e.g. ``'mysite.utils.custom_caster'``
|
||||
- a callable, e.g. :func:`int`
|
||||
|
||||
Example::
|
||||
|
||||
class TemparatureValue(CastingMixin, Value):
|
||||
caster = 'mysite.temperature.fahrenheit_to_celcius'
|
||||
|
||||
Optionally it can take a ``message`` class attribute as the error
|
||||
message to be shown if the casting fails. Additionally an ``exception``
|
||||
parameter can be set to a single or a tuple of exception classes that
|
||||
are required to be handled during the casting.
|
||||
|
||||
.. class:: ValidationMixin
|
||||
|
||||
A mixin to be used with one of the :class:`~Value` subclasses that
|
||||
requires a ``validator`` class attribute of one of the following types:
|
||||
The validator should raise Django's
|
||||
:exc:`~django.core.exceptions.ValidationError` to indicate a failed
|
||||
validation attempt.
|
||||
|
||||
- dotted import path, e.g. ``'mysite.validators.custom_validator'``
|
||||
- a callable, e.g. :func:`bool`
|
||||
|
||||
Example::
|
||||
|
||||
class TemparatureValue(ValidationMixin, Value):
|
||||
validator = 'mysite.temperature.is_valid_temparature'
|
||||
|
||||
Optionally it can take a ``message`` class attribute as the error
|
||||
message to be shown if the validation fails.
|
||||
|
||||
.. class:: MultipleMixin
|
||||
|
||||
A mixin to be used with one of the :class:`~Value` subclasses that
|
||||
enables the return value of the :func:`~Value.to_python` to be
|
||||
interpreted as a dictionary of settings values to be set at once,
|
||||
instead of using the return value to just set one setting.
|
||||
|
||||
A good example for this mixin is the :class:`~EmailURLValue` value
|
||||
which requires setting many ``EMAIL_*`` settings.
|
||||
|
||||
.. _`Twelve-Factor methodology`: http://www.12factor.net/
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
flake8
|
||||
coverage
|
||||
django-discover-runner
|
||||
mock
|
||||
mock
|
||||
dj-database-url
|
||||
dj-email-url
|
||||
django-cache-url
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ author-email = jannis@leidel.info
|
|||
summary = A helper for organizing Django settings.
|
||||
description-file = README.rst
|
||||
license = BSD
|
||||
requires-dist = six
|
||||
home-page = http://django-configurations.readthedocs.org/
|
||||
project-url =
|
||||
Github, https://github.com/jezdez/django-configurations/
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
from configurations import Settings
|
||||
from configurations import Configuration, values
|
||||
|
||||
|
||||
class Base(Settings):
|
||||
class Base(Configuration):
|
||||
# Django settings for test_project project.
|
||||
|
||||
DEBUG = True
|
||||
DEBUG = values.BooleanValue(True, environ=True)
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
# ('Your Name', 'your_email@example.com'),
|
||||
)
|
||||
|
||||
EMAIL_URL = values.EmailURLValue('console://', environ=True)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
DATABASES = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue