Static Files in Django

Managing static files in Django seems to be one of the most confusing areas for me - it seems like it should be simple! But so often, it’s not.

Example scenarios

The default case during development

Suppose you haven’t changed any settings related to static files, and you’re running your site locally using runserver - how does that work?

You do not need to run collectstatic in this case. STATIC_URL and STATICFILES_FINDERS are important, but STATIC_ROOT is not used at all.

runserver intercepts any request starting with the value of STATIC_URL so it will be handled by a special development-only file server. STATIC_URL is probably set to "/static/", so requests starting with /static/ will go to that development file server. It in turn looks up the rest of the path the same way as collectstatic and findstatic do, and serves whatever file it finds as the response.

The most common case when deployed to production

In the typical simple production case, you’ll put a web server program like nginx listening on ports 80/443 as needed. You’ll configure it so if a request starts with STATIC_URL, then the web server will serve the file from some directory on your server machine, and otherwise, it’ll forward the request to Django for processing.

To get the static files to the directory where your web server will look for them, you’ll set STATIC_ROOT to that directory path, then run collectstatic.

At runtime, requests for static files shouldn’t ever get to Django, so is the STATIC_URL setting now irrelevant? No! The browser will only know where to get those static files based on URLs that Django puts into the web responses. And in the default case, the information used to generate those URLs comes from STATIC_URL.

Another common case for production

Another common case in production is serving the static files using a completely different kind of server. It could be a storage system like Amazon S3, or a content distribution network, but whatever it is, we need Django to copy the static files to it when we deploy, and we need Django to know how to build the URLs that browsers will use to fetch those files.

To do this, the most important setting is STATICFILES_STORAGE, which will be the package and classname of an alternative storage system class that is likely specific to the kind of static file service you are using.

As an example, the django_storages package provides a variety of storage systems that let you use Amazon S3, Azure Storage, Dropbox, FTP, Google Cloud Storage, Apache Libcloud, or SFTP.

Whatever storage system you’re using will require other settings to know where to put the files, how to authenticate to get access to write them, and so forth.

Building URLs for static files

What happens when Django is rendering a template containing something like this?:

{% load static %}
{% static "some string" %}

It renders to a URL, but where does that URL come from?

“If the django.contrib.staticfiles app is installed, the tag will serve files using url() method of the storage specified by STATICFILES_STORAGE.”

And otherwise (if staticfiles is not in INSTALLED_APPS)? The tag will just stick the value of the STATIC_URL setting in front.:

if apps.is_installed('django.contrib.staticfiles'):
    from django.contrib.staticfiles.storage import staticfiles_storage
    return staticfiles_storage.url(path)
else:
    return urljoin(PrefixNode.handle_simple("STATIC_URL"), quote(path))

collectstatic

Doc for collectstatic

The collectstatic command finds all the static files, from possibly many places, and copies them all to one place.

Finding the static files

To debug finding static files, you can use the findstatic command.

For collectstatic and findstatic, the finding of the files is controlled by the STATICFILES_FINDERS setting, which is a list of names of classes that represent different ways of looking for static files.

The default list is:

  • FileSystemFinder: Looks in each directory in the STATICFILES_DIRS setting.

  • AppDirectoriesFinder: Looks in the static directory of each app listed in the INSTALLED_APPS setting.

You might occasionally need to add to that - for example, if you’re using django-compressor - but most of the time, the default list is fine.

Copying the static files

collectstatic writes the files into the storage system specified by STATICFILES_STORAGE.

The doc for collectstatic says it writes the files to STATIC_ROOT, but that is only correct when STATICFILES_STORAGE is set to its default storage system and some others. The storage system can store them anywhere it wants.

Post-processing

After collectstatic has copied the files, it calls post_process() on the storage system class and passes the list of files. This lets the storage system do additional processing. For example, it could compress the files, make a manifest of them, compile style files, etc.

Integrating collectstatic with a CSS/JavaScript build step

The whitenoise docs suggest what seems like a reasonable approach if you need to build some of your static files before deploying or serving them.

  • Put your source files in one directory, e.g. static_src

  • Have your build step put its output in a second directory, e.g. static_build

  • Add "static_build" to STATICFILES_DIRS

Now just run your build step before collectstatic, and the built files will be collected along with the other static files.

Storage systems

Django uses storage systems to abstract the concept of storing files and being able to list and retrieve them again.

The default storage systems work with the local file system, but you can use alternatives to store files on network file services, or do additional processing on the files, for example.

Static files are managed using the storage system from the STATICFILES_STORAGE setting.

By default, this is django.contrib.staticfiles.storage.StaticFilesStorage, which stores files under the directory specified by the STATIC_ROOT setting. Other storage systems might ignore STATIC_ROOT and have their own settings.

