Create a cms_menus.py in your application, with the following:
frommenus.baseimportMenu,NavigationNodefrommenus.menu_poolimportmenu_poolfromdjango.utils.translationimportgettext_lazyas_classTestMenu(Menu):defget_nodes(self,request):nodes=[]n=NavigationNode(_('sample root page'),"/",1)n2=NavigationNode(_('sample settings page'),"/bye/",2)n3=NavigationNode(_('sample account page'),"/hello/",3)n4=NavigationNode(_('sample my profile page'),"/hello/world/",4,3)nodes.append(n)nodes.append(n2)nodes.append(n3)nodes.append(n4)returnnodesmenu_pool.register_menu(TestMenu)
Note
Up to version 3.1 this module was named menu.py. Please
update your existing modules to the new naming convention.
Support for the old name will be removed in version 3.5.
If you refresh a page you should now see the menu entries above.
The get_nodes function should return a list of
NavigationNode instances. A
menus.base.NavigationNode takes the following arguments:
title
Text for the menu node
url
URL for the menu node link
id
A unique id for this menu
parent_id=None
If this is a child of another node, supply the id of the parent here.
parent_namespace=None
If the parent node is not from this menu you can give it the parent
namespace. The namespace is the name of the class. In the above example that
would be: TestMenu
attr=None
A dictionary of additional attributes you may want to use in a modifier or
in the template
To adapt your menus according to request dependent conditions (say: anonymous/logged in user), you
can use Navigation Modifiers or you can make use of existing ones.
For example it’s possible to add {'visible_for_anonymous':False}/{'visible_for_authenticated':False} attributes recognised by the django CMS core
AuthVisibility modifier.
Classes that extend from menus.base.Menu always get attached to the
root. But if you want the menu to be attached to a CMS Page you can do that as
well.
A simple example: you have a news application that publishes pages
independently of django CMS. However, you would like to integrate the
application into the menu structure of your site, so that at appropriate
places a News node appears in the navigation menu.
In another example, you might want a particular attribute of your Pages to be available in
menu templates. In order to keep menu nodes lightweight (which can be important in a site with
thousands of pages) they only contain the minimum attributes required to generate a usable menu.
In both cases, a Navigation Modifier is the solution - in the first case, to add a new node at the
appropriate place, and in the second, to add a new attribute - on the attr attribute, rather
than directly on the NavigationNode, to help avoid conflicts - to all nodes in the menu.
Place your modifiers in your application’s cms_menus.py.
To make your modifier available, it then needs to be registered with
menus.menu_pool.menu_pool.
Now, when a page is loaded and the menu generated, your modifier will
be able to inspect and modify its nodes.
Here is an example of a simple modifier that places each Page’s changed_by attribute in the corresponding
NavigationNode:
frommenus.baseimportModifierfrommenus.menu_poolimportmenu_poolfromcms.modelsimportPageclassMyExampleModifier(Modifier):""" This modifier makes the changed_by attribute of a page accessible for the menu system. """defmodify(self,request,nodes,namespace,root_id,post_cut,breadcrumb):# only do something when the menu has already been cutifpost_cut:# only consider nodes that refer to cms pages# and put them in a dict for efficient accesspage_nodes={n.id:nforninnodesifn.attr["is_page"]}# retrieve the attributes of interest from the relevant pagespages=Page.objects.filter(id__in=page_nodes.keys()).values('id','changed_by')# loop over all relevant pagesforpageinpages:# take the node referring to the pagenode=page_nodes[page['id']]# put the changed_by attribute on the nodenode.attr["changed_by"]=page['changed_by']returnnodesmenu_pool.register_modifier(MyExampleModifier)
It has a method modify() that should return a list
of NavigationNode instances.
modify() should take the following arguments:
request
A Django request instance. You want to modify based on sessions, or
user or permissions?
nodes
All the nodes. Normally you want to return them again.
namespace
A Menu Namespace. Only given if somebody requested a menu with only nodes
from this namespace.
root_id
Was a menu request based on an ID?
post_cut
Every modifier is called two times. First on the whole tree. After that the
tree gets cut to only show the nodes that are shown in the current menu.
After the cut the modifiers are called again with the final tree. If this is
the case post_cut is True.
breadcrumb
Is this a breadcrumb call rather than a menu call?
Here is an example of a built-in modifier that marks all node levels:
classLevel(Modifier):""" marks all node levels """post_cut=Truedefmodify(self,request,nodes,namespace,root_id,post_cut,breadcrumb):ifbreadcrumb:returnnodesfornodeinnodes:ifnotnode.parent:ifpost_cut:node.menu_level=0else:node.level=0self.mark_levels(node,post_cut)returnnodesdefmark_levels(self,node,post_cut):forchildinnode.children:ifpost_cut:child.menu_level=node.menu_level+1else:child.level=node.level+1self.mark_levels(child,post_cut)menu_pool.register_modifier(Level)
Navigation modifiers can quickly become a performance bottleneck. Each modifier is called
multiple times: For the breadcrumb (breadcrumb=True),
for the whole menu tree (post_cut=False),
for the menu tree cut to the visible part (post_cut=True) and perhaps for each level
of the navigation. Performing inefficient operations inside a navigation modifier
can hence lead to big performance issues.
Some tips for keeping a modifier implementation fast:
Specify when exactly the modifier is necessary (in breadcrumb, before or after cut).
Only consider nodes and pages relevant for the modification.
Perform as less database queries as possible (i.e. not in a loop).
In database queries, fetch exactly the attributes you are interested in.
If you have multiple modifications to do, try to apply them in the same method.