Skip to main content

Python: Recursion in Django Templates

Whenever learning a new Web technology, I like to write a bulletin board application. It's easy, I have it memorized, and it gives me a chance to compare and contrast the new technology with what I already know. See here for the application written in Aquarium.

The thing that was hardest about using Django templates to write this application was the lack of functions and recursion in the templating engine. Recursion is really suitable for generating a hierarchy of messages. This post is about how I had to work around the problem. I got it to work, but it wasn't what I'd call elegant.

To use Django to generate a page that looks like this, you break it up into a few steps. First, you create a template that has everything other than the hierarchy of messages. I called it "index.html":
{% comment %}
Output the messages, and output a message input form.
{% endcomment %}

{% extends "bulletinboard/vertical" %}

{% block vertical_body %}
<div class="container">
<div style="float: right">[<a href="">Start a New Thread</a>]</div>
<p><b>List of Messages:</b></p>
{% if not messages %}
<p>There are no messages yet.</p>
{% else %}
{{ messages }}
{% endif %}
</div><br />
{% endblock %}
Secondly, you pass the pre-generated hierarchy of messages as a chunk of HTML to the first template. Hence, in my view, I have code:
    return render_to_response('bulletinboard/index', 
{'messages': show_message_hierarchy()})
where "show_message_hierarchy()" generates the big chunk of HTML. Next, I created a template, "showmessagehierarchy.html", that gets called recursively (although, naturally, it can't call itself recursively in Django):
{% comment %}
This template is called recursively to output the bulletin board messages in
a nice hierarchy.
{% endcomment %}

<ul>
{% for child in children %}
<li>
<a href="/messages/{{ child.id }}/">{{ child.subject|escape }}</a>
{{ child.recurse }}
</li>
{% endfor %}
</ul>
Note, the recursion happens with "child.recurse", which is a callback that the view sets up. Each child has a different callback--it's not possible to say "recurse(child)" because there are no function arguments in Django (unless you use a custom tag). "child.recurse" is not a method of "child"; it's just a function that I've attached to "child". Now, let's look at the view that pulls everything together:
def index(request):

"""Put the list of messages in a nice hierarchical format."""

def show_message_hierarchy(parent=None):
"""Show the bulletin board messages in a nice hierarchy."""
children = message_dict.get(parent, [])
if not children:
return ""
c = Context({"children": children})
return template.render(c)

def create_recurse_callback(id):

"""This is a closure.

It curries the id for show_message_hierarchy. The "partial" function
doesn't exist yet in Python 2.4.

"""

def inner():
return show_message_hierarchy(id)

return inner

message_list = messages.get_list()
message_dict = {}
for message in message_list:
message.recurse = create_recurse_callback(message.id)
message_dict[message.parent_id] = message_dict.get(message.parent_id, []) + [message]
template = loader.get_template('bulletinboard/showmessagehierarchy')
return render_to_response('bulletinboard/index',
{'messages': show_message_hierarchy()})
Here you can see "child.recurse", i.e. "message.recurse", getting setup: "message.recurse = create_recurse_callback(message.id)". "create_recurse_callback" is a closure that is used to curry the "message.id" so that you can call "show_message_hierarchy" without needing to pass an argument. I got that trick from function programming ;) Notice that, basically, the "show_message_hierarchy" function and the "showmessagehierarchy" template recursively call each other.

When I did this in Cheetah, I just had the main template, and another function in the template that could call itself recursively. Nonetheless, I was able to get it to work in Django, albeit in a non-elegant fashion.

Comments

Anonymous said…
Did you consider trying to hook Cheetah up in Django?
jjinux said…
That's the plan ;) I may steal a bunch of code from Aquarium to make the integration really nice :)
Ian Bicking said…
Can you include templates from within other templates in Django? If the template included itself, that should handle recursion. The only issue then is getting the message ID from the parent. Which might be hard in Django's case...?
jjinux said…
Ian, yes, I'm pretty sure you can do a recursive include, but without arguments, recursion is not very useful. :-/ You can't even change existing variables, so it's just a no go. I think you either have to 1) do it in the view 2) control recursion from the view like I did 3) use a custom tag.
Anonymous said…
Why are you affraid of custom tags? :)
jjinux said…
I just don't think I should have to fall back to Python just to package a bit of HTML in a reusable function. I think even template authors should be able to package HTML into functions.
akaihola said…
There's also the {% recurse %} template tag on djangosnippets.org.
jjinux said…
Interesting.

You'll note that that tag came out two years and two months after my post ;)
akaihola said…
Now that I took another look at the top of the post, I noticed that. But below the post it says:

Posted by Shannon -jj Behrens at 11:23 AM

No date -> looks just like it was written today! I say Google has a usability issue right there :)
cardhu said…
I have sucessfuly used include with pseudo-parameters - what I do is enclose the _include_ tag in a _with_ tag declaring a variable name used later in the included template. Template Recursion is in fact a No. I haven't tried this code but looks nice.
Anonymous said…
You seem to be building your html from root to leafs:

render message:
apply template to message
(template iterates over children)

This forces you to perform that 'call' from within the template -- the problem.

Have you considered generating the html starting at the leafs? generate the html there and pass that as an argument for rendering the parent message. thus:

render message:
for msg in message children
child html += render msg
apply template to message passing child html
return html

problem solved (me thinks).
jjinux said…
I'm sorry, I don't see the recursion in your suggestion. It's a tree, not just a list.
Anonymous said…
Oh, well, I should've put it a little more formal probably:

def render_message(message):
___child_html = []
___for msg in message.children:
______child_html.append(render_message(msg))
___return render_to_string("message.html", {responses:child_html})

Sorry for the ugly dashes but I cannot post code.
jjinux said…
Ah, cool. Now I see the recursion.

What I have learned is that Django and I disagree on design. I tend to let my templates control their own flow, pulling in things as necessary--that's why I'm such a big fan of template functions. In Django, the Python view really needs to be in control of everything, and templates should only be used for mostly dumb chunks of HTML.
Fingal said…
I notice that the link to your example is no longer valid. It appears that the member in question is no longer active, and trying it with a different member yields a page whose format doesn't scream "hierarchy of messages."
jjinux said…
Sorry, that was a demo application running on my friend's server. It showed a nested hierarchy of messages (kind of like an outline). If you're trying to do that in Django, just walk through the instructions in the blog post using your own Django application, and you'll get it working.

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…

JavaScript: Porting from react-css-modules to babel-plugin-react-css-modules (with Less)

I recently found a bug in react-css-modules that prevented me from upgrading react-mobx which prevented us from upgrading to React 16. Then, I found out that react-css-modules is "no longer actively maintained". Hence, whether I wanted to or not, I was kind of forced into moving from react-css-modules to babel-plugin-react-css-modules. Doing the port is mostly straightforward. Once I switched libraries, the rest of the port was basically:
Get ESLint to pass now that react-css-modules is no longer available.Get babel-plugin-react-css-modules working with Less.Get my Karma tests to at least build.Get the Karma tests to pass.Test things thoroughly.Fight off merge conflicts from the rest of engineering every 10 minutes ;) There were a few things that resulted in difficult code changes. That's what the rest of this blog post is about. I don't think you can fix all of these things ahead of time. Just read through them and keep them in mind as you follow the approach above.…