jinja2: How to properly format a recursive macro - recursion

I have a nested structure and a recursive macro to render it. however there is an extra empty line before all of the closing tags. How do I remove the empty lines so that is renders one contiguous block?
The data is in a form like so
name='...'
list
a
a
b
name='...'
list
b
b
a
The template is like so
{% macro list(item) -%}
<ul name="{{ item.name }}">
{% for item in item.list -%}
{% if item.a -%}
<li a="{{ item.a }}"/>
{% elif item.b -%}
<li b="{{ item.a }}">
{% item.name -%}
{{- list(item) }}
{% endif -%}
{%- endfor %}
</ul>
{% endmacro -%}
The expected outcome shouldn't have this extra whitespace before the </ul>.
<ul name="...">
<li a="a"/>
<li a="a"/>
<li b="b"/>
<ul name="...">
<li b="b"/>
<li b="b"/>
</ul>
<li a="a"/>
</ul>

Since Jinja is keeping track of linespacing why not move the statements to one line for the ending of "if" and "for" jinja declarations?
{% macro list(item) -%}
<ul name="{{ item.name }}">
{% for item in item.list -%}
{% if item.a -%}
<li a="{{ item.a }}"/>
{% elif item.b -%}
<li b="{{ item.a }}">
{% item.name -%}
{{- list(item) }}{% endif -%}{%- endfor %}
</ul>
{% endmacro -%}

Related

hide empty collection in / from menu in Shopify

I recently moved from Woocommerce to Shopify. I am using Expanse theme from Shopify. I have set listings not to show once they sold out or no stock is available. I need help with hiding the collection from menu that has no stock. I am going to add the code below. Kindly help what code line i need to add within that code.
The header navigation liquid code is below;
{%- liquid
unless limit
assign limit = main_menu.links.size
endunless
unless offset
assign offset = 0
endunless
-%}
<ul class="site-nav site-navigation site-navigation--{{ nav_position }} small--hide" role="navigation">
{%- for link in main_menu.links limit: limit offset: offset -%}
{%- liquid
assign has_dropdown = false
assign is_megamenu = false
if link.levels > 0
assign has_dropdown = true
if link.levels > 1
assign is_megamenu = true
endif
endif
assign isCollection = false
if show_mega_products
if is_megamenu and link.url contains '/collections/'
assign lang_code_string = request.locale.iso_code | prepend: '/' | downcase
assign collection_handle = link.url | remove: '/collections/' | remove: lang_code_string
assign collection_drop = collections[collection_handle]
assign isCollection = true
endif
endif
-%}
<li
class="site-nav__item site-nav__expanded-item{% if has_dropdown %} site-nav--has-dropdown{% endif %}{% if is_megamenu %} site-nav--is-megamenu{% endif %}"
{% if has_dropdown %}aria-haspopup="true"{% endif %}>
<a href="{{ link.url }}" class="site-nav__link site-nav__link--underline{% if has_dropdown %} site-nav__link--has-dropdown{% endif %}">
{{ link.title }}
</a>
{%- if is_megamenu -%}
{%- assign previous_column_type = '' -%}
<div class="site-nav__dropdown megamenu text-left">
<div class="page-width">
<div class="site-nav__dropdown-animate megamenu__wrapper">
<div class="megamenu__cols">
<div class="megamenu__col">
{%- for childlink in link.links -%}
{%- liquid
assign create_new_column = false
if childlink.levels > 0 and forloop.index != 1
assign create_new_column = true
endif
if childlink.levels == 0 and previous_column_type == 'full'
assign create_new_column = true
endif
-%}
{%- if create_new_column -%}
</div><div class="megamenu__col">
{%- endif -%}
<div class="megamenu__col-title">
{{ childlink.title }}
</div>
{%- liquid
if childlink.levels > 0
assign previous_column_type = 'full'
else
assign previous_column_type = 'single'
endif
-%}
{%- for grandchildlink in childlink.links -%}
<a href="{{ grandchildlink.url }}" class="site-nav__dropdown-link">
{{grandchildlink.title}}
</a>
{%- endfor -%}
{%- endfor -%}
</div>
</div>
{%- if isCollection -%}
<div class="megamenu__featured">
<div class="product-grid">
{%- liquid
assign mega_product = collection_drop.products.first
render 'product-grid-item', product: mega_product
if settings.quick_shop_enable
render 'quick-shop-modal', product: mega_product
endif
-%}
</div>
</div>
{%- endif -%}
</div>
</div>
</div>
{%- elsif has_dropdown -%}
<div class="site-nav__dropdown">
<ul class="site-nav__dropdown-animate site-nav__dropdown-list text-left">
{%- for childlink in link.links -%}
{%- liquid
assign has_sub_dropdown = false
if childlink.levels > 0
assign has_sub_dropdown = true
endif
-%}
<li class="{% if has_sub_dropdown %} site-nav__deep-dropdown-trigger{% endif %}">
<a href="{{ childlink.url }}" class="site-nav__dropdown-link site-nav__dropdown-link--second-level{% if has_sub_dropdown %} site-nav__dropdown-link--has-children{% endif %}">
{{ childlink.title | escape }}
{%- if has_sub_dropdown -%}
<svg aria-hidden="true" focusable="false" role="presentation" class="icon icon--wide icon-chevron-down" viewBox="0 0 28 16"><path d="M1.57 1.59l12.76 12.77L27.1 1.59" stroke-width="2" stroke="#000" fill="none" fill-rule="evenodd"/></svg>
{%- endif -%}
</a>
{%- if has_sub_dropdown -%}
<ul class="site-nav__deep-dropdown">
{%- for grandchildlink in childlink.links -%}
<li>
{{ grandchildlink.title | escape }}
</li>
{%- endfor -%}
</ul>
{%- endif -%}
</li>
{%- endfor -%}
</ul>
</div>
{%- endif -%}
</li>
{%- endfor -%}
</ul>
Here is a way to check if a link object is a collection and if it contains or not available products (which I understand is your question) (not tested):
{% if link.type == 'collection_link' %}
{% assign c_products = link.object.products | where: "available" %}
{% if c_products.size > 0 %}
Display the link
{% endif %}
{% endif %}
Explanations:
First, I check if the current link is a link to a collection by checking type value.
If it is, I define an array of available products contained by the collection (accessing it via link.object) whith a filter. The where filter is here to keep only available products in the array.
Then I check if the array size, then I know whether collection contains available products or not.
HTH
Useful documentation :
Link object: https://shopify.dev/api/liquid/objects#link
Where filter (arrays): https://shopify.github.io/liquid/filters/where/
Size filter (arrays): https://shopify.github.io/liquid/filters/size/

