Code


Version 13 (modified by CordiS, 4 years ago) (diff)

--

Splitting up the settings file

If you use a source control system (CVS, SVN, ...), or want to publish your application on the web, it may be a good idea to move sensitive or machine/user specific settings like database passwords and such out of the main settings.py file.

As discussions on the django-developers mailing list have shown everybody has different requirements and ideas how to do this. This page is meant to collect some of these ideas for future reference.

One thing to keep in mind is that django's config files are pure python. This gives you the ultimate flexibility to handle configurations the way you think is best. Or to quote Adrian Holovaty:

We don't need a default solution for this. It's not within the scope
of this project to tell people how they should organize their settings
files. Take that opportunity to showcase your individualism.

Links to discussions

Different settings in different files

I believe the first user who came up with this was Hugo, who used this method in the projects he published on his site.

SECRET_KEY = open(os.path.expanduser('~/.gallery-secret')).read().strip()

ini-style file for deployment

This is a solution that Michael Radziej posted to django-developers. His motivation was to be able to store the settings for an app he published under /etc with all the other system config files.

/etc/whatever/settings.ini

[database]
DATABASE_USER: bla
DATABASE_PASSWORD: XXXXXXXX
DATABASE_HOST: dev
DATABASE_PORT:
DATABASE_ENGINE: mysql
DATABASE_NAME: blo
TESTSUITE_DATABASE_NAME: test_blo

[secrets]
SECRET_KEY: random-string-of-ascii
CSRF_MIDDLEWARE_SECRET: random-string-of-ascii

[cookies]
SESSION_COOKIE_DOMAIN:

# all settings in debug section should be false in productive
environment
# INTERNAL_IPS should be empty in productive environment
[debug]
DEBUG: true
TEMPLATE_DEBUG: true
VIEW_TEST: true
INTERNAL_IPS: 127.0.0.1
SKIP_CSRF_MIDDLEWARE: true

[email]
SERVER_EMAIL: django@localhost
EMAIL_HOST: localhost

# the [error mail] and [404 mail] sections are special. Just add
lines with
#  full name: email_address@domain.xx
# each section must be present but may be empty.
[error mail]
Adam Smith: adam@localhost

[404 mail]
John Wayne: john@localhost

/path/to/whatever/settings.py

from ConfigParser import RawConfigParser

config = RawConfigParser()
config.read('/etc/whatever/settings.ini')

DATABASE_USER = config.get('database', 'DATABASE_USER')
DATABASE_PASSWORD = config.get('database', 'DATABASE_PASSWORD')
DATABASE_HOST = config.get('database', 'DATABASE_HOST')
DATABASE_PORT = config.get('database', 'DATABASE_PORT')
DATABASE_ENGINE = config.get('database', 'DATABASE_ENGINE')
DATABASE_NAME = config.get('database', 'DATABASE_NAME')
TEST_DATABASE_NAME = config.get('database', 'TESTSUITE_DATABASE_NAME')

SECRET_KEY = config.get('secrets','SECRET_KEY')
CSRF_MIDDLEWARE_SECRET = config.get('secrets', 'CSRF_MIDDLEWARE_SECRET')

SESSION_COOKIE_DOMAIN = config.get('cookies','SESSION_COOKIE_DOMAIN')

DEBUG = config.getboolean('debug','DEBUG')
TEMPLATE_DEBUG = config.getboolean('debug','TEMPLATE_DEBUG')
VIEW_TEST = config.getboolean('debug', 'VIEW_TEST')
INTERNAL_IPS = tuple(config.get('debug', 'INTERNAL_IPS').split())
if config.getboolean('debug', 'SKIP_CSRF_MIDDLEWARE'):
    MIDDLEWARE_CLASSES = tuple([x for x in list(MIDDLEWARE_CLASSES)
                                  if not x.endswith('CsrfMiddleware')])

SERVER_EMAIL = config.get('email', 'SERVER_EMAIL')
EMAIL_HOST = config.get('email', 'EMAIL_HOST')
ADMINS = tuple(config.items('error mail'))
MANAGERS = tuple(config.items('404 mail'))

Multiple setting files importing from each other

This is my (Steven Armstrong) preferred solution.

Keep application wide, unsensitive settings and sane defaults in your normal settings.py file.

/path/to/whatever/settings.py

import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# why is this here?
#PROJECT_DIR = os.path.abspath(os.path.join(BASE_DIR, '..'))


PROJECT_DIR = BASE_DIR

DEBUG = False
TEMPLATE_DEBUG = DEBUG

ADMINS = (
    ('Mr Sysadmin', 'sysadmin@domain.tld'),
)

MANAGERS = ADMINS

DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = os.path.join(PROJECT_DIR, 'project.db')
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''

TIME_ZONE = 'Europe/Zurich'
LANGUAGE_CODE = 'en-us'
SECRET_KEY = 'secret'

#[more default and app wide settings]

