Django static files

Robert Crowther Jan 2022
Last Modified: Feb 2023

A long, detailed and superficial article about ‘static’ files and how they are handled in Django. For some people, this will be plain. But a websearch will tell you that many people are lost. Not only that, but Django documentation has directions without explanation.

Intro

Static files are… in truth, definitions are vague. But Django is clear—‘static’ files are not Django code, they are data delivered by a server in support of web activity. They include CSS files, Javascript files, and some image files such as logos or icons.

Related to static files are ‘media’ files. ‘media’ files are similar to ‘static’ files in that they are not code but support a website. But ‘media’ files are uploaded by users. Some of the comments made about ‘static’ files relate to ‘media’ files.

Why static files are special

Most web frameworks try to split static files from the main body of code. In practice, if you have the gear, you will place static files on a separate server. This is because the server can be tuned to serve the files fast. Also, a separate server reduces some risks. The muddle of concerns between serving files and running code can make holes in security. Frankly, OWASP says the top risk for websites is an XML attack, but this is another way in.

Why am I wading through this introductory mud? Because here is the usually‐sober Django documentation,

[if you use our development app django.contrib.staticfiles] This method is grossly inefficient and probably insecure, so it is unsuitable for production.

Not a word of explanation.

The second reason I wade through mud is because Django makes a big deal from static files.

The Django solution

Static files are managed by code called django.contrib.staticfiles. Almost alltimes, this code is installed.

The files go in a directory/folder. Since Django is not tied to a server hierarchy, this means the directory can have any name and be placed anywhere. But it is usually configured in ‘settings.py’ and called ‘static’,

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'

You probably read that in documentation and webposts. But that’s not the end of it. It’s not even the start.

The problem with placing static files

Consider this. You write a groovy widget—it shows the time by signs of the zodiac. For it’s display the widget uses Javascript and icons. You need to distribute these files with the widget. How will you do that?

For distribution purposes, the files need to be packaged with the app. But this is not where we would like static files to be placed—we would like them gathered in one directory. There they can be served through one URL and, even better, from another server.

Now, some web frameworks have no answer for this. They leave static files in the apps. Or ask for manual moves. Some frameworks have code tricks to transfer app files to a central ‘static’ directory. Django’s solution is a variation of this.

Django solution 1—moving the files

This is the Django method—when you distribute files with an app, you put them in a nested directory called ‘static’. This may seem ineffective. What if a project uses ten of these apps? The project has CSS, Javascript and images spread everywhere.

This is where django.contrib.staticfiles kicks in. It has a management command,

python manage.py collectstatic

This visits all the directories in a project called ‘static’, gathers the contents, then dumps them in STATIC_ROOT.

The collectstatic command

This sounds like a radical action. It is. You need to know,

The action is a copy

Original files are not moved, thank goodness

The central directory, /static, may not exist

You may or may not have noted this. Anyway, it means you can undo collectstatic by deleting the directory

Every app has a directory in the central /static folder

…and that is the reason why it is good to nest a directory with the application name inside an application’s /static directory

Now, at least, you know you can undo this action. But I’d want to know about,

Appname directories nested in /static directories

You are making an app. It has a directory ‘static/’. Django documentation recommends you nest a directory inside ‘static/’ with the app name. Let’s say the app is called ‘zodiac’. The structure is,

zodiac
|_  static
    |_  zodiac
        |_ aquarius.png

Not DRY or intuitive, but there is a reason. When dumped by collectstatic, the data is namespaced by the extra appname directory,

project
|_  static
    |_  zodiac
        |_ ...
    |_  another appname 1
        |_ ...
    |_  another appname 2
        |_ ...
    ...

You see that collectstatic is not clever. As collectstatic visits every app, it could record the app name then, when files are moved to the central directory, it could nest an appname directory automatically. But no, collectstatic doesn’t record or think. It scrapes the directories, then dumps the results (for what it is worth, I think it is the right decision not to make the code clever).

Right, we know what the problem was. Directories that are scattered for packaging need to be gathered for deployment. And we see Django solve this by asking you to manually collect the files. Not the only solution, but direct.

But there is more to say. Because how does any of this work, in practice?

How collectstatic rewrites URLs

