5 | SettingsandRequirementsFiles
Django 1.8 has over 140 settings that can be controlled in the settings module, most of which come with default values. Settings are loaded when your server starts up, and experienced Django devel- opers stay away from trying to change settings in production since they require a server restart.
Some best practices we like to follow:
All settings files need to be version-controlled. is is especially true in production environ- ments, where dates, times, and explanations for settings changes absolutely must be tracked.
Don’t Repeat Yourself. You should inherit from a base settings le rather than cutting-and- pasting from one le to another.
Keep secret keys safe. They should be kept out of version control.
5.1 Avoid Non-Versioned Local Settings
We used to advocate the non-versioned local settings anti-pattern. Now we know better.
As developers, we have our own necessary settings for development, such as settings for debug tools which should be disabled (and often not installed to) staging or production servers.
Furthermore, there are often good reasons to keep speci c settings out of public or private code repositories.The SECRET KEY setting is the first thing that comes to mind, but API key settings to services like Amazon, Stripe, and other password-type variables need to be protected.
WARNING: Protect Your Secrets!
The SECRET KEY setting is used in Django’s cryptographic signing functionality, and needs to be set to a unique, unpredictable setting best kept out of version control. Running Django with a known SECRET KEY defeats many of Django’s security protections, which can lead to serious security vulnerabilities. For more details, read https://docs.djangoproject.com/en/1.8/topics/signing/.
The same warning for SECRET KEY also applies to production database passwords, AWS keys, OAuth tokens, or any other sensitive data that your project needs in order to operate. Later in this chapter we’ll show how to handle the SECRET KEY issue in the “Keep Secret Keys Out With Environment Settings” section.
A common solution is to create local settings.py modules that are created locally per server or development machine, and are purposefully kept out of version control. Developers now make development- specific settings changes, including the incorporation of business logic without the code being tracked in version control. Staging and deployment servers can have location speci c settings and logic without them being tracked in version control.
What could possibly go wrong?!?
Ahem...
- Every machine has untracked code.
- How much hair will you pull out, when after hours of failing to duplicate a production bug locally, you discover that the problem was custom logic in a production-only setting?
- How fast will you run from everyone when the ‘bug’ you discovered locally, xed and pushed to production was actually caused by customizations you made in your own local settings.py module and is now crashing the site?
- Everyone copy/pastes the same local settings.py module everywhere. Isn’t this a violation of Don’t Repeat Yourself but on a larger scale?
Let’s take a different approach. Let’s break up development, staging, test, and production settings into separate components that inherit from a common base object in a settings le tracked by version control. Plus, we’ll make sure we do it in such a way that server secrets will remain secret.
Read on and see how it’s done!
5.2 Using Multiple Settings Files
TIP: History of This Setup Pattern
The setup described here is based on the so-called “The One True Way”, from Jacob Kaplan-Moss’ The Best (and Worst) of Django talk at OSCON 2011. See http://2scoops.co/the-best-and-worst-of-django.
Instead of having one settings.py file, with this setup you have a settings/ directory containing your settings les. is directory will typically contain something like the following:
EXAMPLE 5.1
settings/
__init__.py
base.py
local.py
staging.py
test.py
production.py
WARNING: Requirements + Settings
Each settings module should have its own corresponding requirements le. We’ll cover this at the end of this chapter in section 5.5, ‘Using Multiple Requirements Files.’
Settings file | Purpose |
---|---|
base.py | Settings common to all instances of the project. |
local.py | This is the settings file that you use when you're working on the project locally. Local development-specific settings include DEBUG mode, log level, and activation of developer tools like django-debug-toolbar. Developers sometimes name this file dev.py. |
staging.py | Staging version for running a semi-private version of the site on a production server. This is where managers and clients should be looking before your work is moved to production. |
test.py | Settings for running tests including test runners, in-memory database definitions, and log settings. |
production.py | This is the settings file used by your live production server(s). That is, the server(s) that host the real live website. This file contains production-level settings only. It is sometimes called prod.py. |
Table 5.1: Settings files and their purpose
TIP: Multiple Files With Continuous Integration Servers
You’ll also want to have a ci.py module containing that server’s settings. Similarly, if it’s a large project and you have other special-purpose servers, you might have custom settings les for each of them.
Let’s take a look at how to use the shell and runserver management commands with this setup. You’ll have to use the --settings command line option, so you’ll be entering the following at the command-line.
To start the Python interactive interpreter with Django, using your settings/local.py settings file:
EXAMPLE 5.2
python manage.py shell --settings=twoscoops.settings.local
To run the local development server with your settings/local.py settings file:
EXAMPLE 5.3
python manage.py runserver --settings=twoscoops.settings.local
TIP: DJANGO SETTINGS MODULE and PYTHONPATH
A great alternative to using the --settings command line option everywhere is to set the DJANGO SETTINGS MODULE and PYTHONPATH environment variable to your desired settings module path. You’d have to set DJANGO SETTINGS MODULE to the corresponding settings module for each environment, of course.
For those with a more comprehensive understanding of virtualenv, another alternative is to set DJANGO SETTINGS MODULE and PYTHONPATH in the postactivate script. en, once the virtualenv is activated, you can just type python from anywhere and import those values into your project. is also means that typing django-admin.py at the command-line works without the --settings option.
For the settings setup that we just described, here are the values to use with the --settings com- mand line option or the DJANGO SETTINGS MODULE environment variable:
Environment | Option To Use With --setting (or DJANGO SETTINGS MODULE value) |
---|---|
Your local development server | twoscoops.settings.local |
Your staging server | twoscoops.settings.staging |
Your test server | twoscoops.settings.test |
Your production server | twoscoops.settings.production |
Table 5.2: Setting DJANGO SETTINGS MODULE per location
5.2.1 A Development Settings Example
As mentioned earlier, we need settings con gured for development, such as selecting the console email backend, setting the project to run in DEBUG mode, and setting other con guration options that are used solely for development purposes. We place development settings like the following into settings/local.py :
EXAMPLE 5.4
# settings/local.py
from .base import *
DEBUG = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DATABASES = {
"default": {
"ENGINE": "django.core.mail.backends.console.EmailBackend",
"NAME": "twoscoops",
"USER": "",
"PASSWORD": "",
"HOST": "localhost",
"PORT": ""
}
}
INSTALLED_APPS += ("debug_toolbar", )
Now try it out at the command line with:
EXAMPLE 5.5
python manage.py runserver --settings=twoscoops.settings.local
Open http://127.0.0.1:8000 and enjoy your development settings, ready to go into version control! You and other developers will be sharing the same development settings files, which for shared projects, is awesome.
Yet there’s another advantage: No more ‘if DEBUG’ or ‘if not DEBUG’ logic to copy/paste around between projects. Settings just got a whole lot simpler!
At this point we want to take a moment to note that Django settings files are the single, solitary place we advocate using import *. The reason is that for the singular case of Django setting modules we want to override all the namespace.
5.2.2 Multiple Development Settings
Sometimes we’re working on a large project where different developers need different settings, and sharing the same dev.py settings module with teammates won’t do.
Well, it’s still better tracking these settings in version control than relying on everyone customizing the same dev.py or local_settings.py
module to their own tastes. A nice way to do this is with multiple dev settings files, e.g. dev_audrey.py
and dev_pydanny.py
:
EXAMPLE 5.6
# settings/dev_pydanny.py
from .local import *
# Set short cache timeout
CACHE_TIMEOUT = 30
Why? It’s not only good to keep all your own settings les in version control, but it’s also good to be able to see your teammates’ dev settings files. That way, you can tell if someone’s missing a vital or helpful setting in their local development setup, and you can make sure that everyone’s local settings files are synchronized. Here is what our projects frequently use for settings layout:
EXAMPLE 5.7
settings/
__init__.py
base.py
dev_audreyr.py
dev_pydanny.py
local.py
staging.py
test.py
production.py
5.3 Separate Configuration From Code
One of the causes of the local settings anti-pattern is that putting SECRET KEY, AWS keys, API keys, or server-specific values into settings files has problems:
- Config varies substantially across deploys, code does not.
- Secret keys are configuration values, not code.
- Secrets often should be just that: secret! Keeping them in version control means that everyone with repository access has access to them.
- Platforms-as-a-service usually don’t give you the ability to edit code on individual servers. Even if they allow it, it’s a terribly dangerous practice.
To resolve this, our answer is to use environment variables in a pattern we like to call, well, The Environment Variables Pattern.
Every operating system supported by Django (and Python) provides the easy capability to create environment variables.
Here are the benefits of using environment variables for secret keys:
- Keeping secrets out of settings allows you to store every settings file in version control without hesitation. All of your Python code really should be stored in version control, including your settings.
- Instead of each developer maintaining an easily-outdated, copy-and-pasted version of the local_settings.py.example file for their own development purposes, everyone shares the same version-controlled settings/local.py.
- System administrators can rapidly deploy the project without having to modify files containing Python code.
- Most platforms-as-a-service recommend the use of environment variables for configuration and have built-in features for setting and managing them.
TIP: 12 Factor App: Store Config in the Environment
If you've read the 12 Factor App's article on configuration you'll recognize this pattern. For reference, see http://12factor.net/config. Some developers even advocate combining the use of environment variables with a single settings modules. We cover this practice in Appendix E under 12 Factor-Style Settings.
5.3.1 A Caution Before Using Environment Variables for Secrets
Before you begin setting environment variables, you should have the following:
A way to manage the secret information you are going to store.
A good understanding of how bash works with environment variables on servers, or a willing- ness to have your project hosted by a platform-as-a-service.
For more information, see http://2scoops.co/wikipedia-env-variable.
WARNING: Environment Variables Do Not Work With Apache
If your target production environment uses Apache, then you will discover that setting op- erating system environment variables as described below doesn’t work. Confusing the issue is that Apache has its own environment variable system, which is almost but not quite what you’ll need. If you are using Apache and want to avoid the local settings anti-pattern, we recommend reading section 5.4, ‘When You Can’t Use Environment Variables,’ later in this chapter.
5.3.2 How to Set Environment Variables Locally
On Mac and many Linux distributions that use bash for the shell, one can add lines like the following to the end of a .bashrc, .bash profile, or .profile. When dealing with multiple projects using the same API but with different keys, you can also place these at the end of your virtualenv’s bin/activate script:
EXAMPLE 5.8
$ export SOME_SECRET_KEY=1c3-cr3am-15-yummy $ export AUDREY_FREEZER_KEY=y34h-r1ght-d0nt-t0uch-my-1c3-cr34m
On Windows systems, it’s a bit trickier. You can set them one-by-one at the command line (cmd.exe) in a persistent way with the setx command, but you’ll have to close and reopen your command prompt for them to go into effect. A better way is to place these commands at the end of the vir- tualenv’s bin/activate.bat script so they are available upon activation:
EXAMPLE 5.9
set SOME_SECRET_KEY 1c3-cr3am-15-yummy
PowerShell is much more powerful than the default Windows shell and comes with Windows Vista and above. Setting environment variables while using PowerShell:
For the current Windows user only:
EXAMPLE 5.10
Machine-wide:
EXAMPLE 5.11
For more information on Powershell, see http://2scoops.co/powershell
TIP: virtualenvwrapper Makes This Easier
Mentioned earlier in this book, virtualenvwrapper, simplifies per-virtualenv environment variables. It’s a great tool. Of course, setting it up requires a more-than-basic understanding of the shell and Mac OS X, Linux, or Windows.
5.3.3 How to Set Environment Variables in Production
If you’re using your own servers, your exact practices will differ depending on the tools you’re using and the complexity of your setup. For the simplest 1-server setup for test projects, you can set the environment variables manually. But if you’re using scripts or tools for automated server provisioning and deployment, your approach may be more complex. Check the documentation for your deployment tools for more information.
If your Django project is deployed via a platform-as-a-service, check the documentation for specific instructions. We’ve included Heroku instructions here so that you can see that it’s similar for platform-as-a-service options.
On Heroku, you set environment variables with the following command, executed from your development machine:
EXAMPLE 5.12
$ heroku config:set SOME_SECRET_KEY=1c3-cr3am-15-yummy
To see how you access environment variables from the Python side, open up a new Python prompt and type:
EXAMPLE 3.13
import os os.environ("SOME _SECRET_KEY") "1c3-cr3am-15-yummy"
To access environment variables from one of your settings les, you can do something like this:
EXAMPLE 3.14
# Top of settings/prodution.py
import os SOME_SECRET_KEY = os.environ("SOME_SECRET_KEY")
This snippet simply gets the value of the SOME_SECRET_KEY environment variable from the operating system and saves it to a Python variable called SOME_SECRET_KEY.
Following this pattern means all code can remain in version control, and all secrets remain safe.
5.3.4 Handling Missing Secret Key Exceptions
In the above implementation, if the SECRET_KEY isn’t available, it will throw a KeyError, making it impossible to start the project. That’s great, but a KeyError doesn’t tell you that much about what’s actually wrong. Without a more helpful error message, this can be hard to debug, especially under the pressure of deploying to servers while users are waiting and your ice cream is melting.
Here’s a useful code snippet that makes it easier to troubleshoot those missing environment variables. If you’re using our recommended environment variable secrets approach, you’ll want to add this to your settings/base.py file:
EXAMPLE 5.15
# settings/base.py
import os
# Normally you should not import ANYTHING from Django directly # info your settings, but ImproperlyConfigured is an exception.
from django.core.exceptions import ImproperlyConfigured
def get_env_variable(var_name): """Get the environment variabel ro return exception""" try: return os.environ(var_name) except KeyError: error_msg = "Set the {} environment variable".format(var_name) raise ImproperlyConfigured(error_msg)
Then, in any of your settings files, you can load secret keys from envrionment variables as follows:
EXAMPLE 5.16
SOME_SECRET_KEY = get_env_variable("SOME_SECRET_KEY")
Now, if you don't have SOME_SECRET_KEY set as an environment variable, you get a traceback that ends with a useful error message like this:
EXAMPLE 5.17
django.core.exceptions.ImproperlyConfigured : Set the SOME_SECRET_KEY environment variable.
WARNGIN: Don't Import Django Components Into Settings Modules
This can have many unpredictable side effects, so avoid any sort of import of Django components into your settings. ImproperlyConfigured is the exception because it's the official Django exception for...well...improperly configured projects. And just to be helpful we add the name of the problem setting to the error message.
TIP: Using django-admin.py Instead of manage.py
The official Django documenttation says that you should use django-admin.py rather than manage.py when working with multiple settings files: https://docs.djangoproject.com/en/1.8/ref/django-admin/
That being said, if you're struggling with django-admin.py, it's perfectly okey to develop and launch your site running it with manage.py.
5.4 When You Can't Use Environment Variables
The problem with using environment variables to store secrets is that is doesn't always work. The most common scenario for this is when using Apache for serving HTTP, but this also happens even in Nginx-based environments where operations wants to do things in a particular way. When this occurs, rather than going back to the local_settings anti-pattern, we advocate using non-executable files kept out of version control in a method we like to call the secrets file pattern.
To implement the secrets file pattern, follow these three steps:
- Create a secrets file using the configuration format fo choice, be it JSON, Config, YAML, or even XML.
- Add a secrets loader (JSON-powered example below) to manage the secrets in a cohesive, explicit manner.
- Add the secrets file name to the .gitignore or .bgignre.
5.4.1 Using JSON Files
Our preference is to use shallow JSON file. The JSON format has the advantage of being the format of choise for various Python and non-Python tools. To use the JSON format, first create a secrets.json file:
EXAMPLE 5.18 { "FILENAME": "secrets.json", "SECRET_KEY": "I've got a secret!", "DATABASES_HOST": "127.0.0.1", "PORT": "5432" }
To use the secrets.json file, add the following code to your base settings module.
EXAMPLE 5.19
# settings/base.py
import json
# Normally you should not import ANYTHING from Django directly # into settings, but ImproperlyConfigured is an exception.
with open("secrets.json") as f: secrets = json.loads(f.read())
def get_secret(setting, secrets=secrets): """ Get the secret variable or return explicit exception. """ try: return secrets[setting] except: error_msg = "Set the {0} environment variable".format(setting) raise ImproperlyConfigured(error_msg)
SECRET_KEY = get_secret("SECRET_KEY")
Now we are loading secrets from non-executable JSON files instead of from unversioned executable code. Hooray!
5.4.2 Using Config, YAML, and XML File Formats
While we prefer the forced simplicity of shalow JSON, others might prefer other file formats. We'll leave it up to the reader to create additional get_secret() alternatives that work with these formats. Just remember to be familiar with things like yaml.safe.load() and XML bombs. See section 26.9, 'Defend Against Python Code Injection Attacks.'
5.5 Using Multiple Requirements Files
Finally, there’s one more thing you need to know about multiple settings files setup. It’s good practice for each settings file to have its own corresponding requirements file. This means we’re only installing what is required on each server.
To follow this pattern, recommended to us by Jeff Triplett, first create a requirements/ directory in the <repository_root>. Then create '.txt' files that match the contents of your settings directory. The results should look something like:
EXAMPLE 5.20
requirements/
base.txt
local.txt
staging.txt
prodution.txt
In the base.txt file, place the dependencies, used in all environments. For example, you might have something like the following in there:
EXAMPLE 5.21
Django==1.8.0 psycopg2==2.6 djangorestframework==3.1.1
Your local.txt file should have dependenceies used for local development, such as:
EXAMPLE 5.22 -r base.txt # include the base.txt requirements file
coverage==3.7.1 django-debug-toolbar==1.3.0
The needs of a continuous integration server might prompt the following for a ci.text file:
EXAMPLE 5.23 -r base.txt # include the base.txt requirements file
coverage==3.7.1 django-jenkins==0.16.4
Prodution installations should be close to what is used in other locations, so prodution.txt commonly just calls base.txt:
EXAMPLE 5.24 -r base.txt # include the base.txt requirements file
5.5.1 Installing From Multiple Requirements Files
For local development:
EXAMPLE 5.25
. $ pip install -r requirements/local.txt
For prodution:
EXAMPLE 5.26
$ pip install -r requirements/production.txt
5.5.2 Using Multiple Requirements Files With Platforms as a Service (PaaS)
See chapter 30, Deployment: Platforms as a Service.
EXAMPLE 5.25
$ pip install -r requirements/local.txt
For prodution:
EXAMPLE 5.26
$ pip install -r requirements/prodution.txt
5.5.2 Using Multiple Requirements Files With Platforms as a Service (PaaS)
See chapter 30, Deployment:Platforms as a Service.
TIP: Pin Requirements Exactly
All the pip requirements.txt examples in this chapter are explicitly set to a package version. is ensures a more stable project. We cover this at length in subsection 21.7.2, ‘Add Package and Version Number to Your Requirements.’
5.6 Handling File Paths in Settings
If you switch to the multiple settins setup and get new file path errors to things like templates and media, don't be alarmed. This section will help you resolve these errors.
We humbly beseech the reader hardcode file paths in Django settings files. This is really bad:
BAD EXAMPLE 5.1
# settings/base.py
# Configuring MEDIA_ROOT # DON'T DO THIS! Hardcoded to just one user's preferences
MEDIA_ROOT = "/Users/pydanny/twoscoops_project/media"
# Configuring STATIC_ROOT # DON'T DO THIS! Hardcoded to just one user's preferences
STATIC_ROOT = "/Users/pydanny/twoscoops_project/collected_static"
# Configuring TEMPLATES # DON'T DO THIS! Hardcoded to just one user's preferences
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', DIRS: ("/Users/pydjannpy/twoscoops_project/templates",) } ]
The above code represents a common pitfall called hardcoding. The above code, called a fixed path, is bad because as far as you know, pydanny (Daniel Roy Greenfeld) is the only person who has set up their computer to match this path struture. Anyone else trying to use this example will see their project break, forcing to either change their directory structure(unlikely) or change the settings module to match their preference(causing problems for every else including pydanny).
Don't hardcode your paths!
To fix the path issue, we dynamically set a project root variable intuitively named BASE_DIR at the top of the base settigns module. Since BASE_DIR is determined in relation to the location of base.py, your project can be run from any location on any development computer or server.
We find the cleanest way to set a BASE_DIR-like setting is with Unipath (http://pypi.python.org/pypi/Unipath/)[http://pypi.python.org/pypi/Unipath/], a Python package that does elegant, clean path calculations:
EXAMPLE 5.27
# At the top of settings/base.py
from unipath import Path
BASEDIR = Path(_file).ancestor(3)
MEDIA_ROOT = BASE_DIR.child("media") STATIC_ROOT = BASE_DIR.child("static") STATICFILES_DIRS = ( BASE_DIR.child("assert"), )
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', DIRS = (BASE_DIR.child("templates"),) } ]
If you really want to set your BASE_DIR with the Python standard library's os.path library, though, this is one way to do it in a way that will account for paths:
# At the top of setings/base.py
from os.path import join, abspath, dirname
here = lambda *dirs: join(abspath(dirname(__file__)), *dirs) BASE_DIR = here("..", "..")
root = lambda *dirs: join(abspath(BASE_DIR), *dirs)
# Configuring MEDIA_ROOT
MEDIA_ROOT = root("media")
# Configuring STATIC_ROOT
STATIC_ROOT = root("collected_static")
# Additional locations of static files
STATICFILES_DIRS = (
root("assets"),
)
# Configuring TEMPLATE_DIRS
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
DIRS = (root("templates"),)
}, ]
With your various path settings dependent on BASE_DIR, your file path settings should work, which means your templates and media should be loading without error.
TIP: How Diffent Are Your Settings From the Django Defaults?
If you want to know how things in your project differ from Django's defaults, use the diffsettings management command.
5.7 Summary
Remember, everything except for critical security related values ought to be tracked in version control.
Any project that’s destined for a real live production server is bound to need multiple settings and requirements les. Even beginners to Django need this kind of settings/requirements le setup once their projects are ready to leave the original development machine. We provide our solution, as well as an Apache-friendly solution since it works well for both beginning and advanced developers.
Also, if you prefer a different shell than the ones provided, environment variables still work. You’ll just need to know the syntax for defining them.
The same thing applies to requirements files. Working with untracked dependency differences increases risk as much as untracked settings.