[Django]-I'm trying to build site navigation using django-mptt

6👍

django-page-cms has solved this type of problem using django-mptt, so I suggest having a look at the way it’s done there.

Specifically, the template to render the tree menu is recursively included via a custom template tag called pages_dynamic_tree_menu.

This sounds complicated (custom ta-whaaa!), so I’ll break it down a bit more.

We can consider a Page in django-page-cms to be roughly the same as your Section class.

In the base template which is shared between 99% of your pages, django-page-cms gets you to insert the following snippet:

<ul>
{% for page in pages_navigation %}
    {% pages_dynamic_tree_menu page %}
{% endfor %}
</ul>

pages_navigation is set by a context processor (a way to add items to a template context for all views in a django project) and contains the top level pages (i.e. those without parents).

The pages_dynamic_tree_menu tag is defined and registered as follows:

def pages_dynamic_tree_menu(context, page, url='/'):
    """
    Render a "dynamic" tree menu, with all nodes expanded which are either
    ancestors or the current page itself.

    Override ``pages/dynamic_tree_menu.html`` if you want to change the
    design.

    :param page: the current page
    :param url: not used anymore
    """
    lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
    page = get_page_from_string_or_id(page, lang)
    children = None
    if page and 'current_page' in context:
        current_page = context['current_page']
        # if this node is expanded, we also have to render its children
        # a node is expanded if it is the current node or one of its ancestors
        if(page.tree_id == current_page.tree_id and
            page.lft <= current_page.lft and
            page.rght >= current_page.rght):
            children = page.get_children_for_frontend()
    context.update({'children': children, 'page': page})
    return context
    pages_dynamic_tree_menu = register.inclusion_tag(
        'pages/dynamic_tree_menu.html',
        takes_context=True
    )(pages_dynamic_tree_menu)

The whole point of it is to set the page and the children variables.

The magic is in the if statement, which ensures the children are only displayed if the page is the current one (the current_page template variable is set by the django-page-cms view which renders a Page), or if the page is an ancestor. The lft & rght attributes are provided by django-mptt, and two of the properties of this MPTT are that the left value of a parent tree node will be less than its child node’s left value and the right value of a parent node will be more than the right value of its child node, which you can verify visually by looking at the image below from the article Trees in SQL:

picture of MPTT

The get_children_for_frontend() method of Page calls MPTTModel.getChildren() with some filtering to hide unpublished Page objects, so nothing special there.

The tag then does the tag equivalent of {% include %} with the pages/dynamic_tree_menu.html template, which in turn calls the pages_dynamic_tree_menu tag again, causing the whole thing to recurse:

{% load pages_tags cache %}
{% if page.calculated_status %}
<li {% ifequal page current_page %}class="selected"{% endifequal %}>
<a href="{% show_absolute_url page %}">{% show_content page "title" %}</a>
{% if children %}
    <ul>{% for child in children %}{% pages_dynamic_tree_menu child url %}{% endfor %}</ul>
{% endif %}
</li>
{% endif %}

So if you define a similar setup (provide top level sections and the current section to your template, define an “inclusive” template tag and matching template for the tag) and apply some styling, then you should be able to use this approach to accomplish your goal.

👤Caspar

Leave a comment