Aug 09

Django Template Tag Namespaces Now Possible

I've been interested in the Django template language for some time now, and I've admired much of its simplicity and extendibility. I even wrote a shell for it (screencast) and a two-phase template renderer. Having spent the time to understand how it works, I've also had my share of ideas on how to improve it (addition of "elif", mathematical operations in variable tags, namespaces). The pony that I've been wanting the most is probably namespaces. There has been talk of adding namespaces to Django templates for quite a while (including a ticket with patches and some various discussions on the mailing list (1, 2 and 3)). For years, this concept has sat dormant due to lack of discussion and interest. No pluggable solution had been offered (as far as I know), so I wrote a couple of templatetags that offer namespacing and other features while retaining backwards compatibility and not requiring a fork of Django. This code is available on Github as django-smart-load-tag.

Backwards compatibility

Django's policy is to remain backwards compatible, and the template language is certainly no exception. In order to give the "{% load %}" tag namespacing features, it needed to be extended in a way that allows current assumptions about its behavior to remain the same. In particular, the assumption that all tags will be loaded into the global namespace by default had to stay. This means that, given a template library named "lib1" containing "tag1" and "tag2", the following code must work:

{% load lib1 %}
{% tag1 %}
{% tag2 %}

Current proposals have suggested the backwards-incompatible syntax that assumes namespaces are on by default:

{% load lib1 %}
{% lib1.tag1 %}
{% lib1.tag2 %}

In my implementation, "load" works the same (as in the top example), but has a few keywords that control its behavior. For example, to load a library into a namespace, use "into":

{% load lib1 into lib1 %}
{% lib1.tag1 %}
{% lib1.tag2 %}

Other features

To load a specific tag (optionally renaming it with the "as" keyword):

{% load lib1.tag1 as my_tag %}
{% my_tag %}

Loading from a specific app can be done using the "from" keyword:

{% load lib1 from app1 into app1 %}
{% load lib1 from app2 into app2 %}
{% app1.tag1 %}
{% app2.tag1 %}

To make everybody happy

It has been suggested to write a separate "{% import %}" tag in order to enable namespaces by default while retaining backwards-compatibility with existing Django applications. I've also experimented with the following import syntax, and it's also included in django-smart-load-tag:

{% import lib1 %}
{% lib1.tag1 %}

Its namespace-on-by-default design can be subverted using "* from":

{% import * from lib1 %}
{% tag1 %}

The "as" and "from" keywords are also implemented:

{% import lib1 as my_lib %}
{% my_lib.tag1 %}

{% import lib1 from app1 %}
{% lib1.tag1 %}

Where to go from here

If template tag namespaces are to be accepted as a core part of Django, some discussion will need to take place on what is the most correct solution for moving forward. Your comments here or on the mailing list can make a difference, and with enough contribution from the community, perhaps all my ponies will one day run free.

(Source and documentation available here.)

Tags: django, template