Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Django template syntax in a css file

I have a css file home_global.css which has the following in it:

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

This static file is loaded using:

<link rel="stylesheet" href="{% static 'citator/home_global.css' %}" type="text/css">

However the url of the background-image, expectedly, doesn't resolve but is parsed literally. What I would like to do is enable Django template syntax in the css file.

Please note that the static URLs etc are all set up correctly and this question doesn't involve that.

This question is very similar to another question that I myself asked a month ago: How to use django template syntax in static files

However, the answer provided there was specific to javascript, and in particular noted that "The fundamental issue that prevents this is that context is passed to the template that is mentioned in your render() function in the view(or any other function the behaves the same way e.g. render_to-response())."

If I understand this correctly, the same limitation does not apply here. Furthermore, I've subsequently learned from the Django documentation that it is possible to use Django template syntax in a variety of text documents. It therefore seems to me that in this case, where I want to use it in a css file, this should be possible. So, how can I do this?

like image 386
Neil Avatar asked Mar 13 '19 14:03

Neil


Video Answer


3 Answers

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">
    ...
like image 110
Mario Orlandi Avatar answered Oct 29 '22 23:10

Mario Orlandi


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.

like image 29
Neil Avatar answered Oct 29 '22 21:10

Neil


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>
like image 38
jax Avatar answered Oct 29 '22 21:10

jax