Recursion in Django templates

Recently, someone asked in #django about outputting recursive structures, say a forum or comment thread. Although this could be considered a presentation logic issue, the smartest thing is most likely to prepare the data in your view accordingly. Besides, Django's template language doesn't support recursion. Or does it?

There are no macros, or a call/function concept. And if you attempt a standard recursive {% include %} you'll notice that requests hang, as the tag is resolved during compilation - the context is still out of the picture, so there is no way to stop the recursion. But if you look at the source you'll notice that includes are actually implemented differently if the filename is passed as a variable. A context is now needed to resolve it, and the included file is loaded while rendering (see IncludeNode vs ConstantIncludeNode).

This behavior can be exploited to solve the initial problem:

{# filename: list.html #}
<ul>
{% for post in thread %}
	<li>{{ post.title }}
    {% with "list.html" as filename %}
    {% with post.replies as thread %}
        {% include filename %}
    {% endwith %}
    {% endwith %}</li>
{% endfor %}
</ul>

Kind of neat, I think - although I wouldn't recommend actually using that approach in a production app, and if it's only for the fact that the template is reloaded from disk each and every time, which I can't imagine is good for performance.

4 Responses to “Recursion in Django templates”

  1. Ivan Says:

    Super cool man...
    I was just wondering why recursion doesn't work... in a reply-to-a-reply rendering template of mine

    Why not use in production?
    What could poooooosibly ever go wrong with a recursive call :)

  2. Brandon Says:

    How do you actually implement this in the main template? I've tried running this code as an include, and copy/pasted into my main template and handing it a set of categories:

    {% for category in categories %}
    {{ category.name }}
    {% with "list.html" as filename %}
    {% with category.category_set.all as category %}
    {% include filename %}
    {% endwith %}
    {% endwith %}
    {% endfor %}

    This causes the built-in server to completely shut down without an error. What am I doing wrong?

  3. rennat Says:

    Ok so there's some cases where I really want to use this in production. (like dynamic nested nav) because it seems like an elegant solution from the way it's written.

    Is this a really bad idea or just not a good idea for a million-uniques-a-day site?

  4. admin Says:

    I would now say it's mainly problematic since it relies on an implementation detail in the Django template language. There's no guarantee this might not stop working in a new version.

    As for performance issue, I was probably too alarmist, especially now that Django actually can cache templates in memory once compiled. So you probably shouldn't worry.

    But still, if you do want to go after performance, and you are rendering out a forum thread with a huge number of branches and nodes, it'll sure be faster to set something like an indentation-level attribute in your python code.

Leave a Reply