75

I have a tree structure in memory that I would like to render in HTML using a Django template.

class Node():
  name = "node name"
  children = []

There will be some object root that is a Node, and children is a list of Nodes. root will be passed in the content of the template.

I have found this one discussion of how this might be achieved, but the poster suggests this might not be good in a production environment.

Does anybody know of a better way?

David Sykes
  • 7,131
  • 4
  • 36
  • 39

10 Answers10

83

Using with template tag, I could do tree/recursive list.

Sample code:

main template: assuming 'all_root_elems' is list of one or more root of tree

<ul>
{%for node in all_root_elems %} 
    {%include "tree_view_template.html" %}
{%endfor%}
</ul>

tree_view_template.html renders the nested ul, li and uses node template variable as below:

<li> {{node.name}}
    {%if node.has_childs %}
        <ul>
         {%for ch in node.all_childs %}
              {%with node=ch template_name="tree_view_template.html" %}
                   {%include template_name%}
              {%endwith%}
         {%endfor%}
         </ul>
    {%endif%}
</li>
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Rohan
  • 52,392
  • 12
  • 90
  • 87
  • 3
    Though I'm sure there are good reasons to not do what was asked, this answer actually circumvents the problem. There is a performance hit, but using {% with %} to store the template name in a variable prevents the django template compiler from recursing infinitely. – Brian Arsuaga Aug 01 '12 at 00:06
  • 1
    Thanks! Simple, and works. Depending on the data structure you may not even need the loop in the main file (for the root objects) – Chris Koston Jul 03 '14 at 22:42
47

I'm too late.
All of you use so much unnecessary with tags, this is how I do recursive:

In the "main" template:

<!-- lets say that menu_list is already defined -->
<ul>
    {% include "menu.html" %}
</ul>

Then in menu.html:

{% for menu in menu_list %}
    <li>
        {{ menu.name }}
        {% if menu.submenus|length %}
            <ul>
                {% include "menu.html" with menu_list=menu.submenus %}
            </ul>
        {% endif %}
    </li>
{% endfor %}
cezar
  • 11,616
  • 6
  • 48
  • 84
Arthur Sult
  • 676
  • 7
  • 10
  • 2
    IMHO This is the cleanest and best abstraction in this thread. Nice one Arthur! – Neil Apr 17 '18 at 00:23
  • 1
    Be aware that if you're passing QuerySet objects to your template, you need to call `all` on the query set object: `{% if menu.submenus.all|length %}` – Behdad Dec 01 '19 at 10:50
  • Works like a charm. But I suppose this solution has only been available for a few years, see [the related Django ticket](https://code.djangoproject.com/ticket/3544). The other answers are just outdated. – natka_m Jul 14 '20 at 13:15
  • Would `menu.submenus.count` be more efficient than getting the length that way? – multithr3at3d Aug 27 '21 at 02:08
  • Simple, clean and quick. Another addition in the "main" template, you might want to pass a context: `
      {% include "menu.html" with menu_list=menu.submenus %}
    `
    – Nav May 21 '22 at 08:48
  • I'd up-vote, but you have 42 votes and I don't want to change that 8-) – Terry Brown Nov 16 '22 at 23:17
30

I think the canonical answer is: "Don't".

What you should probably do instead is unravel the thing in your view code, so it's just a matter of iterating over (in|de)dents in the template. I think I'd do it by appending indents and dedents to a list while recursing through the tree and then sending that "travelogue" list to the template. (the template would then insert <li> and </li> from that list, creating the recursive structure with "understanding" it.)

I'm also pretty sure recursively including template files is really a wrong way to do it...

Anders Eurenius
  • 4,170
  • 2
  • 24
  • 20
  • 5
    I don't see how this could possibly preserve the hierarchy of the original data unless you render the whole thing to HTML in your view. Can you provide a more concrete example? – slacy Nov 09 '09 at 22:29
  • 13
    Sure. You make a list like [ 'in', 'in', 'blah', 'out', 'blah', 'out'] and then you loop over that in the template. If it's equal to 'in' you emit a li, 'out' you emit a /li and otherwise you just dump the text itself. – Anders Eurenius Nov 17 '09 at 09:07
  • 4
    Any illumination on why that recursively building the menu is "really wrong?" Is it really that expensive? – Yablargo Feb 01 '14 at 02:54
  • Well, whether it's expensive or not in practice is sort of missing the point, the problem is that it is logic, and as such doesn't belong on the presentation side. It's more of a cleanliness thing than a performance thing. – Anders Eurenius Feb 01 '14 at 08:27
  • 2
    While I agree with separating logic/presentation. This is one of the rare instances where the problem IS presentation. Because its a recursive thing being displayed. One of the problems with the logic/presentation divide is where to draw the line such that it doesnt actually CAUSE too much coupling on account of it requiring convoluted logic in the controller to get the dumbed down view to work. The controller in theory shouldnt have to understand the view, just know how to pass it data. But I guess its just one of those compromises with reality. – Shayne Sep 02 '18 at 03:24
  • 2
    This should not be the accepted answer. It is trivial to create a simple inclusion template tag that recursively includes itself to display a tree of information as html. As long as each recursive call does not call the database, and as long as proper caching is implemented, this need not present any performance concerns. It is far messier to try to write the proper html as concatenated strings in the view; html code belongs in templates. – kloddant Dec 29 '20 at 00:16
20

this might be way more than you need, but there is a django module called 'mptt' - this stores a hierarchical tree structure in an sql database, and includes templates for display in the view code. you might be able to find something useful there.

here's the link : django-mptt

dfarrell07
  • 2,872
  • 2
  • 21
  • 26
12

Yes, you can do it. It's a little trick, passing the filename to {% include %} as a variable:

{% with template_name="file/to_include.html" %}
{% include template_name %}
{% endwith %}
Ashley
  • 2,256
  • 1
  • 33
  • 62
Vladimir
  • 6,162
  • 2
  • 32
  • 36
  • Nesting this way is nice and quick - I'm using it now - though I wonder if it's efficient (too lazy to test it myself). Anyone can chime in... – JxAxMxIxN Feb 16 '17 at 05:20
10

Django has a built in template helper for this exact scenario:

https://docs.djangoproject.com/en/dev/ref/templates/builtins/#unordered-list

cod3monk3y
  • 9,508
  • 6
  • 39
  • 54
John
  • 14,944
  • 12
  • 57
  • 57
  • 8
    Works if all you want to output is "
  • sometext
  • " for each item. If you have a nested hierarchy of more complex items, and you (for example) want each item to be a link, then this tag isn't useful. – slacy Nov 09 '09 at 22:30