Use Django template syntax in a css file

Okay well after a month and a half I decided to just go through the docs and see whether I could figure this one out myself. And I have a working solution. Here's how to do it.

STEP 1: add some directories and setup some new variables in settings.

First, I assume that you already have a directory structure like this for static files:

/<app_name>/static/<app_name>

Add another sub-directory called "templates". This is where we will make our templates. This is not where they will be saved. So you should now have this:

/<app_name>/static/<app_name>/templates

In this directory, add your css template. Here's my example, saved as "test.css":

{% load static %}
body {
    background-image: url("{% static 'citator/citator.jpg %}");
}

Now add these variables to settings. Note, I only have one app. If you have more, find a more manageable way of doing this:

APP_NAME = "<your app's name>"
STATIC_TEMPLATE_PATH = os.path.join(BASE_DIR, APP_NAME, "static", APP_NAME, "templates")

Next we need to write a custom command that will run the Django Template Engine on these templates. To do this, navigate to:

/<app_name>/management/commands

If you don't have these directories, make them.

Then you need to make a file, call it something like "render_static_templates.py", with this in it, in this directory:

from django.core.management.base import BaseCommand 
from django.template import engines
from <project_name>.settings import STATIC_TEMPLATE_PATH, APP_NAME
import glob 
import os

def find_templates(stem: str):
    """
    Custom template finder that just looks in 
    your new static templates directories for templates
    """
    leaf = "*"
    path = os.path.join(stem, leaf)
    all_templates = glob.glob(path)
    return all_templates


class Command(BaseCommand):
    def handle(self, *args, **options):

        # Get the file paths for all our new static templates:    
        all_templates = find_templates(STATIC_TEMPLATE_PATH)

        # See docs: https://docs.djangoproject.com/en/2.1/topics/templates/    
        django_engine = engines['django']

        for path in all_templates:
            template = django_engine.get_template(path)
            # Debug if you want. The URL should now be resolved in this print output.
            print(template.render())

            # Now we need to save the RENDERED template somewhere. I save it up one level from the templates folder, so that collectstatic can find it. You can put it elsewhere.
            leaf = path.split("/")[-1]
            save_path = os.path.join(APP_NAME, "static", APP_NAME, leaf)
            # Debug
            print(save_path)

            with open(save_path, "w") as f:
                f.write(template.render())

What you do next is run this new management command:

python manage.py render_static_templates

You should now be able to see the rendered template in

/app/static/app

Next run

python manage.py collectstatic

Assuming you have your STATIC_ROOT and STATIC_URL set up correctly, you will have the rendered template moved to where it should be. In my case:

/static/<app_name>

From there, the RENDERED template will be served using whatever you use to serve. (I use whitenoise). And if all has gone well, you should see a background image on your page!

Possible improvements: - Better directory structures. - Integrating into the "collect_static" command.


As you correctly pointed out, Django templates can be used for any text file, not just HTML. However, you need to make sure they're rendered by the template engine, providing a specific url and a view.

That way, you can expect to have all variables and tags interpolated, and in particular to have "static" replaced by settings.STATIC_URL. However, I wouldn't insist in prepending "/static/" to the url of the CSS file itself ... that would be cheating, as you're rather rendering the file dynamically.

In practice:

project/urls.py

from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    ...
    path('css/home_global.css', TemplateView.as_view(
        template_name='home_global.css',
        content_type='text/css')
    ),
    ...
]

The view is rather trivial, and has been inlined in urls.py. Please note I also specified the appropriate mimetype 'text/css'.

Here, I prepended a 'css/' prefix to the url, but this is not necessary, and you don't need a "css" folder in your project; just make sure that the template engine can find "home_global.css"; that is, put it in the /template/ subfolder of any installed app, or even in the project if it is listed among the installed apps:

project/templates/home_global.css

{% load static %}

body {
    background-image: url("{% static 'citator/citator.jpg' %}");
}

You can check immediately the result by navigating to this url with your browser:

http://127.0.0.1:8000/css/home_global.css

which renders the document as follows:

body {
    background-image: url("/static/citator/citator.jpg");
}

and include it in main template as required:

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="/css/home_global.css" type="text/css">
    ...

Should you need to render many css documents, it might be convenient to treat the filename as parameter, then use a single view for all documents. In this case I would opt for a function based view, for simplicity:

urls.py:

from django.urls import path
from . import views

urlpatterns = [
    ...
    path('css/<str:filename>.css', views.css_renderer),
    ...
]

where:

views.py

from django.shortcuts import render


def css_renderer(request, filename):
    return render(request, filename + '.css', {}, content_type="text/css")

and in your main template:

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="/css/home_global.css" type="text/css">
    <link rel="stylesheet" href="/css/another.css" type="text/css">
    <link rel="stylesheet" href="/css/yet_another.css" type="text/css">
    ...

Chopped off the particular part of the CSS tag and add into HTML file using style tag. A quick fix and worked for me.

main.css

  ...

#banner {
    background-color: #e5474b;
    color: #f2a3a5;
    padding: 13em 0 11em 0;
    background-color: #0c0c0c;
    background-image: url("/images/banner.jpg"); <-- Remove this part and put under html
    background-size: cover;
    background-repeat: no-repeat;
    background-position: 15% left;
    text-align: right;
    position: relative;
    z-index: 9999;
}
  ...

index.html

<head>
  ...
  <style>
    #banner {
      background-image: url("{% static 'images/banner.jpg' %}");
    }
  </style>
 ...
</head>