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. Maybe you should use it. Doesn’t Django use a cached loader now? Make your own decision.

15 thoughts on “Recursion in Django templates

  1. 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 🙂

    Like

  2. 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?

    Like

  3. 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?

    Like

    1. 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.

      Like

  4. Awesome work. Was so proud of my recursion in the view and got stuck on the rendering of it in the template. Can’t believe that you have to assign the filename to a variable but it works just like you said. Thanks, you’re a legend! 🙂

    PS! I’m using it on a production system but just for admin/cms stuff, so I guess there’s no real harm there. Wanted to have a menu using this (one of those nice superfish-style dropdown menus), but I suspect that that would REALLY kill the server (having to resolve this recursion on every single page call).

    Like

  5. Hi,

    This is exactly what I need, but I can’t make it work…

    I put the code in a template which I included in my main template, the first include is fine, but it doesn’t go further.
    It react like if “post” from “{% for post in thread %} ” is empty (but it’s not) and doesn’t go inside the loop.

    Does anybody have this kind of trouble?

    thanks.

    Like

  6. Jérémie, I’m having this exact same issue while trying to get threaded-comments[1] rendering working.

    Other directions I’ve been going are to integrate a “recursive_include” into my template tags based on the function at this bug request [2]. That seemed to cause the same problems, so I’m going in a new direction based on [3], which is an intricate dance between template and view to create the final template.

    You wouldn’t think getting threaded-comments to actually display in a nested structure would be this difficult!

    Have you made progress on the issue?

    [1] http://github.com/ericflo/django-threadedcomments
    [2] http://code.djangoproject.com/ticket/3544
    [3] http://www.elfsternberg.com/2009/05/18/template-recursion-in-django-with-treebeard/

    Like

  7. Aaaand three and a half years later, this trick still works as expected, while include with a constant still causes crashing.

    Like

  8. This still works like a charm years later, I also find it more elegant than doing it in the view, even though we might encounter performance issues.
    It’s a template that isn’t used that much on the site anyway.
    I have a suggestion to make the rendered code more beautiful and avoid having empty blocks when the node has no children.
    Put a if test before the include block {% include filename %} or you could also put it at the beginning of the template, so it doesn’t render with nothing inside.

    Like

  9. Thanks! I’m not yet to the point where I actually need a recursive template, but I’ve used what you shared in my template; I’ll have to wait until I get more data added to find out if it works like I’m hoping.

    Like

Leave a comment