How can I render product category and it's subcategory list alphabetically sorted in Django-Oscar?

I can render the product category list in template. But the category list are not showing in alphabetical order. How can I render them in alphabetical order ?
Edit
I am using the following code to render categories and subcategories.
<ul class="cd-accordion-menu animated">
{% for tree_category, info in tree_categories %}
<li {% if info.has_children %}class="has-children"
{% else %}{% endif %}>
{% if info.has_children %}
<input type="checkbox" name="{{ tree_category.name|lower }}"
id="{{ tree_category.name|lower }}">
<label for="{{ tree_category.name|lower }}">{{ tree_category.name|safe }}
{% else %}
{{ tree_category.name|safe }}
{% endif %}
{% if info.has_children %}</label>{% endif %}
{% if info.has_children %}
<ul>
{% endif %}
{% for n in info.num_to_close %}
</ul>
</li>
{% endfor %}
{% endfor %}
</ul>
This shows the categories as below.
I had the same requirement, and ended up customising the category_tree template tag. I copied the original implementation of category_tree (see link) and added a sorting step to sort. Only one lined changed in comparison to the original category_tree template tag.
from django import template
from oscar.core.loading import get_model
register = template.Library()
Category = get_model('catalogue', 'category')
#register.assignment_tag(name="category_tree")
def get_annotated_list(depth=None, parent=None):
"""
Gets an annotated list from a tree branch.
Borrows heavily from treebeard's get_annotated_list
"""
# 'depth' is the backwards-compatible name for the template tag,
# 'max_depth' is the better variable name.
max_depth = depth
annotated_categories = []
start_depth, prev_depth = (None, None)
if parent:
categories = parent.get_descendants()
if max_depth is not None:
max_depth += parent.get_depth()
else:
categories = Category.get_tree()
info = {}
# CUSTOM SORTING HERE
for node in sorted(categories, key=lambda x: x.name):
node_depth = node.get_depth()
if start_depth is None:
start_depth = node_depth
if max_depth is not None and node_depth > max_depth:
continue
# Update previous node's info
info['has_children'] = prev_depth is None or node_depth > prev_depth
if prev_depth is not None and node_depth < prev_depth:
info['num_to_close'] = list(range(0, prev_depth - node_depth))
info = {'num_to_close': [],
'level': node_depth - start_depth}
annotated_categories.append((node, info,))
prev_depth = node_depth
if prev_depth is not None:
# close last leaf
info['num_to_close'] = list(range(0, prev_depth - start_depth))
info['has_children'] = prev_depth > prev_depth
return annotated_categories
PS: check the django docs if you don't know how to include custom templatetags, they need to go in a dedicated folder.
Template code:
<ul class="cd-accordion-menu animated">
{% for tree_category, info in tree_categories %}
<li {% if info.has_children %}class="has-children"
{% else %}{% endif %}>
{% if info.has_children %}
<input type="checkbox" name="{{ tree_category.name|lower }}" id="{{ tree_category.name|lower }}">
<label for="{{ tree_category.name|lower }}">{{ tree_category.name|safe }}</label>
{% else %}
{{ tree_category.name|safe }}
{% endif %}
{% if info.has_children %}
<ul>
{% else %}
</li>
{% endif %}
{% for n in info.num_to_close %}
</ul>
</li>
{% endfor %}
{% endfor %}
</ul>
Just try to order the MP_Node.
class Category(AbstractCategory):
node_order_by = ['name']

