Skip to main content

Python: Django Custom Tags

In my last post, I complained that code like the following is redundant:
<tr class="fieldrow">
<th><label for="id_subject">Subject:</label></th>
<td>
{{ form.subject }}<br>
{% if form.subject.errors %}
<div class="error">{{ form.subject.errors|join:", " }}</div>
{% endif %}
</td>
</tr>

<tr class="fieldrow">
<th><label for="id_name">
Poster's Name:
</label></th>
<td>
{{ form.name }}<br>
{% if form.name.errors %}
<div class="error">{{ form.name.errors|join:", " }}</div>
{% endif %}
</td>
</tr>

<tr class="fieldrow">
<th><label for="id_email">
Poster's Email:
</label></th>
<td>
{{ form.email }}<br>
{% if form.email.errors %}
<div class="error">{{ form.email.errors|join:", " }}</div>
{% endif %}
</td>
</tr>

<tr>
<td></td>
<td>
{{ form.body }}<br>
{% if form.body.errors %}
<div class="error">{{ form.body.errors|join:", " }}</div>
{% endif %}
</td>
</tr>
What I really want is:
{% load formutils %}
{% fieldrow form "subject" %}Subject:{% endfieldrow %}
{% fieldrow form "name" %}Poster's Name:{% endfieldrow %}
{% fieldrow form "email" %}Poster's Email:{% endfieldrow %}
{% fieldrow form "body" %}{% endfieldrow %}
This would allow me to create new forms and form fields quickly. Naturally, there could be other custom tags besides just "fieldrow" for different style layouts. This is just the beginning. Well, I started by creating a custom tag:
"""These are custom tags for creating forms more easily."""

from django.core import template
from django.core.template import Context, loader


register = template.Library()


@register.tag
def fieldrow(parser, token):
"""This tag creates a table row with a form field.

Example: {% fieldrow form "name" %}Name:{% endfieldrow %}

form -- This is a formfields.FormWrapper object.
name -- This is the name of the field. It may be a variable or a quoted
string. If it is a quoted string, it may not contain spaces.
Name: -- This is the label for the field.

Note, I'll use <tr class="fieldrow">.

"""
try:
tag_name, form, fieldname = token.contents.split()
except ValueError:
raise template.TemplateSyntaxError(
"%s tag requires two arguments" % token.contents[0])
nodelist = parser.parse(('endfieldrow',))
parser.delete_first_token()
return FieldRow(form, fieldname, nodelist)


class FieldRow(template.Node):

def __init__(self, form, fieldname, nodelist):
self.form = form
self.fieldname = fieldname
self.nodelist = nodelist

def render(self, context):
form = template.resolve_variable(self.form, context)
fieldname = resolve_variable_or_string(self.fieldname, context)
field = form[fieldname]
label = self.nodelist.render(context)
t = loader.get_template('bulletin/fieldrow')
c = Context({"form": form, "fieldname": fieldname,
"field": field, "label": label})
return t.render(c)


def resolve_variable_or_string(s, context):
"""s may be a variable or a quoted string.

If s is a quoted string, unquote it and return it. If s is a variable,
resolve it and return it.

"""
if not s[0] in ("'", '"'):
return template.resolve_variable(s, context)
if s[-1] == s[0]:
s = s[:-1] # Strip trailing quote, if any.
s = s[1:] # Strip starting quote.
return s
This made use of a template:
{% comment %}
This template is used by the fieldrow custom tag to create a table row with
a form field.
{% endcomment %}

<tr class="fieldrow">
<th><label for="id_{{ fieldname }}">
{{ label }}
</label></th>
<td>
{{ field }}<br>
{% if field.errors %}
<div class="error">{{ field.errors|join:", " }}</div>
{% endif %}
</td>
</tr>
Viola! I'm done. It works.

