Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: render staticfiles through template engine at deploy-time

I want to render some static files (*.js in particularly) using Django template variables. I believe this is a common use-case, especially when doing anything AJAX-y; I don't want to hardcode AJAX urls in my .js files any more than I want to hardcode them in my .html files. Buuuut of course I don't want those static files to have to run through the template engine at every client request because this will be slow. I am referring to things like URLs (which do not change after compile/deploy) or static (non-db) model attributes. (I suppose there are use cases where these things might be changed at run-time - this is Python, after all- but I think they are uncommon). For some possible template variables (e.g. model fields), of course the file must be rendered at the time of the client request, but this is not what I'm talking about.

So wouldn't it make sense to render some of my static files through the template engine, for a subset of possible template variables, perhaps at the same time as collectstatic?

As far as I can tell this is not currently the case. To be clear, what I am looking for is a solution to render static files through the template engine at compile/deploy-time so that at "client-request-time" they are in fact plain old static files.

Such an approach would avoid these hacks:

  • DRY URLs in Django Javascript
  • Using the Django URL Tag in an AJAX Call

Disclaimers:

  • Yes I know there are template engines out there for javascript (mustache, handlebars, prototype, etc). Why should I add another template engine to the stack when Django already has one? Plus the syntax collides! That seems silly.
  • This looks like it takes a crack at it, but it's complicated and not fully implemented.

So:

  1. Is there a solution out there that I am missing?
  2. If not, is there a way to hook into collectstatic (like a pre-collectstatic hook) that would allow one to render certain static files through the template engine before "collecting" them?

EDIT: No responses yet...is this a really dumb question, and I'm missing something obvious? If so...go ahead and let me know...

like image 955
andy Avatar asked Feb 09 '14 18:02

andy


3 Answers

  1. There are several frameworks for Django for same purpose: django-pipeline, django-assets, and etc. which integrates different static files processing strategies, with varying degrees of difficulty configuring.
    I use an external tool - Grunt (it requires node.js) - for asset post-processing after collectstatic. It is easier and has a lots of plugins for any purpose (source validation, css/js/images minification, merging, testing and etc.).

  2. It is possible to hook in collectstatic by a custom static files storage with overrided post_process method.

example/settings.py

STATIC_ROOT = 'assets'
STATICFILES_STORAGE = 'example.storage.MyStaticFilesStorage'

example/storage.py

import os
from django.contrib.staticfiles.storage import StaticFilesStorage
from django.core.files.base import ContentFile
from django.template import Template, Context


class MyStaticFilesStorage(StaticFilesStorage):

    def post_process(self, paths, dry_run=False, **options):
        # don't even dare to process the files if we're in dry run mode
        if dry_run:
            return
        js_template_data = {'foo': 'bar'}  # template variables
        js_template_extension = '.jst'
        js_extension = '.js'
        for original_name, (storage, path) in paths.items():
            processed = False
            saved_name = original_name
            original_path, original_extension = os.path.splitext(original_name)
            if original_extension == js_template_extension:
               with storage.open(path) as original_file:
                  saved_name = original_path + js_extension
                  if hasattr(original_file, 'seek'):
                      original_file.seek(0)
                  original_source = original_file.read()
                  c = Context(js_template_data)
                  saved_source = Template(original_source).render(c)
                  self.delete(saved_name)
                  self.delete(original_name)
                  self._save(saved_name, ContentFile(saved_source))
                  processed = True
            yield original_name, saved_name, processed
like image 107
Evgeniy Generalov Avatar answered Nov 17 '22 10:11

Evgeniy Generalov


A completely different way to approach the problem would be to ask if you really need to get those URLs in javascript--instead, can the Javascript get the URLs from things like data attributes in your HTML?

In other words, you might have wanted:

homepage.html:

<div id="pop-up-modal">pop me up</div>

homepage.js:

$("#pop-up-modal").click(function {
  $.ajax("{% url 'some-class-name %}") 
  ...
});

When it can often be more straightforward to do something like:

homagepage.html:

<div id="pop-up-modal" data-popurl="{% url 'some-class-name' %}">pop me up</div>

homepage.js:

$("#pop-up-modal").click(function {
  $.ajax($(this).data('popurl')) 
  ...
});
like image 34
Joel Burton Avatar answered Nov 17 '22 09:11

Joel Burton


I think that django-medusa would suit your needs.

By setting up a renderer and using the disk based backend, generating the static files would be as easy as:

django-admin.py staticsitegen
like image 1
HAL Avatar answered Nov 17 '22 11:11

HAL