Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining "last" element for each level in django-mptt

I'm trying to generate a list akin to:

<ul>
    <li>Parent 1
        <ul>
            <li>Child 1</li>
            <li>Child 2</li>
            <li class="last">Child 3</li>
        </ul>
    </li>
    <li>Parent 2
        <ul>
            <li>Child 1</li>
            <li>Child 2</li>
            <li class="last">Child 3</li>
        </ul>
    </li>
    <li class="last">Parent 3
        <ul>
            <li>Child 1</li>
            <li>Child 2</li>
            <li class="last">Child 3</li>
        </ul>
    </li>
</ul>

I originally just did:

<ul>
    {% recursetree mytree %}
    <li class="{% if not node.get_next_sibling %}last{% endif %}">
        {{ node }}
        {% if not node.is_leaf_node %}
        <ul>
            {{ children }}
        </ul>
        {% endif %}
    </li>
</ul>

However, the call to node.get_next_sibling results in an extra query for each item. Obviously, that's not ideal. So I tried using tree_info and structure.closed_levels to determine the last item:

{% for node,structure in mytree|tree_info %}
    {% if structure.new_level %}<ul><li>{% else %}</li><li>{% endif %}
        <li class="{% if structure.closed_levels|length > 0 %}last{% endif %}">
            {{ node }}
    {% for level in structure.closed_levels %}</li></ul>{% endfor %}
{% endfor %}

This works great, except the last root level item does not get the "last" class, because its structure.closed_levels is always an empty list. (It really only works for child items).

I'm sure I'm not the first to need to accomplish something similar, so I'm hoping someone here might have a solution already.

like image 221
Chris Pratt Avatar asked Dec 29 '25 01:12

Chris Pratt


1 Answers

I imagine you should be able to get the info you need from the MPTT order info. Here's a good intro to how MPTT works (linked from the django-mptt docs). The key is keeping a reference to the parent around, so you can check whether your node's "right" attribute is one less than your parent's "left".

django-mptt special-cases the root nodes, to let you have multiple trees. If you're iterating over the nodes in a single tree, something like this should work (though I haven't tested):

<ul class="root">
    {% recursetree nodes %}
        <li class="{% if parent == None or node.rgt|plusone == parent.lft %}last{% endif %}">
            {{ node.name }}
            {% if not node.is_leaf_node %}
                {% with node as parent %}
                <ul class="children">
                    {{ children }}
                </ul>
                {% endwith %}
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>

If, however, "nodes" holds a list of all your roots, you'll need to catch that explicitly. Something like this should do the trick:

{% with nodes|last as lastnode %}
<ul class="root">
    {% recursetree nodes %}
        <li class="{% if node == lastnode or parent and node.rgt|plusone == parent.lft %}last{% endif %}">
            {{ node.name }}
            {% if not node.is_leaf_node %}
                {% with node as parent %}
                <ul class="children">
                    {{ children }}
                </ul>
                {% endwith %}
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>
{% endwith %}

You'll notice that the code above references a "plusone" template filter. Something like this should do:

from django import template

register = template.Library()

@register.filter
def plusone(value):
    return value + 1

All that being said, this is a little too much template computation for my liking. If this is something you're doing regularly, it's probably wise to wrap it up in a custom template tag.

like image 143
Gabriel Grant Avatar answered Dec 31 '25 14:12

Gabriel Grant