Now, without a doubt, writing that custom tag is a pain. The hard part is that I have to parse the contents of the custom tag, resolve variables, etc. It sure would be nice if the templating system did that stuff for me. I personally think it would be much nicer if I could simply create a block that takes arguments. Consider something like:
{% block fieldrow(form, fieldname, field) %}
<tr class="fieldrow">
<th><label for="id_{{ fieldname }}">
{{ yield }}
</label></th>
<td>
{{ field }}<br>
{% if field.errors %}
<div class="error">{{ field.errors|join:", " }}</div>
{% endif %}
</td>
</tr>
{% endblock %}

{% fieldrow(form, "name", form.name) %}Subject's Name:{% endfieldrow %}
Sure, a template author might not be able to write that "fieldrow" block. The template authors I work with do understand code like this. If nothing else, a programmer could create a separate template containing that block. He'd be able to avoid duplicating code with very little work. Having the templating system handle parsing arguments and resolving variables would make this task much easier than creating a custom tag.

Comments

Anonymous said…
Thanks, just stole this.
Anonymous said…
Thank you very much for posting this information. The only problem that I encountered was that the import statements are not valid with the development version of Django. And it took me a while to figure out where to place the code. This post gives a thorough explanation of how this should de done.

Thanks Again!
Anonymous said…
Nice writeup. Similar to what Peter Baumgartner described in his technique for dealing with form fields in a clean way.
I often see new Django users confused about the use of template tags versus views. This is a great illustration of how template tags can save a lot of redundant code in an elegant manner.
jjinux said…
> Nice writeup.

Thanks.

Popular posts from this blog

Drawing Sierpinski's Triangle in Minecraft Using Python

In his keynote at PyCon, Eben Upton, the Executive Director of the Rasberry Pi Foundation, mentioned that not only has Minecraft been ported to the Rasberry Pi, but you can even control it with Python. Since four of my kids are avid Minecraft fans, I figured this might be a good time to teach them to program using Python. So I started yesterday with the goal of programming something cool for Minecraft and then showing it off at the San Francisco Python Meetup in the evening.

The first problem that I faced was that I didn't have a Rasberry Pi. You can't hack Minecraft by just installing the Minecraft client. Speaking of which, I didn't have the Minecraft client installed either ;) My kids always play it on their Nexus 7s. I found an open source Minecraft server called Bukkit that "provides the means to extend the popular Minecraft multiplayer server." Then I found a plugin called RaspberryJuice that implements a subset of the Minecraft Pi modding API for Bukkit s…

Apple: iPad and Emacs

Someone asked my boss's buddy Art Medlar if he was going to buy an iPad. He said, "I figure as soon as it runs Emacs, that will be the sign to buy." I think he was just trying to be funny, but his statement is actually fairly profound.

It's well known that submitting iPhone and iPad applications for sale on Apple's store is a huge pain--even if they're free and open source. Apple is acting as a gatekeeper for what is and isn't allowed on your device. I heard that Apple would never allow a scripting language to be installed on your iPad because it would allow end users to run code that they hadn't verified. (I don't have a reference for this, but if you do, please post it below.) Emacs is mostly written in Emacs Lisp. Per Apple's policy, I don't think it'll ever be possible to run Emacs on the iPad.

Emacs was written by Richard Stallman, and it practically defines the Free Software movement (in a manner of speaking at least). Stal…

ERNOS: Erlang Networked Operating System

I've been reading Dreaming in Code lately, and I really like it. If you're not a dreamer, you may safely skip the rest of this post ;)

In Chapter 10, "Engineers and Artists", Alan Kay, John Backus, and Jaron Lanier really got me thinking. I've also been thinking a lot about Minix 3, Erlang, and the original Lisp machine. The ideas are beginning to synthesize into something cohesive--more than just the sum of their parts.

Now, I'm sure that many of these ideas have already been envisioned within Tunes.org, LLVM, Microsoft's Singularity project, or in some other place that I haven't managed to discover or fully read, but I'm going to blog them anyway.

Rather than wax philosophical, let me just dump out some ideas:Start with Minix 3. It's a new microkernel, and it's meant for real use, unlike the original Minix. "This new OS is extremely small, with the part that runs in kernel mode under 4000 lines of executable code." I bet it&…