Django Static Files Tutorial
Tags: django, python, tutorial, beginners
Published: 2020-07-02 15:18:03

Static files are a common source of confusion for Django newcomers. The term "static files" refers to files like CSS, JavaScript, or images that do not change in a web app. They remain static. For local development, static files are served up by the local Django web server and minimal configuration is required. Django does not support serving static files in production, however, so a number of additional configurations are required to get them working.

In this tutorial, we'll look at how static files work in Django, how to configure them locally, as well as using [WhiteNoise](http://whitenoise.evans.io/en/stable/) to serve them in production.

## Local Development

When you first run `startproject` to create a new Django project, a `settings.py` file is automatically created which defaults to local development settings. These include `DEBUG` set to `True` and `DATABASES` set to SQLite. It also includes, near the bottom of the file, a solitary line for static files:

```python 
# settings.py
STATIC_URL = '/static/'
```

The `STATIC_URL` setting controls the actual URL used to locate static files. In this case, that would be `/static/`, so in local development, `127.0.0.1:8000/static/` or `localhost:8000/static/`. In production, if our website was called `example.com`, the static files would be located at `example.com/static/`.

There are two steps required to use static files locally:

* create a `static` directory
* add `{% load static %}` at the top of a template and use the `static` template tag

The `static` directory should be in the same folder as `manage.py`. Add any desired static files here. For example, you might create another folder called `css` and within it a file called `base.css`. To include this file in a template, make sure the first line of the file is `{% load static %}` and the link would look something like this: `<link href="{% static 'css/base.css' %}" rel="stylesheet">`. The `static` template tag is used variable, could also hardcode the result here. This is useful later on for production.

That's it!

## collectstatic
For production, Django looks through the entire project for static files (`STATICFILES_DIRS`) and collects all available static files via the `collectstatic` command into a dedicated directory (`STATIC_ROOT`). The _way_ in which the files are stored is dictated by `STATICFILES_STORAGE`. We therefore need to add these three additional configurations and run the `collectstatic` command in production every time there is a change to the static files.

`STATICFILES_DIRS` tells Django _where to look_ for static files in a Django project. Often, this is simply the `static` directory but there could be static directories in various apps or other locations. When the `collectstatic` command is run, Django refers to the list of directories here when creating a dedicated directory for production serving.

`STATIC_ROOT` is the _location_ of the collected static files, often named `staticfiles`.

`STATICFILES_STORAGE` is the _file storage engine_ used when collecting static files with the `collecstatic` command. By default, it is implicitly set to `django.contrib.staticfiles.storage.StaticFilesStorage`.  

In your local Django project, add these three settings to your `settings.py` file.

```python 
# settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),] # new
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # new
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' # new
```

Then run the command `python manage.py collectstatic`.

```
(env) $ python manage.py collectstatic
You have requested to collect static files at the destination
location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel:
```

Type `yes` to continue and hit the Enter key. A new `staticfiles` directory will be created which has folders for `admin` (the built-in admin has its own static files), `staticfiles.json`, and whatever directories are in your `static` folder.

If you now add a new static file to `static`, it will be available for local usage. It is only for production where the file won't be present, unless you run `python manage.py collectstatic` each and every time. For this reason, running `collectstatic` is typically added to deployment pipelines and is done by default on Heroku.

## WhiteNoise 
Even though we've configured our Django project to collect static files properly, there's one more step involved which is not included in the official Django docs. That is the configuration of [WhiteNoise](http://whitenoise.evans.io/en/stable/), radically simplified static file serving for Python web apps. Is it possible to use another package to serve Python static files? Yes. Have I ever seen it done over the last 5 years? No. Everyone uses WhiteNoise and you should, too, since Django won't serve static files in production on its own.

```
(env) $ pipenv install whitenoise==5.1.0
```

Then in the `settings.py` file, add `whitenoise` to the `INSTALLED_APPS` **above** the built-in `staticfiles` app. Under `MIDDLEWARE`, add a new `WhiteNoiseMiddleware` on the third line. And at the bottom of the file, change `STATICFILES_STORAGE` to use WhiteNoise. It should look like the following:

```python
# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'whitenoise.runserver_nostatic', # new
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', # new
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

...

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # new
```

That's it! Run `python manage.py collectstatic` again so that the files are stored using WhiteNoise. And then deploy with confidence to the hosting platform of your choice.