Yes, there is an issue. If I move files on a server, how do I link to the files? How will forms know where my ‘zodiac’ files are? The last thing we need is for the URLs to change. We don’t want to write in development,

http://host/zodiac/static/zodiac/aquarius.png

Then in deployment, write,

http://host/static/zodiac/aquarius.png

That would mean changing every static URL on the website. It would mean changing the page templates. Yes, it could be done with a function, but surely is best avoided. This is the reason some web apps either decide on gathering static files, or don’t gather at all.

Let’s start with the files in the apps.

App based URLs

Let’s say we have,

zodiac
|_  static
    |_  zodiac
        |_ aquarius.png

You can’t get at that file using a simple URL‐as‐directory‐path. This fails,

http://host/zodiac/static/zodiac/aquarius.png

But would we ever want a URL like that? It is specific to the app. It’s first section is ‘zodiac’. We would want a more general URL such as,

http://host/static/zodiac/aquarius.png

If that represented a directory structure, which it would after a run of collectstatic, then that would be what we would use on a deployed website.

Django solution 2—middleware

Now here is a joy. In a Django environment running django.contrib.staticfiles, the general URL,

http://host/static/zodiac/aquarius.png

will work. It will serve static files wherever they are, in app or central directories. What’s happening?

Answer, django.contrib.staticfiles is rewriting URLs. django.contrib.staticfiles grabs requests for static files. It understands that static files may not be gathered. So it checks in the central ‘static’ directory. If that fails it hunts for ‘static’ directories in apps. If it finds a filename that matches, it serves. Bingo.

As an aside, django.contrib.staticfiles can be configured to search in any places identified in settings.py by STATICFILES_DIRS and STATICFILES_FINDERS.

This is why the documentation says django.contrib.staticfiles is “grossly inefficient”. A bunch of Python code (very slow) seeks (an unnecessary operation) the existence of files (slow and unpredictable code). The fastest server you have can not make that fast. But it is what we need on a development server. It means, even if files are moved, development projects can use the same URLs as deployed projects.

You’d think that was all to static file handling in Django, but there is more.

The template tag

There’s a template tag associated with static file handling. This is less important than it seems. What it does is prepend STATIC_URL to it’s parameter,

{% load static %}
<img src="{% static "my_app/example.jpg" %}" alt="An image">

usually becomes,

<img src="static/my_app/example.jpg" alt="An image">

The tag is not there to do major URL rewriting or anything like that. It’s only there to reflect a potential settings change. Indeed, if you know your directory will be called ’static/’, and will stay as ’static/’, then these links could be hard‐coded.

Addressing static files in code

How do you create URLs to static files in code? Somewhat wildly, by using,

from django.templatetags.static import static

# prefix base_url with the STATIC_URL
static_path =  static(base_url)

With this information you can make sense of a piece of code that is inside django.forms.widgets.Media (and I’ve seen duplicated in apps),

from django.templatetags.static import static

if path.startswith(('http://', 'https://', '/')):
    return path
return static(path)

In other words, if the given URL is a full URL then use it, but if it is a relative URL, assume a static resource and prefix with the settings in the project.

Implications and visitations

So what does it mean if an application says it is ‘statically‐aware’? It means something. It means the app has placed static files in an embedded /static folder, and that whenever static files are referenced in templates or code they use the ‘static’ template tag.

Related notes

Media

There’s another question you may ask—what about ‘/media’? Are uploaded files ‘static’? Well, yes, they are served directly and contribute to a site. But no, they are user uploads, and they go in a central directory, so do not have the distribution and URL issues of the files we have been discussing.

If there are images that are static, such as site logos, they can be placed in a static directory somewhere, and addressed like any other static file.

Serving files from arbitrary folders

A confusing note that if you do not use django.contrib.staticfiles, or need to serve files from somewhere, there is Django code to do that. Serving files can be useful e.g. for media uploads. To point at media uploads, so you can view them on a URL, do this,

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Last Thought

I think Django does a good job on static file management. But the documentation is weird.

References

Django documentation, start page,

https://docs.djangoproject.com/en/3.1/howto/static-files/

People have issues,

https://stackoverflow.com/questions/14799835/django-static-files-results-in-404/