Django Tip: Application-Specific Templates

Today I have another Django goodie to share with you. For the past few days, I've been struggling to come up with a way to only load certain Django template tag libraries when a particular Django application is installed. There may well be other, more elegant solutions for this particular problem, but it can't hurt to add my findings to the pile.

We have several templates which need to display certain information only when a particular application (Satchmo, in this case) is installed in the site. A lot of these templates are global for our 100+ Django-powered sites, such as customized admin templates and the like. It's much easier for us to maintain our code this way, as opposed to overriding templates on a per-site basis.

The Problem

We have created template tags which allow us to render "Content A" if application foo is installed or render "Content B" if foo is not installed. This works great all the way up until you need to use template tags that are specific to foo. The reason for this is that all of the template nodes appear to be parsed before they're actually rendered. That means that if foo is not installed and one of your templates included a template tag from foo's template tag library, Django will complain because it cannot find that tag (since foo is not in your settings.INSTALLED_APPS).

I investigated several possible solutions to this, including a custom loadifapp tag. The idea was to only load a template tag library if the specified application exists in settings.INSTALLED_APPS. This proved to be an interesting and very, very hacky endeavor. In the end it didn't work, and it was taking much too long to get anywhere useful.

The Solution

The solution I came up with for this situation is to create an additional include tag. I basically copied the include tag from Django itself and hacked it a bit. The result:

from django import template
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_app
from django.template.loader_tags import ConstantIncludeNode, IncludeNode

register = template.Library()

def do_include_ifapp(parser, token):
    """
    Loads a template and renders it with the current context if the specified
    application is in settings.INSTALLED_APPS.

    Example::

        {% includeifapp app_label "foo/some_include" %}
    """
    bits = token.split_contents()
    if len(bits) != 3:
        raise TemplateSyntaxError, "%r tag takes two argument: the application label and the name of the template to be included" % bits[0]

    app_name, path = bits[1:]
    app_name = app_name.strip('"\'')
    try:
        models = get_app(app_name)
    except ImproperlyConfigured:
        return template.Node()

    if path[0] in ('"', "'") and path[-1] == path[0]:
        return ConstantIncludeNode(path[1:-1])
    return IncludeNode(path)
register.tag('includeifapp', do_include_ifapp)

The magic here is the return template.Node() if Django cannot load a particular application. This makes it so the template you would be including will not be parsed, and the invalid template tag errors disappear!

To use this tag in your Django-powered site, simple plug it into one of your template tag libraries and do something like this:

{% extends 'base.html' %}
{% load our_global_tags %}

{% block content %}
<h2>Global Content Header</h2>
Bla bla

{% includeifapp foo 'foo_specific_junk.html' %}
{% endblock %}

And within foo_specific_junk.html you would load whatever template tag libraries you need that would break your templates without foo being installed. This tag should work for any application. I would be interested to hear what you use it for in the comments!

Comments

Comments powered by Disqus