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 %}

{% for child in children %}
<a href="/messages/{{ }}/">{{ child.subject|escape }}</a>
{{ child.recurse }}
{% endfor %}
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_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(". "create_recurse_callback" is a closure that is used to curry the "" 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.


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
jjinux said…

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:
___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

Ubuntu 20.04 on a 2015 15" MacBook Pro

I decided to give Ubuntu 20.04 a try on my 2015 15" MacBook Pro. I didn't actually install it; I just live booted from a USB thumb drive which was enough to try out everything I wanted. In summary, it's not perfect, and issues with my camera would prevent me from switching, but given the right hardware, I think it's a really viable option. The first thing I wanted to try was what would happen if I plugged in a non-HiDPI screen given that my laptop has a HiDPI screen. Without sub-pixel scaling, whatever scale rate I picked for one screen would apply to the other. However, once I turned on sub-pixel scaling, I was able to pick different scale rates for the internal and external displays. That looked ok. I tried plugging in and unplugging multiple times, and it didn't crash. I doubt it'd work with my Thunderbolt display at work, but it worked fine for my HDMI displays at home. I even plugged it into my TV, and it stuck to the 100% scaling I picked for the othe

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 , 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.&quo

Haskell or Erlang?

I've coded in both Erlang and Haskell. Erlang is practical, efficient, and useful. It's got a wonderful niche in the distributed world, and it has some real success stories such as CouchDB and Haskell is elegant and beautiful. It's been successful in various programming language competitions. I have some experience in both, but I'm thinking it's time to really commit to learning one of them on a professional level. They both have good books out now, and it's probably time I read one of those books cover to cover. My question is which? Back in 2000, Perl had established a real niche for systems administration, CGI, and text processing. The syntax wasn't exactly beautiful (unless you're into that sort of thing), but it was popular and mature. Python hadn't really become popular, nor did it really have a strong niche (at least as far as I could see). I went with Python because of its elegance, but since then, I've coded both p