-
-
Save ojii/1328081 to your computer and use it in GitHub Desktop.
Attempt to understand the menu system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
========================= | |
How the menu system works | |
========================= | |
Basic concepts | |
============== | |
Registration | |
------------ | |
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. | |
The parts are collected by trying to load any 'menu' module from all the applications in INSTALLED_APPS. Import errors in your menu.py files will fail silently. It's also possible but not encouraged to register parts of the menu in other auto-loaded files such as models.py. | |
The Menu class, base.Menu | |
------------------------- | |
A class based on Menu - such as cms.menu.CMSMenu(Menu). Should expose a method get_nodes which returns a list of NavigationNode instances. Those are added to the full list of nodes in a menu. | |
The Modifier class, base.Modifier | |
--------------------------------- | |
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. | |
NavigationNode, base.NavigationNode | |
----------------------------------- | |
Each node is a NavigationNode, with attributes such as URL, title, parent and children - as one would expect in a navigation tree. | |
Some attributes on this object are added after initiation, some even by modifieres. Therefore dynamically adding NavigationNodes to the list of nodes from modifiers might have unexpected side effects like attribtues that should be populated being empty. | |
Tracing the logic of the menu system | |
==================================== | |
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment