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?
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...?
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? :)
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.
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.
Paul 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).
I'm sorry, I don't see the recursion in your suggestion. It's a tree, not just a list.
Paul 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.
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."
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.