KnpMenuBundle not working with Bootstrap 4 navbar

I am currently making a menu with the Symfony bundle: KnpMenuBundle. I am using Bootstrap 4 as stylesheet.
Bootstrap 4 requires each list item in the navbar to have the class 'nav-item':
<li class="nav-item active"> <-- this
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
I can't seem to figure out how to add the class 'nav-item' to the list item with KnpMenuBundle. Currently I see this when I load the page:
navbar result
This is my Builder class in src/AppBundle/Menu:
namespace AppBundle\Menu;
use Knp\Menu\MenuFactory;
class Builder
{
public function mainMenu(MenuFactory $factory, array $optioins)
{
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'navbar-nav mr-auto');
$menu->addChild('Home', ['route' => 'homepage']);
$menu->setChildrenAttributes(['class' => 'nav-item']);
return $menu;
}
}
My code in base.html.twig to generate menu:
{{ knp_menu_render('AppBundle:Builder:mainMenu', {'currentClass': 'active'}) }}
What do I do to make it working?
I did this to get top level correct. Haven't worked out dropdowns yet though.
$menu->setChildrenAttribute('class', 'navbar-nav');
// menu items
foreach ($menu as $child) {
$child->setLinkAttribute('class', 'nav-link')
->setAttribute('class', 'nav-item');
}
You can add a class to each child items when you add it like that:
$menu->addChild('Home', ['route' => 'homepage'])
->setAttributes(array(
'class' => 'nav-item'
));
You should create new template file, for example "extended_knp_menu.html.twig" (also filename should not be named knp_menu.html.twig), with contents (src):
{% extends 'knp_menu.html.twig' %}
{% macro setCssClassAttribute(item, type, add) %}
{% set getter = 'get' ~ type %}
{% set setter = 'set' ~ type %}
{% set value = attribute(item, getter, ['class']) %}
{% if value is iterable %}
{% set value = value|join(' ') %}
{% endif %}
{% do attribute(item, setter, ['class', value ~ ' ' ~ add]) %}
{% endmacro %}
{% block item %}
{% import "knp_menu.html.twig" as macros %}
{#
As multiple level is not currently supported by bootstrap 4
This requires you to install
https://github.com/bootstrapthemesco/bootstrap-4-multi-dropdown-navbar
And set the the use_multilevel = true
#}
{% set use_multilevel = false %}
{% if item.displayed %}
{%- set attributes = item.attributes %}
{%- set is_dropdown = attributes.dropdown|default(false) %}
{%- set divider_prepend = attributes.divider_prepend|default(false) %}
{%- set divider_append = attributes.divider_append|default(false) %}
{# unset bootstrap specific attributes #}
{%- set attributes = attributes|merge({'dropdown': null, 'divider_prepend': null, 'divider_append': null }) %}
{%- if divider_prepend %}
{{ block('dividerElement') }}
{%- endif %}
{# building the class of the item #}
{%- set classes = item.attribute('class') is not empty ? [item.attribute('class'), 'nav-item'] : ['nav-item'] %}
{%- if matcher.isCurrent(item) %}
{%- set classes = classes|merge([options.currentClass]) %}
{%- elseif matcher.isAncestor(item, options.depth) %}
{%- set classes = classes|merge([options.ancestorClass]) %}
{%- endif %}
{%- if item.actsLikeFirst %}
{%- set classes = classes|merge([options.firstClass]) %}
{%- endif %}
{%- if item.actsLikeLast %}
{%- set classes = classes|merge([options.lastClass]) %}
{%- endif %}
{# building the class of the children #}
{%- set childrenClasses = item.childrenAttribute('class') is not empty ? [item.childrenAttribute('class')] : [] %}
{%- set childrenClasses = childrenClasses|merge(['menu_level_' ~ item.level]) %}
{# adding classes for dropdown #}
{%- if is_dropdown %}
{%- set classes = classes|merge(['dropdown']) %}
{%- set childrenClasses = childrenClasses|merge(['dropdown-menu']) %}
{%- endif %}
{# putting classes together #}
{%- if classes is not empty %}
{%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
{%- endif %}
{%- set listAttributes = item.childrenAttributes|merge({'class': childrenClasses|join(' ') }) %}
<li{{ macros.attributes(attributes) }}>
{# displaying the item #}
{%- if is_dropdown %}
{{ block('dropdownElement') }}
{%- elseif item.uri is not empty and (not item.current or options.currentAsLink) %}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElement') }}
{%- endif %}
{%- if divider_append %}
{{ block('dividerElement') }}
{%- endif %}
{% if item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
{{ block('dropdownlinks') }}
{% endif %}
</li>
{% endif %}
{% endblock %}
{% block dropdownlinks %}
{% if use_multilevel %}
<ul class="dropdown-menu">
{% else %}
<div class="dropdown-menu">
{% endif %}
{% for item in item.children %}
{{ block('renderDropdownlink') }}
{% if use_multilevel and item.hasChildren and options.depth is not same as(0) and item.displayChildren %}
{{ block('dropdownlinks') }}
{% endif %}
{% endfor %}
{% if not use_multilevel %}
</div>
{% else %}
</ul>
{% endif %}
{% endblock %}
{% block renderDropdownlink %}
{% import _self as ownmacro %}
{%- set divider_prepend = item.attributes.divider_prepend|default(false) %}
{%- set divider_append = item.attributes.divider_append|default(false) %}
{%- set attributes = item.attributes|merge({'dropdown': null, 'divider_prepend': null, 'divider_append': null }) %}
{% if use_multilevel %}
<li>
{% endif %}
{%- if divider_prepend %}
{{ block('dividerElementDropdown') }}
{%- endif %}
{%- if item.uri is not empty and (not item.current or options.currentAsLink) %}
{{ ownmacro.setCssClassAttribute(item, 'LinkAttribute', 'dropdown-item') }}
{{ block('linkElement') }}
{%- else %}
{{ block('spanElementDropdown') }}
{%- endif %}
{%- if divider_append %}
{{ block('dividerElementDropdown') }}
{%- endif %}
{% if use_multilevel %}
</li>
{% endif %}
{% endblock %}
{% block spanElementDropdown %}
{% import "knp_menu.html.twig" as macros %}
{% import _self as ownmacro %}
{{ ownmacro.setCssClassAttribute(item, 'LabelAttribute', 'dropdown-header') }}
<div {{ macros.attributes(item.labelAttributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
</div>
{% endblock %}
{% block dividerElementDropdown %}
<div class="dropdown-divider"></div>
{% endblock %}
{% block dividerElement %}
{% if item.level == 1 %}
<li class="divider-vertical"></li>
{% else %}
<li class="divider"></li>
{% endif %}
{% endblock %}
{% block linkElement %}
{% import "knp_menu.html.twig" as macros %}
{% import _self as ownmacro %}
{{ ownmacro.setCssClassAttribute(item, 'LinkAttribute', 'nav-link') }}
<a href="{{ item.uri }}"{{ macros.attributes(item.linkAttributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
</a>
{% endblock %}
{% block spanElement %}
{% import "knp_menu.html.twig" as macros %}
{% import _self as ownmacro %}
{{ ownmacro.setCssClassAttribute(item, 'LabelAttribute', 'navbar-text') }}
<span {{ macros.attributes(item.labelAttributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
</span>
{% endblock %}
{% block dropdownElement %}
{% import "knp_menu.html.twig" as macros %}
{%- set classes = item.linkAttribute('class') is not empty ? [item.linkAttribute('class')] : [] %}
{%- set classes = classes|merge(['dropdown-toggle', 'nav-link']) %}
{%- set attributes = item.linkAttributes %}
{%- set attributes = attributes|merge({'class': classes|join(' ')}) %}
{%- set attributes = attributes|merge({'data-toggle': 'dropdown'}) %}
<a href="#"{{ macros.attributes(attributes) }}>
{% if item.attribute('icon') is not empty %}
<i class="{{ item.attribute('icon') }}"></i>
{% endif %}
{{ block('label') }}
<b class="caret"></b>
</a>
{% endblock %}
{% block label %}{{ item.label|trans }}{% endblock %}
After just include rendering template for menu:
{{ knp_menu_render('AppBundle:Builder:mainMenu', {
'currentClass': 'active',
'template': 'extended_knp_menu.html.twig'
}) }}
Hope it helped you.

Recursive parsing of hierarchical Twig layout with parents, children, grandchildren

In my SF2 project I have an entity (Category) which I am representing in a hierarchical format with a parent at the top, followed by children, grandchildren etc.
The Category entity has a getChildren method, which works and returns Category entity objects.
I'm trying to work out a way to make this layout more dynamic, rather than having to explicitly set children and grandchildren variables within the template.
Is there a better way to do this?
<ul class="sortable">
{% for cat in cats %}
{% set children = cat.getChildren %}
<li id="menuItem_{{ cat.id }}">
<div data-id="{{ cat.id }}">
<span>{{ cat.name }}</span>
</div>
{% for child in children %}
{% set grandchildren = child.getChildren %}
<ul>
<li id="menuItem_{{ child.id }}">
<div data-id="{{ child.id }}">
{{ child.name }}
</div>
{% for grandchild in grandchildren %}
<ul>
<li id="menuItem_{{ grandchild.id }}">
<div data-id="{{ grandchild.id }}">
{{ grandchild.name }}
</div>
</li>
</ul>
{% endfor %}
</li>
</ul>
{% endfor %}
</li>
{% endfor %}
</ul>
so what you are trying to achieve is recursive parsing of a tree in twig right?
If so, have a look at macros
.
{% import _self as macros %}
{% macro showChild(object) %}
{% import _self as macros %}
<ul>
{% for child in object.children %}
{{ macros.showChild(child) }}
{% endfor %}
<li id="menuItem_{{ object.id }}">
<div data-id="{{ object.id }}">
{{ object.name }}
</div>
</li>
</ul>
{% endmacro %}
<ul class="sortable">
{% for cat in cats %}
{{ macros.showChild(cat) }}
{% endfor %}
</ul>
that's all :)
let me know if you need help
EDIT 1:
If you want to use the macro in another file, remove the "import _self" line and just import it with an alias in another file:
index.html.twig:
{% import 'macro_file_name.html.twig' as macros %}
then you can use the same notation to call it

Django CMS Multi-Level Dropdown Menu

Im kinda new to Django CMS and im trying my best to avoid asking, but this one drives me crazy.
I made a Wiki app with a Topic, and Category model. I hooked it to a Site on my CMS and added it to my Menu. Now i would like to be able to show all Top-Level categories, their Child Categories & Topics, and the Child categories of these, and so on, on my menu.
Menu/Navigation should look like this:
Wiki
Category1
Category1.1
Topic
Category1.2
Topic
Category2
Topic
Category3
...
Right now i can only show the Top categories:
Wiki
Category1
Category2
Category3
I Already created a menu.py to get a Custom SubMenu on my Wiki (the one you see above):
menu.py
class WikiSubMenu(CMSAttachMenu):
name = _("Wiki Sub-Menu")
def get_nodes(self, request):
nodes = []
categories = Category.objects.filter(parent_id__isnull=True)
for c in categories:
node = NavigationNode(
mark_safe(c.name),
c.get_absolute_url(),
c.id,
)
nodes.append(node)
return nodes
menu_pool.register_menu(WikiSubMenu)
My Category Model:
class Category(models.Model):
''' Category model. '''
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
parent = models.ForeignKey(
'self',
null=True,
blank=True,
related_name='children'
)
sort = models.IntegerField(default=0)
class Meta:
ordering = ['sort', 'name']
def __unicode__(self):
return self.name
#models.permalink
def get_absolute_url(self):
return ('topics:categories_category_detail', (), {'slug': self.slug})
def get_all_children(self):
return Category.objects.filter(parent=self)
Now, is it possible to create a Sub-SubMenu, for all Categories with Childs, and their Childs, and their Childs, and so on?
Thanks for help & sorry for bad english
-- EDIT : --
I just found that:
docs.django-cms.org/en/3.0.6/extending_cms/app_integration.html#integration-modifiers
(Removed direct link to add 2 new Links, sorry for that)
I think that is what im looking for, i was kinda blind that i didn't found it. I'll try it out and Post the Answer if it worked out.
-- EDIT (AGAIN): --
The modifier didn't worked for me, but i got a whole piece further,
i read the Docs again, and found that i can give the NavigationNodes an optional attr dictonary, which i filled with all Categories with parent=c, on that way i had the data i needed, then i found that real nice bootstrap dropdown menu, that does exacly what i wanted. So my code until now looks like that:
menu.py
class TopicsSubMenu(CMSAttachMenu):
name = _("Wiki Sub-Menu")
def get_nodes(self, request):
nodes = []
categories = Category.objects.filter(parent_id__isnull=True)
for c in categories:
node = NavigationNode(
mark_safe(c.name),
c.get_absolute_url(),
c.pk,
attr=dict(
subcategories=Category.objects.filter(parent=c),),
)
nodes.append(node)
return nodes
And my Template:
menu.html
{% for child in children %}
<li>
{% if child.children %}
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
{{ child.get_menu_title }}
<span class="caret">
</span>
</a>
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
{% for child in child.children %}
{% if child.attr.subcategories.count %}
<li class="dropdown-submenu">
<a tabindex="-1" href="#">{{ child.get_menu_title }}</a>
<ul class="dropdown-menu">
{% for subcategory in child.attr.subcategories %}
<li>
<a tabindex="-1" href="{{ subcategory.get_absolute_url }}">{{ subcategory }}</a>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>{{ child.get_menu_title }}</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<a href="{{ child.get_absolute_url }}">
<span>
{{ child.get_menu_title }}
</span>
</a>
{% endif %}
</li>
{% if class and forloop.last and not forloop.parentloop %}
{% endif %}
{% endfor %}
My next step will be to write the whole "for" loops from the template in a Method, make it recursive with a while loop or something and post the result as Answer.
I hope i can help someone with that stuff :)
WOHO! I finally did it!
base.html
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% show_menu 0 100 100 100 "menu.html" %}
</ul>
</div>
menu.html:
{% for child in children %}
<li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}{% if child.children %} dropdown{% endif %}">
<a {% if child.children %}class="dropdown-toggle" data-toggle="dropdown"{% endif %} href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">
<span>{{ child.get_menu_title }}</span>{% if child.children|length %}<span class="caret"></span>{% endif %}
</a>
{% if child.children %}
<ul class="dropdown-menu multi-level" role="menu" aria-labelledby="dropdownMenu">
{% show_menu from_level to_level extra_inactive extra_active "dropdownmenu.html" "" "" child %}
</ul>
{% endif %}
</li>
{% if class and forloop.last and not forloop.parentloop %}{% endif %}
{% endfor %}
and my dropdownmenu.html:
(The recursive stuff starts here)
{% load i18n menu_tags cache mptt_tags %}
{% for child in children %}
<li {% if child.children %}class="dropdown-submenu"{% else %} {% endif %}>
<a tabindex="-1" href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}</a>
{% if child.children %}
<ul class="dropdown-menu">
{% show_menu from_level to_level extra_inactive extra_active "dropdownmenu.html" "" "" child %}
</ul>
{% endif %}
</li>
{% endfor %}
and the most important, menu.py:
class TopicsSubMenu(CMSAttachMenu):
name = _("Wiki Sub-Menu")
def get_nodes(self, request):
nodes = []
categories = Category.objects.all()
for c in categories:
node = NavigationNode(
mark_safe(c.name),
c.get_absolute_url(),
c.pk
)
if c.parent:
node.parent_id = c.parent_id
nodes.append(node)
topics = Topic.objects.all().exclude(category__isnull=True)
for t in topics:
node = NavigationNode(
mark_safe(t.title),
t.get_absolute_url(),
t.pk,
t.category.all()[0].id,
parent_namespace="TopicsSubMenu"
)
nodes.append(node)
return nodes
menu_pool.register_menu(TopicsSubMenu)
And thats it!

Resources