Chris Pratt
Chris Pratt

Reputation: 239290

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.

Upvotes: 3

Views: 1460

Answers (2)

frederik-b
frederik-b

Reputation: 178

You should use the{{forloop.last}} or {{if 1 == forloop.revcounter}} 2nd last would be {{if 2 == forloop.revcounter}}

to add classes with django logic...

if you just want to add styles you can use css selecotrs :last-child instead of adding a class

css selectors to work for jquery if you need it for javascript and are using jquery

Upvotes: 0

Gabriel Grant
Gabriel Grant

Reputation: 5591

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.

Upvotes: 1

Related Questions