Django Patterns
Contributors: Corey Oordt
You want to allow configuration of your app without having to modify its code. You may also want to provide reasonable defaults that users can override.
Use this whenever project- or implementation-specific information is required at runtime or there are obvious choices or options for the application.
Good examples:
Create a settings.py
file in your application
coolapp
├── __init__.py
├── admin.py
├── models.py
├── settings.py
├── tests.py
└── views.py
Inside the settings.py
file, you will import Django’s settings and use getattr()
to retrieve the value, or use a default value. There are several parts to this:
settings.py
, with a prefix to avoid collisions.settings.py
.1 2 3 | from django.conf import settings
COOL_WORD = getattr(settings, 'COOLAPP_COOL_WORD', 'cool')
|
Here, COOL_WORD
is the internal name, COOLAPP_COOL_WORD
is the namespaced name, and 'cool'
is the default value.
For something like an API key, you will want to draw attention if it’s empty. You will do this by raising an ImproperlyConfigured
exception.
1 2 3 4 5 6 7 | from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
API_KEY = getattr(settings, 'COOLAPP_API_KEY', None)
if API_KEY is None:
raise ImproperlyConfigured("You haven't set 'COOLAPP_API_KEY'.")
|
Django has internally began using dictionaries for groups of settings, such as DATABASES
. Django debug toolbar, for example, uses one dictionary to store all its configurations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | """
The main DebugToolbar class that loads and renders the Toolbar.
"""
from django.conf import settings
from django.template.loader import render_to_string
class DebugToolbar(object):
def __init__(self, request):
self.request = request
self.panels = []
base_url = self.request.META.get('SCRIPT_NAME', '')
self.config = {
'INTERCEPT_REDIRECTS': True,
'MEDIA_URL': u'%s/__debug__/m/' % base_url
}
# Check if settings has a DEBUG_TOOLBAR_CONFIG and updated config
self.config.update(getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}))
# ... more code below
|
It creates a standard set of configurations in line 13, and then uses the dictionaries update()
method in line 18 to add or override current key/values.
If your settings dictionary has a dictionary as a value, you need to take a slightly different approach. dict.update()
will completely overwrite the nested dictionaries, not merge them. To make things trickier, dict.update()
doesn’t return a value, so
1 2 | DEFAULT_SETTINGS.update(getattr(settings, 'FOOBAR_SETTINGS', {}))
DEFAULT_SETTINGS['FOO'] = DEFAULT_FOO.update(DEFAULT_SETTINGS.get('FOO', {}))
|
leaves DEFAULT_SETTINGS['FOO']
with a value of None
. So lets try something else.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | DEFAULT_SETTINGS = {
'ENABLED': False,
'DEBUG': False,
# ... other settings
}
DEFAULT_MARKUP_SETTINGS = {
'ENABLED': False,
'FIELD_SUFFIX': "tagged",
'EXCLUDE': [],
'CONTENT_CACHE_TIMEOUT': 3600,
'MIN_RELEVANCE': 0,
}
temp_settings = getattr(settings, 'SUPERTAGGING_SETTINGS', {})
USER_SETTINGS = dict(DEFAULT_SETTINGS.items() + temp_settings.items())
USER_SETTINGS['MARKUP'] = dict(
DEFAULT_MARKUP_SETTINGS.items() + USER_SETTINGS.get('MARKUP', {}).items()
)
|
In this example taken from django-supertagging, line 8 shows the default values for SUPERTAGGING_SETTINGS['MARKUP']
. Line 16 retrieves the SUPERTAGGING_SETTINGS
dictionary into a temporary variable using getattr
.
Line 17 merges the DEFAULT_SETTINGS
dictionary with the dictionary retrieved in line 16 into a new copy. By converting each dictionary into a list of tuple-pairs with the items()
method, it can combine them using the +
operator. When this list is converted back into a dictionary, it uses the last found key-value pair.
Lines 18-20 merge the defaults for MARKUP
with whatever the user has specified.
Having one dictionary for all your applications settings is all well and good, but requires more typing. Instead of typing:
1 2 3 4 | from settings import USER_SETTINGS
if USER_SETTINGS['ENABLED']:
# do something
pass
|
it would be nice to type:
1 2 3 4 | from settings import ENABLED
if ENABLED:
# do something
pass
|
What we want to do is convert the first set of keys into variables. Python has a built-in function called globals() that returns a dictionary of the symbol table of the current module.
If you printed the value of globals()
in an empty Python script, you would see something like:
>>> print globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}
Since globals()
returns a dictionary, you can use its update()
method to alter its contents in place.
>>> d = {'foo': 1, 'bar': 2}
>>> globals().update(d)
>>> print globals()
{'bar': 2, 'd': {'foo': 1, 'bar': 2}, '__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'foo': 1, '__doc__': None, '__package__': None}
Warning
While the effect of this is localized to this module, you must be careful with the names of the dictionary keys. If there is a naming conflict, your dictionary wins. That is probably not what you want.
Using the suppertagging example above, adding:
1 | globals().update(USER_SETTINGS)
|
to the bottom of the supertagging/settings.py
file, gives you access to all the top-level keys of USER_SETTINGS
as attributes of settings.py
Access to your settings is a simple import. As shown in the previous section, you can import the entire dictionary of settings or, if you added them to the settings module, you can import them individually.