Parts of the menu system, which can be from the menus application, or from another application (cms, or some other application entirely) are registered with the menu system.
Then, when a menu is built, the system allows the registered menu generators and modifiers to work on it.
A class based on Menu - such as cms.menu.CMSMenu(Menu) - adds nodes.
A class based on Modifier - such as cms.menu.NavExtender or cms.menu.SoftRootCutter - examines the nodes that have been assembled, and modifies them according to its requirements (adding, removing or otherwise marking them as it sees fit).
Each Modifer is called twice:
- first, by menu_pool.MenuPool.get_nodes(), with the argument post_cut = False
- later, by the templatetage, with the argument post_cut = True
This corresponds to the state of the nodes list before and after menus.templatetags.cut_levels(), which removes nodes from the menu according to the arguments provided by the templatetag.
Each node is a NavigationNode, with attributes such as URL, title, parent and children - as one would expect in a navigation tree.
Let's look at an example using the {% show_menu %} templatetag.
An blank line separates methods; an indentation represents a similar Python/logical indentation.
Each of the methods below passes a big list of nodes to the ones it calls, and returns them to the one that it was in turn called by.
{% show_menu %}:
menu_tags.ShowMenu.render():
menu_pool.MenuPool.get_nodes():
- menu_pool.MenuPool.discover_menus():
- [this loops over every application, checking the menu.py file; it registers any Menu classes that have not been registered and puts them all in the self.menus dict, does the same for Modifier classes, which go into the self.modifiers list]
- menu_pool.MenuPool._build_nodes():
[this first checks the to see if it should return cached nodes] [then, it loops over the Menus in self.menus - by default the only one is: * cms.menu.CMSMenu]:
cms.menu.CMSMenu.get_nodes() [the menu's own method for getting nodes]
menu_pool._build_nodes_inner_for_one_menu() [I don't really understand what this does]
adds all nodes into a big list ]
menu_pool.MenuPool.apply_modifiers():
- menu_pool.MenuPool._mark_selected():
- [loops over each node, comparing its URL with the request.path, and marks the best match as selected]
[loops over the Modifiers in self.modifiers - by default, these are: * cms.menu.NavExtender * cms.menu.SoftRootCutter * menus.modifiers.Marker * menus.modifiers.AuthVisibility * menus.modifiers.Level]:
cms.menu.NavExtender.modify() [needs a description]
cms.menu.SoftRootCutter.modify() [decribed below]
- menus.modifiers.Marker.modify():
- loops over all nodes
- once it has found the selected node, marks all its ancestors, siblings and children
menus.modifiers.AuthVisibility.modify() [removes nodes that require authorisation]
- menus.modifiers.Level.modify():
if post_cut = False, loops over all nodes; for each one that is a root node (level = 0) passes it to:
- menus.modifiers.Level.mark_levels():
- [recurses over a node's descendants marking their levels until it has reached them all]
[we are back in menu_tags.ShowMenu.render() again] if we have been provided a root_id, get rid of any nodes other than its descendants]
menus.templatetags.cut_levels() [removes nodes from the menu according to the arguments provided by the templatetag]
menu_pool.MenuPool.apply_modifiers(post_cut = True) [remember we did these earlier with post_cut = False]
returns the nodes to the context