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>