You wan't to make your project's
settings.py flexible enough to run on various environments and add the minimum set of variables to run in the real world.
settings.py switch the
DEBUG variable to
FALSE. Define the
ALLOWED_HOSTS variable to include a host value or list of host values that reflect the domain or domains an application will run as. Be careful of exposing the
SECRET_KEY variable as it can lead to security exploits. If you suspect the
SECRET_KEY variable has been compromised, change it immediatly. Define the
MANAGERS variables with a list of tuple values. Each tuple should include a name & email.
settings.py variables associated with directories to use dynamically determined absolute paths using
os.path.abspath. Then substitute the variables in Django variables that require absolute directories (e.g.
If you want to use a single
settings.py file for multiple environments, read the host name of the server at the top of
settings.py. Based on the host name, define a control variable (e.g.
DJANGO_HOST) to load different environment variables based on a server's host name. You can also use
settings.py to read configuration variables from files that use a data structure similar to the one used in Microsoft Windows INI files.
If you want to use multiple configuration files based on Django's configuration syntax, create multiple files named differently than the default
settings.py file. To configure Django to load variables from a file not named
settings.py, set the OS environment variable
One of the first things that's necessary to launch a Django application into the real world is switch the
DEBUG variable to
False. I've already mentioned in a couple of previous recipes how Django's behavior changes when switching
DEBUG=True. All these behavioral changes associated with changing the
DEBUG variable have the intent of enhancing a project's security. Table 1 illustrates the default differences between switching
|Error handling and notification|
(See the 'Define administrators for
|Displays full stack of errors on request pages for quick analysis||Displays default 'vanilla' or custom error pages without any stack details to limit security threats or embarrasments.|
Also emails project administrators of errors.
|Set up by default on a project's ||Disables automatic set up to avoid security vulnerabilities and requires consolidation on a separate directory to run static resources on a separate web server.|
(See the 'Define
|Requests for all hosts/sites are accepted for processing||It's necessary to qualify for which hosts/sites a project can handle requests. If a site/host is not qualified, all requests are denied.|
As you can see in table 1, the changes enforced by changing
DEBUG=False are all associated with a more secure Django project intended for the public (i.e. a production environment). You may not like the hassle of adapting and re-testing to these changes, but all these changes are enforced to maintain a heightened level of security on all Django projects that run in the real world.
By default, the
ALLOWED_HOSTS variable is present in
settings.py but it's empty. The purpose of
ALLOWED_HOSTS is to validate a request's (HTTP) Host header. Validation is done to prevent rogue users from sending fake HTTP Host headers that can potentially poison caches and password reset emails with links to malicious hosts. Since this issue can only present itself under an uncontrolled user environment (i.e. public/production servers), validation is only done when
If you switch to
ALLOWED_HOSTS is left empty, Django will refuse to serve requests and instead report HTTP 400 bad request pages because it can't validate incoming HTTP Host headers against any allowed value. Listing 1 illustrates a sample definition of
As you can see in listing 1, the
ALLOWED_HOSTS value is a list of strings. In this case we define two host domains, which permits
bestcoffeehouse.com to act as an alias of
coffeehouse.com. The leading
.(dot) for each domain indicates a sub-domain is also an allowed host domain (e.g.
shop.coffeehouse.com is valid for
If you wanted to accept a single and fully qualified domain (FQDN) you would define
ALLOWED_HOSTS=['www.coffeehouse.com'], which would only accept requests with an HTTP host
www.coffeehouse.com. In a similar fashion, if you wanted to accept any HTTP host -- effectively bypassing the verification -- you would define
ALLOWED_HOSTS=['*'] which indicates a wildcard.
SECRET_KEY is another security related variable like
ALLOWED_HOSTS. However, unlike
SECRET_KEY is assigned a default value and a very long value at that (e.g. 'oubrz5ado&%+t(qu^fqo_#uhn7*+q*#9b3gje0-yj7^#g#ronn').
The purpose of the
SECRET_KEY value is to digitally sign certain data structures that are sensitive to tampering. Specifically, Django uses the
SECRET_KEY on things like session identifiers, cookies and password reset tokens, among other things.
The one thing these signed data structures with
SECRET_KEY have in common is they're sent to users on the wider Internet and are then sent back to the application to trigger actions on behalf of users. It's in this scenario we enter into a trust issue. Can the data sent back to the application be trusted ? What if a malicious user modifies his cookie to try and hijack another user's data ? This is what digitally signed data prevents.
Before Django sends any of these sensitive data structures to users on the Internet, it signs them with a project's
SECRET_KEY. When the data structures come back to fulfill an action, Django re-checks these sensitive data structures against the
SECRET_KEY again. If there was any tampering on the data structures, the signature check fails and Django stops the action.
The only remote possibility a rogue user has of successfully pulling an attack of this kind is if the
SECRET_KEY is compromised -- since an attacker can potentially create an altered data structure that will match against a project's
SECRET_KEY. Therefore you should be careful about exposing your project's
SECRET_KEY. If for any reason you suspect your project's
SECRET_KEY has been compromised you should replace it immediately -- only a few ephemeral data structures (i.e. sessions, cookies) become invalid with this change, until users re-login again and the new
SECRET_KEY is used to re-generate these data structures.
Once a Django project is made accessible to end users, you'll want some way to receive notifications of important events related to security or other critical factors. Django has two sets of administrative groups defined in
MANAGERS. By default, both
MANAGERS are assigned an empty tuple. The values assigned to both variables also need to be tuples, where the first value of the tuple is a name and the second part of the tuple is an email. Listing 2 shows a sample definition of
As you can see is listing 2, the
ADMINS variable is assigned two tuples with different administrators. Next, you can see the
ADMINS value is assigned to the
MANAGERS variable. You can of course define different values for
MANAGERS using the same syntax as
ADMINS, but in this case I just gave both variables the same values for simplicity.
The purpose of having these two administrative groups in
settings.py is for Django to send email notifications of project events. By default, these events are limited and happen under certain circumstances. After all, you don't want to send administrators 10 email notifications every minute 24/7.
ADMINS are sent email notifications of errors associated with the
django.security packages, if and only if
DEBUG=False. This is a pretty narrow criteria, as it's intended to notify only the most serious errors -- for requests and security -- and only for production environments which is when
DEBUG=False. For no other events or conditions are the
ADMINS notified by email.
MANAGERS are sent email notifications of broken links (i.e. HTTP 404 page requests), if and only if
DEBUG=False and the Django middleware
django.middleware.common.BrokenLinkEmailsMiddleware is enabled. Because HTTP 404 page requests aren't a serious problem, by default
BrokenLinkEmailsMiddleware is disabled. This is an even narrower criteria than for
ADMINS, because irrespective of a project being in development (
DEBUG=True) or production (
BrokenLinkEmailsMiddleware class needs to be added to
MANAGERS to get notifications. For no other events or conditions are the
MANAGERS notified by email.
Now that you know the purpose of
MANAGERS, add users and emails as you see fit to your project. Remember that you can always leverage the values in
MANAGERS for other custom logic in a Django project (e.g. notify administrators of user sign ups).
| Modify LOGGING values to stop email notifications of errors to |
By default, users in
To stop email notifications of errors to
There are some Django variables in
settings.py that rely on directory locations, such is the case for
STATIC_ROOT which defines a consolidation directory for a project's static files or the
DIRS list of the
TEMPLATES variable which defines the location of a project's templates, among other variables.
The problem with variables that rely on directory locations is that if you run the project on different servers or share it with other users, it can be difficult to keep track or reserve the same directories across a series of environments. To solve this issue you can define variables to dynamically determine the absolute paths of a project. Listing 3 illustrates a Django project directory structure, deployed to the
/www/ system directory.
Typically a Django
settings.py file would define the values for
TEMPLATE_DIRS as illustrated in listing 4.
settings.pywith absolute path values
The issue with the setup in listing 4 is if you deploy the application to another server in which you can't deploy the application to
/www/ (e.g. due to restrictions or a Windows OS where directories start with a leading letter C:/) it becomes difficult to deploy the application.
An easier approach illustrated in listing 5 is to define variables to dynamically determine the absolute paths of a project.
settings.pywith dynamically determined absolute path
The variables defined at the top of listing 5 rely on the Python
os module to dynamically determine the absolute system path relative to the
PROJECT_DIR=os.path.dirname(os.path.abspath(__file__)) statement gets translated into the
/www/STORE/coffeehouse/ value, which is the absolute system directory of files like
settings.py. To access the parent of
/www/STORE/coffeehouse/ we simply wrap the same statement with another call to
os.path.dirname and define the
BASE_DIR variable so it gets translated into the
The remaining statements in listing 5 use standard Python string substitution to use the
BASE_DIR to set the absolute paths in the
TEMPLATE_DIRS variables. In this manner you don't need to hard code the absolute paths for any Django configuration variable, the variables automatically adjust to any absolute directory irrespective of the application deployment directory.
In every Django project you'll eventually come to the realization that you have to split
settings.py into multiple environments or files. This will be either because the values in
settings.py need to change between development and production servers, there are multiple people working on the same project with different requirements (e.g. Windows and Linux) or you need to keep sensitive
settings.py information (e.g. passwords) in a local file that's not shared with others.
In Django there is no best or standard way to split settings.py into multiple environments or files. In fact, there are many techniques and libraries to make a Django project run with a split
settings.py file. Next, I'll present the three most popular options I've used in my projects. Depending on your needs you may feel more comfortable using one option over another or inclusively mixing two or all three of these techniques to achieve an end solution.
settings.py file is read as an ordinary Python file, so there's no limitation to using Python libraries or conditionals to obtain certain behaviors. This means you can easily introduce a control variable based on a fixed value (e.g. server host name) to conditionally set up certain variable values. For example, changing the DATABASES variable -- because passwords and the database name change between development and production -- changing the EMAIL_BACKEND variable -- since you don't need to send actual emails in development as you do in production -- or changing the CACHES variable -- since you don't need a cache to speed up performance in development as you need in production.
Listing 6 illustrates the setup of a control variable called
DJANGO_HOST based on Python's
socket module, the variable is then used to load different sets of Django variables based on a server's host name.
The first line in listing 6 imports the Python
socket module to gain access to the host name. Next, a series of conditionals are declared using
socket.gethostname() to determine the value of the control variable
DJANGO_HOST. If the host name starts with the letters
DJANGO_HOST variable is set to
"production", if the host name starts with
DJANGO_HOST is set to
"testing" and if the host name starts with neither of the previous options then
DJANGO_HOST is set to
In this scenario, the string method
startswith is used to determine how to set the control variable based on the host name. However, you can just as easily use any other Python library or even criteria (e.g. IP address) to set the control variable. In addition, since the control variable is based on a string, you can introduce as many configuration variations as needed. In this case we use three different variations to set
settings.py variables -- "production","testing" and "development" -- but you could easily define five or a dozen variations if you require such an amount of different set ups.
Another variation to split
settings.py is to rely on Python's built-in
configparser allows Django to read configuration variables from files that use a data structure similar to the one used in Microsoft Windows INI files. Listing 7 illustrates a sample
As you can see in Listing 7, the format for a
configparser file is structured in various sections declared between brackets (e.g.
[databases]) and below each section are the different keys and values. The variables in Listing 7 represent a production environment placed in a file named
production.cfg. I chose the
.cfg extension for this file, but you could use the
.ini extensions if you like, the extension is irrelevant to Python, the only thing that matters is the data format in the file itself.
Similar to the contents in
production.cfg, you can create other files with different variables for other environments (e.g.
development.cfg). Once you have the
configparser file or files, then you can import them into a Django
settings.py. Listing 8 shows a sample
settings.py that uses values from a
As you can see in Listing 8,
configparser is loaded into Django via
django.utils.six.moves, which is a utility to allow cross-imports between Python 2 and Python 3. In Python 2 the
configparser package is actually named
ConfigParser, but this utility allows us to use the same import statement using either Python 2 and Python 3. See the Install Django recipe and side-note 'Django and Python 3' for more details on six and using Django with Python 3. After the import, we use the
SafeConfigParser class with the argument
allow_no_value=True to allow processing of empty values in
Then we rely on the same prior technique using Python's
socket module to gain access to the host name and determine which
configparser file to load. The
configparser file is loaded using the
read method of the
SafeConfigParser instance. At this juncture all
configparser variables are loaded and ready for access. The remainder of listing 8 shows a series of standard Django
settings.py variables that are assigned their value using the
get method of the
SafeConfigParser instance, where the first argument is the
configparser section and the second argument is the key variable.
So there you have another option on how to split the variables in
settings.py into multiple environments. Like I mentioned at the start, there's no best or standard way of doing this. Some people may like
configparser better because it splits values into separate files and avoids the many conditionals of option 1, but other people may hate
configparser because of the need to deal with the special syntax and separate files. Choose whatever feels best for your project.
settings.pyfiles with different names for each environment
Finally, another option to split Django variables into multiple environments is to create multiple
settings.py files with different names. By default, Django looks for configuration variables in the
settings.py file in a project's base directory. However, it's possible to tell Django to load a configuration file with a different name. Django uses the OS variable
DJANGO_SETTINGS_MODULE for this purpose. By default, Django sets this OS variable to
<project_name>.settings in the
manage.py file located in the base directory of any Django project. And since the
manage.py file is used to bootstrap Django applications, the
DJANGO_SETTINGS_MODULE value in this file guarantees configuration variables are always loaded from the
settings.py file inside the
So let's suppose you create different
settings.py files for a Django application -- placed in the same directory as
settings.py -- named
development.py. You have two options to load these different files. One option is to change the
DJANGO_SETTINGS_MODULE definition in a project's
manage.py file to the file with the desired configuration (e.g.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coffeehouse.production" to load the
production.py configuration file). However, hard coding this value is inflexible because you would need to constantly change the value in
manage.py based on the desired configuration. Here you could use a control variable in
manage.py to dynamically determine the
DJANGO_SETTINGS_MODULE value based on a host name -- similar to the process described in the previous option 1 for
Yet another possibility to set
DJANGO_SETTINGS_MODULE without altering
manage.py is to define
DJANGO_SETTINGS_MODULE at the operating system level so it overrides the definition in
Listing 9 illustrates how to set the
DJANGO_SETTINGS_MODULE variable on a Linux/Unix OS so that application variables in the
testing.py file are used instead of the
testing.pyand not the default
In listing 9 we use the standard Linux/Unix syntax
export variable_name=variable_value to set an environment variable. Once this is done, notice the Django application started with the development server displays the start-up message "
using settings 'coffeehouse.testing'".
If you plan to override the
DJANGO_SETTINGS_MODULE at the OS level to load different Django application variables, be aware that by default OS variables aren't permanent or inherited. This means you may need to define the
DJANGO_SETTINGS_MODULE for every shell from which you start Django and also define it as a local variable for run-time environments (e.g. Apache).