Dynamic template content in Django fields
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 + "”"
-
try:
-
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 %}
- <p>Our latest Strange Bedfellow Award goes out to {% key_link "Strange Bedfellow Award" %}.</p>
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.
- Django
- “Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.” Oh, the sweet smell of pragmatism.
- Django template language for Python programmers
- “This document explains the Django template system from a technical perspective—how it works and how to extend it.”
- django/template/__init__.py
- “The Template class is a convenient wrapper that takes care of template compilation and rendering.”
- Negative Space
- Books, games, and the politics of prohibition. Special pages include Tools for Comic Creators, Cerebus the Gopher, Neon Alley, Jerry's Diner, Highland Games, Strange Bedfellows, FireBlade Coffeehouse, Oscar Wilde, and Alexandre Dumas. And many, many more!
- Smart link plugin for WordPress
- “The smart link plugin for WordPress lets you insert links in your posts using natural language rather than urls. The resulting increase in usability is positively spectacular. Such, in fact, that you might never want to insert an html anchor again.”
More Django
- Converting an existing Django model to Django-MPTT
- Using a SQL database to mimic a filesystem will, eventually, create bottlenecks when it comes to traversing the filesystem. One solution is modified preordered tree traversal, which saves the tree structure in an easily-used manner inside the model.
- Two search bookmarklets for Django
- Bookmarklets—JavaScript code in a bookmark—can make working with big Django databases much easier.
- Fixing Django’s feed generator without hacking Django
- It looks like it’s going to be a while before the RSS feed generator in Django is going to get fixed, so I looked into subclassing as a way of getting a working guid in my Django RSS feeds.
- ModelForms and FormViews
- This is just a notice because when I did a search, nothing came up. Don’t use ModelForm with FormView, use UpdateView instead.
- Django: fix_ampersands and abbreviations
- The fix_ampersands filter will miss some cases where ampersands need to be replaced.
- 29 more pages with the topic Django, and other related pages
More Python
- Quick-and-dirty old-school island script
- Here’s a Python-based island generator using the tables from the Judges Guild Island Book 1.
- Astounding Scripts on Monterey
- Monterey removes Python 2, which means that you’ll need to replace it if you’re still using any Python 2 scripts; there’s also a minor change with Layer Windows and GraphicConverter.
- Goodreads: What books did I read last week and last month?
- I occasionally want to look in Goodreads for what I read last month or last week, and that currently means sorting by date read and counting down to the beginning and end of the period in question. This Python script will do that search on an exported Goodreads csv file.
- Test classes and objects in python
- One of the advantages of object-oriented programming is that objects can masquerade as each other.
- Timeout class with retry in Python
- In Paramiko’s ssh client, timeouts don’t seem to work; a signal can handle this—and then can also perform a retry.
- 30 more pages with the topic Python, and other related pages