views:

119

answers:

1

I am trying to write a set of template tags that allow you to easily specify js and css files from within the template files themselves. Something along the lines of {% requires global.css %}, and later in the request, {% get_required_css %}.

I have this mostly working, but there are a couple of issues. We'll start with the 'timing' issues.

Each template tag is made up of two steps, call/init and render. Every call/init happens before any render procedure is called. In order to guarantee that all of the files are queued before the {% get_required_css %} is rendered, I need to build my list of required files in the call/init procedures themselves.

So, I need to collect all of the files into one bundle per request. The context dict is obviously the place for this, but unfortunately, the call/init doesn't have access to the context variable.

Is this making sense? Anyone see a way around this (without resorting to a hack-y global request object)?

Another possibility to store these in a local dict but they would still need to be tied to the request somehow... possibly some sort of {% start_requires %} tag? But I have no clue how to make that work either.

+2  A: 

I've come up with a way to do this which more suits your needs. It will have a bit more load on the server, but proper caching can help to alleviate most of that. Below I've outlined a way that should work if the CSS includes are the same for each path. You'll need to create a single view to include all of these files, but you can actually optimize your CSS using this method, making only a single CSS call for each page.

import md5
class LoadCss(template.Node):
    def __init__(self, tag_name, css):
        self.css = css
        self.tag_name = tag_name
    def render(self, context):
        request = context['request']
        md5key = md5.new(request.path).hexdigest()
        if md5key not in request.session:
            request.session[md5key] = list()
        ## This assumes that this method is being called in the correct output order.
        request.session[md5key].append(self.css)
        return '<!-- Require %s -->' % self.css
def do_load_css(parser, token):
    tag_name, css = token.split_contents()
    return LoadCss(tag_name, key)
register.tag('requires', do_load_css)

class IncludeCss(template.Node):
    def __init__(self, tag_name):
        self.tag_name = tag_name
    def render(self, context):
        request = context['request']
        md5key = md5.new(request.path).hexdigest()
        return '<link rel="stylesheet" href="/path/to/css/view/%s">' % md5key
def do_include_css(parser, token):
    return IncludeCss(token)
register.tag('get_required_css', do_include_css)

views.py:

from django.conf import settings
from django.views.decorators.cache import cache_page
import os

@cache_page(60 * 15) ## 15 Minute cache.
def css_view(request, md5key):
    css_requires = request.session.get(md5key, list())
    output = list()
    for css in css_requires:
        fname = os.path.join(settings.MEDIA_ROOT, 'css', css) ## Assumes MEDIA_ROOT/css/ is where the CSS files are.
        f = open(fname, 'r')
        output.append(f.read())
    HttpResponse(''.join(output), mimetype="text/css")

This allows you to store the CSS information in the context, then in the session, and serve the output from a view (with caching to make it faster). This will, of course, have a bit more server overhead.

If you need to vary the CSS on more than just the path, then you can simply modify the md5 lines to suit your needs. You have access to the entire request object, and the context, so almost everything should be in there.

Beware: On second review, this may cause a race condition if the browser fetches the CSS before the session has been populated. I do not believe Django works that way, but I don't feel like looking it up right now.

Jack M.
That's how I had it initially implemented, but the problem is one of ordering. With this, you *must* make sure that all of your 'requires' are parsed before the 'get_required' is parsed. This is particularly evident when an inclusion template tag is itself 'requiring' a css file. If you could actually add the files during __init__, then you get around that issue.
Jason Persampieri
I added another possible way to accomplish this which doesn't rely on the internal ordering. It's a little convoluted, but it should work.
Jack M.