Django like all modern application development frameworks requires that you eventually manage tasks to support the core operation of a project. This can range from efficiently setting up a Django application to run in the real world, deploying a Django application to a cloud provider, to managing an application's static resources (e.g. CSS, JavaScript, image files).

In addition, other routine application management tasks can include: establishing a logging strategy to enforce problem detection; setting up email delivery for application users and/or administrators; as well as debugging tasks to inspect the outcome of complex operations. In this chapter, you'll learn about these and other common topics associated with Django application management.

Django settings.py for the real world

The settings.py is the central configuration for all Django projects. In previous chapters you already worked with a series of variables in this file to configure things like Django applications, databases, templates and middleware, among other things.

Although the settings.py file uses reasonable default values for practically all variables, when a Django application transitions into the real world, you need to take into account a series of adjustments, to efficiently run the Django application, offer end users a streamlined experience and keep potentially rogue attackers in check.

Switch DEBUG to False

One of the first things that's necessary to launch a Django application into the real world is to change the DEBUG variable to False in a project's settings.py file. I've briefly mentioned in previous chapters how Django's behavior changes when switching DEBUG=False to DEBUG=True in settings.py. All these behavioral changes associated with the DEBUG variable are intended to enhance project security and performance. Table 5-1 illustrates the differences between having a project run with DEBUG=False and DEBUG=True.

Table 5-1. Django behavior differences between DEBUG=True and DEBUG=False in settings.py

Functionality DEBUG=True behavior DEBUG=False behavior
Error handling and notification 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 embarrassments. Emails project administrators of errors.(See the 'Define administrators for ADMINS and MANAGERS' section in this section for more details on email notifications)
Static resources Set up by default on a project's /static/ URL for simplicity. Disables automatic set up to enhance performance and avoid security vulnerabilities. This requires dealing with static resources in a separate workflow, using one of various techniques:
  • Configuring the the same web server that serves the Django application to serve static resources.
  • Serving static resources on a separate web server, for performance reasons.
  • Serving static resources on a cloud service (e.g. AWS S3 and/or CDN [Content Delivery Network]).
  • Using a turn-key Python module like WhiteNoise to deal with static resources as part of the Django application itself.

The upcoming section Set up static web page resources -- Images, CSS, JavaScript -- has more details

Host/site qualifier 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. (See the 'Define ALLOWED_HOSTS' sub-section in this section for more details)

As you can see in table 5-1, the changes enforced by changing DEBUG=True to DEBUG=False in settings.py are intended for publicly accessible applications (i.e. production environments). You may not like the hassle of adapting to these changes, but they are enforced to maintain a heightened level of security and maintain high performance on all Django projects that run in the real world.

Define ALLOWED_HOSTS

By default, the ALLOWED_HOSTS variable in settings.py is 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), this validation is only done when DEBUG=False.

If you switch to DEBUG=False and ALLOWED_HOSTS is left empty, Django refuses to serve requests and instead responds with HTTP 400 bad request pages, since it can't validate incoming HTTP Host headers. Listing 5-1 illustrates a sample definition of ALLOWED_HOSTS.

Listing 5-1 Django ALLOWED_HOSTS definition

ALLOWED_HOSTS = [
    '.coffeehouse.com',
    '.bestcoffeehouse.com',
]

As you can see in listing 5-1, the ALLOWED_HOSTS value is a list of strings. In this case it defines two host domains, that allow 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. static.coffeehouse.com or shop.coffeehouse.com is valid for .coffeehouse.com).

If you want 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 want to bypass this security feature and accept any HTTP host value in a request -- which I don't recommend, see below -- you can define ALLOWED_HOSTS=['*'], where '*' represents a wild-card to accept any HTTP host value.

Caution Although setting ALLOWED_HOSTS=['*'] offers a quick solution, doing so raises the possibility of the rare, but possible, security vulnerability: HTTP host header attack[1].

Be careful with the SECRET_KEY value

The SECRET_KEY value in settings.py is another security related variable like ALLOWED_HOSTS. However, unlike ALLOWED_HOSTS, SECRET_KEY is assigned a default value and a very long value at that (e.g. 'django-insecure-3*w@re!%88p%w%+-3^g_z=pna5zot51cfjt4t^!6=u7sp7qo1!').

The purpose of the SECRET_KEY value is to digitally sign certain data structures that are sensitive to tampering. Specifically, Django by default uses the SECRET_KEY on sensitive data structures like session identifiers, cookies and password reset tokens. But you can rely on the SECRET_KEY value to cryptographically protect any sensitive data structure in a Django project[2].

  1. https://portswigger.net/web-security/host-header     

  2. https://docs.djangoproject.com/en/4.0/topics/signing/