from settings_local import *

At the end of your normal settings.py include * from an other, machine specific config file which could look something like this.

/path/to/whatever/settings_local.py

DEBUG = True
TEMPLATE_DEBUG = DEBUG

# don't want emails while developing
ADMINS = ()
MANAGERS = ADMINS

DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'mydbname'
DATABASE_USER = 'mydbuser'
DATABASE_PASSWORD = 'mydbpassword'
DATABASE_HOST = 'localhost'
DATABASE_PORT = ''

SECRET_KEY = 'random-string-of-ascii'

#[more user/machine specific settings]
# urls.py
import settings
     (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.BASE_DIR+'/core/static/', 'show_indexes': True}),                      

settings.py goes into CVS, SVN, (put your favorite RCS here)
settings_local.py does _not_ go under RCS

If wanted a settings_local.template file can be put under version control with instructions to copy it over to settings_local.py, change it to suite the environment, and to never ever commit it to the RCS system.

Development/Machine Dependant Settings Configuration

I personally use this solution.

/path/to/project/settings.py

# Django project settings loader
import os
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))

# You can key the configurations off of anything - I use project path.
configs = {
    '/path/to/user1/dev/project': 'user1',
    '/path/to/user2/dev/project': 'user2',
    '/path/to/qa/project': 'qa',
    '/path/to/prod/project': 'prod',
}

# Import the configuration settings file - REPLACE projectname with your project
config_module = __import__('config.%s' % configs[ROOT_PATH], globals(), locals(), 'projectname')

# Load the config settings properties into the local scope.
for setting in dir(config_module):
    if setting == setting.upper():
        locals()[setting] = getattr(config_module, setting)

This settings loader will import the appropriate settings module. I store my settings files in a config directory in the root of the project.

/path/to/project/config/prod.py

DEBUG = False
TEMPLATE_DEBUG = DEBUG

# don't want emails while developing
ADMINS = ()
MANAGERS = ADMINS

DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'mydbname'
DATABASE_USER = 'mydbuser'
DATABASE_PASSWORD = 'mydbpassword'
DATABASE_HOST = 'localhost'
DATABASE_PORT = ''

SECRET_KEY = 'random-string-of-ascii'

# More production settings

/path/to/project/config/user1.py

# Load the production settings that we will overwrite as needed in our user1 settings file.
from projectname.config.prod import *

DEBUG = True

DATABASE_NAME = 'devdbname'

# More development/maching specific settings

Using a list of conf files (Transifex)

Transifex has adopted a solution using an ordered list of multiple .conf files inside a settings directory. This helps separating the config options, packaging in Linux distributions, and putting the settings files under /etc.

$ ls settings/

10-base.conf
20-engines.conf
30-site.conf
...

$ cat settings.py

import os.path
import glob
conffiles = glob.glob(os.path.join(os.path.dirname(__file__), 'settings', '*.conf'))
conffiles.sort()
for f in conffiles:
    execfile(os.path.abspath(f))

To apply local-only changes, create a XX-local.conf file, which can be ignored with your VCS of choice:

$ cat settings/51-local.conf

INSTALLED_APPS += ['debug_toolbar']

$ cat .hgignore

syntax: glob
settings/*-local.conf

Accessing and modifying existing settings

I (Antti Kaihola) have used the following solution to modify settings which are tuples or lists. This is handy e.g. when separating app-specific settings to their own files.

settings.py:

INSTALLED_APPS = ('whatever',)
import more_settings
more_settings.modify(globals())

more_settings.py:

def modify(settings):
    settings['INSTALLED_APPS'] += ('another_app',)

alternate more_settings.py with more magic:

def config(INSTALLED_APPS=(), **other_settings):
    INSTALLED_APPS += ('another_app',)
    del other_settings
    return locals()

def modify(settings):
    settings.update(config(**settings))

Settings inheritance

I (Sergey Yushckeyev) have extended proposed solution:

env

export APP_SETTINGS = 'development.cordis'

settings/init.py

CONFIGURATION = 'common'

def deep_update(from_dict, to_dict):
    for (key, value) in from_dict.iteritems():
        if key in to_dict.keys() and isinstance(to_dict[key], dict) and isinstance(value, dict):
            deep_update(value, to_dict[key])
        else:
            to_dict[key] = value

try:
    modules = os.environ.get('APP_SETTINGS', '').split('.')
    current = __name__
    for name in modules:
        module  = getattr(__import__(current, globals(), locals(), [name]), name)
        current += '.' + name
        current_settings = {}
        for setting in dir(module):
            if setting == setting.upper():
                current_settings[setting] = getattr(module, setting)
        deep_update(current_settings, locals())
except ImportError, e:
    print 'Unable to import configuration: %s' % (str(e))

settings/development/cordis.py

CONFIGURATION = 'cordis'

app/models.py

from settings import CONFIGURATION
print CONFIGURATION # prints 'cordis'