Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Dynamic template content in Django fields

Jerry Stratton, September 26, 2006

I’ve been looking for a better way to manage the sprawling mess that is Negative Space for several years now. I currently use a hacked-up FileMaker Pro database, but FileMaker has over the last few revisions become more and more expensive for less and less return, and in any case it isn’t as useful now that I expect to be able to make changes automatically and remotely. The fact is, except for the parts I’ve turned into a blog I haven’t updated any part of the site for a long time.

What I’ve been looking for is something that’s as easy to use for managing a large web site but that makes use of a standard SQL database and can be managed from the command line as well as a GUI. A few weeks ago I ran across Django, and it looks like a great choice. I still don’t know much Python but even so Django was very easy to set up.

One of the things I want the new system to do is handle smart links in the manner of Denis de Bernardy’s brilliant smart link plugin for WordPress: provide a title and get back the first page, external link, or media resource that matches. This is not quite that, but it was an easy start.

Django Tags and filters

I have a special model in Django just for external links. I can explicitly attach any of the links in that model to a page through a ManyToManyField in Django’s admin view, but sometimes I want something more dynamic. I’ve planned ahead by putting tags in for just about every piece of my site, including the Links model.

In this case, what I’m going to want to do is tell Django to “give me the latest link which has this keyword attached to it”. In Django lingo, this might look like:

Our latest Strange Bedfellow Award goes out to {% key_link "Strange Bedfellow Award" %}.

Django would take “{% key_link "Strange Bedfellow Award" %}” and replace it with a link to the most recent external link tagged with that phrase.

I can add features like this to Django by extending the Django template language. It’s a simple, two-step process.

First, I created a folder called “templatetags” inside the appropriate application. In this case, I created it inside the pages application, the one that describes pages and related information. This folder must be a package, so don’t forget to add an empty “__init__.py”.

Second, I create a Python file that will contain the extensions. The name of that file is what you’ll “load” into your templates so as to make use of the extensions. I’m calling mine “crosslinks.py”, so if I want to use it in a template I’ll need a “{% load crosslinks %}” at the top of the template.

The extension file needs to import information from Django about templates, and any exceptions you may risk running into, such as not being able to find an object corresponding to the model you’re searching. In this case, those models are KeyWord and Link.

[toggle code]

  • from django import template
  • from django.core.exceptions import ObjectDoesNotExist
  • from resources.models import Link, KeyWord
  • register = template.Library()
  • #take a keyword and return the most recent link with that keyword
  • def key_link(keyword):
    • key = False
    • latestlink = False
    • try:
      • key = KeyWord.objects.get(key=keyword)
    • except ObjectDoesNotExist:
      • title = "No Keyword “" + keyword + "”"
    • if key:
      • try:
        • latestlink = Link.objects.filter(keywords=key).order_by('-added')[:1].get()
      • except ObjectDoesNotExist:
        • title = "No Link Matching “" + keyword + "”"
    • if latestlink:
      • title = latestlink.title
      • URL = latestlink.url
      • link = '<a href="' + URL + '">' + title + '</a>'
    • else:
      • link = title
    • return link
  • register.simple_tag(key_link)

In this example, KeyWord is the model that describes a tag word or phrase, and Link is the model that describes an external link. The function first looks for the KeyWord, and then searches for the most recent Link tagged with that KeyWord. For each of those two lookups, if it can’t find that key or link, it returns an appropriate error message.

register = template.Library()
First, I need to get an object from template.Library that knows how to tell Django what extensions are available.
def key_link(keyword):
Second, I need to create the function that will take the key phrase and return the most recent link tagged to that phrase.
register.simple_tag(key_link)
Finally, I need to register that function with Django.

That’s it. The key_link extension is now ready for use in templates. If I put “{% load crosslinks %}” at the top of a template (below the “extends” if this template extends another template) I can put {% key_link "some tag" %} into that template.

The View

However, this isn’t the sort of thing that belongs in a template. I’d end up having to make a new template for every page in which I make such a reference. These references need to be in the content of the pages, the part that gets stored in the database and put into the templates.

Well, we can do that. In my Page model, each page has a content field that contains the main content for the web page. I went ahead and put the load tag at the top of my web page content field and used the key_link tag within the content:

[toggle code]

    • {% load crosslinks %}
    • &lt;p&gt;Our latest Strange Bedfellow Award goes out to {% key_link "Strange Bedfellow Award" %}.&lt;/p&gt;

When I went to view the page, Django displayed the actual codes. That was expected: Django doesn’t recursively check every piece of what gets rendered into the template to see if those pieces have template tags and filters. But it does provide the tools if we want to ask it to do some extra rendering. We can, in the views.py where we send the page off to be rendered to a template, first render some part of our model as if it were a template.

I found this by browsing the template source code. While hidden away, the instructions in that file were fairly easy to follow. On my computer it was in “/Library/Python/2.3/site-packages/Django-0.95-py2.3.egg/django/template/__init__.py”.

It appeared to be, and was, a simple three-step process. I added this to the rendering function in the views.py for my pages application:

  • from django.template import Context, Template
  • content = Template(page.content)
  • emptyContext =Context({})
  • page.content = content.render(emptyContext)
  • return render_to_response('pages/index.html', {'page': page})
from django.template import Context, Template
The first thing I need to do is import the Template class so that I can render directly to it. I also need the Context class, but only so I can send an empty context to the template class.
content = Template(page.content)
In my views.py, I’m rendering a web page, and page.content contains the main content of the page being rendered. This line creates a Template instance using page.content as the template “file”.
emptyContext =Context({})
I then create an empty Context (“{}”). If you’ve played around with your views.py, you’ve seen this already: usually we include at least an instance to some object, such as “{"page": page}”. I could provide an object here if I wanted to but I don’t yet need to. The only reason I’m doing this is to provide access to my crosslinks extension, and it doesn’t currently need to know about the context (page) it is displayed within.
page.content = content.render(emptyContext)
Now, I’m telling that “template” to render itself with that empty Context. Once the template instance (“content”) renders itself, I assign it straight back to page.content. Django never knows what hit it.
return render_to_response('pages/index.html', {'page': page})
And finally, the standard render_to_response, using the new, pre-rendered page. Most likely in the “real world” you would also provide other objects in the context, such as a list of recently changed pages.

Extending Django’s template language turned out to be extraordinarily easy. There are ways to make it more difficult (you don’t have to use register.simple_tag, for example) and they’ll be useful, too.

If your admin pages are exposed to the Internet or if you let other people use them, you’ll want to be very careful about security, since allowing template tags inside of the database opens up all of the built-in template tags and filters to any user who can enter data into the pre-rendered field.

And finally, this is the first template extension I’ve written for Django, so it is entirely likely that there’s a major flaw, or a much better way of doing it. But it’s still pretty damn cool to me.

  1. <- Windows v Mac Revisited
  2. Troubleshooting Phobia ->