Storage systems can override the post_process() method so that after collectstatic has copied a bunch of files into the storage system, it can do further processing on them. For example, ManifestStaticFilesStorage, which appends hashes to filenames when saving them, “automatically replaces the paths found in the saved files matching other saved files with the path of the cached copy”.

Storage system also provide a url() method that just returns a URL that the user’s browser could use to fetch the file itself. That’s what the static template tag uses to know which URL to insert into pages.

The whitenoise app

whitenoise lets Django itself serve static files in production, kind of like runserver does in development.

To install whitenoise for Django, just add "whitenoise.middleware.WhiteNoiseMiddleware" to your MIDDLEWARE immediately after SecurityMiddleware and before anything else. STATIC_URL needs to be set, as always.

You don’t have to use StaticFilesStorage for your file storage system, but you have to use something that stores the files locally at STATIC_ROOT.

It’s not a bad idea to use whitenoise in development if you’re using it in production, just to make sure things are working the same way. The way to make runserver not serve the static files itself - so that whitenoise will get to serve them - is to put "whitenoise.runserver_nostatic" at the top of INSTALLED_APPS.

Then just make sure DEBUG is on when using runserver, because that’ll result in WHITENOISE_AUTOREFRESH and WHITENOISE_USE_FINDERS both defaulting to True, which means you won’t have to run collectstatic in order for whitenoise to find your static files.

whitenoise provides a couple of alternative storage systems that optionally add compression and forever caching features.

django-compressor

django-compressor docs

django-compressor lets you compile, combine, and compress javascript and css files (or any files that compile to js and css files).

It works fairly smoothly if your static files are all on a local filesystem, including your Javascript and CSS.

Warning

django-compressor can be used with remote static files, but it’s a royal pain, and I’d recommend looking at something else in that case, if you have the option.

If settings.COMPRESS_ENABLED is False, then it will just compile the files.

If settings.COMPRESS_ENABLED is True, then it will also combine and compress the files.

Basic installation

  • pip install django_compressor

  • Add "compressor" to settings.INSTALLED_APPS

  • Add "compressor.finders.CompressorFinder" to settings.STATICFILES_FINDERS.

Invoking compress

When your site is running, django-compressor gets invoked during template rendering, anywhere that you’ve used the compress tag:

{% load compress %}

{% compress js %}
    <script type="text/javascript" src="/static/js/site-base.js"/>
    <script type="text/coffeescript" charset="utf-8" src="/static/js/awesome.coffee" />
{% endcompress %}

If COMPRESS_ENABLED is False, then it just does compilation, and that would render to something like:

<script type="text/javascript" src="/static/js/site-base.js"></script>
<script type="text/javascript" src="/static/CACHE/js/awesome.8dd1a2872443.js" charset="utf-8"></script>

If COMPRESS_ENABLED is True, then it also combines and maybe even compresses, and you’d get something like:

<script type="text/javascript" src="/static/CACHE/js/sadfiasdoifasdf.js" charset="utf-8"></script>

This can be a big improvement if you had a dozen .js files.

Offline compression

You can use offline compression to do most of the work of compilation, compression, etc at deploy time rather than on every request. You run the compress command and it looks through all the templates it can find based on TEMPLATE_LOADERS to find uses of {% compress ... %}...{% endcompress %} and computes and caches how it would render each occurrence.

This won’t work very well if there’s dynamic content inside {% compress ...%}...{% endcompress %}. You can try to work around it using COMPRESS_OFFLINE_CONTEXT, but it’s a hack. But most applications won’t have any dynamic content inside compress tags.

The compress command looks in COMPRESS_ROOT (defaults to STATIC_ROOT) for the files referred to in the templates, so you’ll need to run collectstatic before compress.

compress will write its output into the CACHE subdirectory of STATIC_ROOT. (You can change that by setting COMPRESS_OUTPUT_DIR.)

Use caching

In production with django-compressor, be sure Django is configured with a real cache backend or compressor can really slow things down.

Scenarios

The compressor docs contain tips for the most common compressor scenarios.

Miscellaneous notes

Fixing a ValueError

What if you get the error “ValueError: Missing staticfiles manifest entry for …”?

The next bit is copied direct from:

http://whitenoise.evans.io/en/stable/django.html#why-do-i-get-valueerror-missing-staticfiles-manifest-entry-for

If you are seeing this error that you means you are referencing a static file in your templates (using something like {% static "foo" %}) which doesn’t exist, or at least isn’t where Django expects it to be. If you don’t understand why Django can’t find the file you can use

python manage.py findstatic --verbosity 2 foo

which will show you all the paths which Django searches for the file “foo”.

If, for some reason, you want Django to silently ignore such errors you can subclass the storage backend and set the manifest_strict attribute to False.