Skip to content

Commit

Permalink
Merge pull request #6582 from evildmp/evildmp/menu-system-logic
Browse files Browse the repository at this point in the history
Improved menu logic description
  • Loading branch information
evildmp committed Dec 14, 2018
2 parents e6b5553 + 5f03ce7 commit 9863ecd
Showing 1 changed file with 87 additions and 41 deletions.
128 changes: 87 additions & 41 deletions docs/topics/menu_system.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,44 +145,90 @@ to, rather than placing them directly on the node itself, where they might clash
Menu system logic
*****************

Let's look at an example using the {% show_menu %} template tag. It will be different for other template tags, and your applications might have their own menu classes. But this should help explain what's going on and what the menu system is doing.

One thing to understand is that the system passes around a list of ``nodes``, doing various things to it.

Many of the methods below pass this list of nodes to the ones it calls, and return them to the ones that they were in turn called by.

Don't forget that show_menu recurses - so it will do *all* of the below for *each level* in the menu.

* ``{% show_menu %}`` - the template tag in the template
* :py:meth:`menus.templatetags.menu_tags.ShowMenu.get_context()`
* :py:meth:`menus.menu_pool.MenuPool.get_nodes()`
* :py:meth:`menus.menu_pool.MenuPool.discover_menus()` checks every application's ``cms_menus.py``, and registers:
* Menu classes, placing them in the ``self.menus`` dict
* Modifier classes, placing them in the self.modifiers list
* :py:meth:`menus.menu_pool.MenuPool._build_nodes()`
* checks the cache to see if it should return cached nodes
* loops over the Menus in self.menus (note: by default the only generator is :py:class:`cms.menu.CMSMenu`); for each:
* call its :py:meth:`menus.base.Menu.get_nodes()` - the menu generator
* :py:func:`menus.menu_pool._build_nodes_inner_for_one_menu()`
* adds all nodes into a big list
* :py:meth:`menus.menu_pool.MenuPool.apply_modifiers()`
* :py:meth:`menus.menu_pool.MenuPool._mark_selected()`
* loops over each node, comparing its URL with the request.path_info, and marks the best match as ``selected``
* loops over the Modifiers in ``self.modifiers`` calling each one's :py:meth:`~menus.base.Modifier.modify()` with ``post_cut=False``. The default Modifiers are:
* :py:class:`cms.menu.NavExtender`
* :py:class:`cms.menu.SoftRootCutter` removes all nodes below the appropriate soft root
* :py:class:`menus.modifiers.Marker` loops over all nodes; finds selected, marks its ancestors, siblings and children
* :py:class:`menus.modifiers.AuthVisibility` removes nodes that require authorisation to see
* :py:class:`menus.modifiers.Level` loops over all nodes; for each one that is a root node (``level == 0``) passes it to:
* :py:meth:`~menus.modifiers.Level.mark_levels()` recurses over a node's descendants marking their levels
* we're now back in :py:meth:`menus.templatetags.menu_tags.ShowMenu.get_context()` again
* if we have been provided a root_id, get rid of any nodes other than its descendants
* :py:meth:`menus.templatetags.menu_tags.cut_levels()` removes nodes from the menu according to the arguments provided by the template tag
* :py:meth:`menus.menu_pool.MenuPool.apply_modifiers()` with ``post_cut = True`` loops over all the Modifiers again
* :py:class:`cms.menu.NavExtender`
* :py:class:`cms.menu.SoftRootCutter`
* :py:class:`menus.modifiers.Marker`
* :py:class:`menus.modifiers.AuthVisibility`
* :py:class:`menus.modifiers.Level`:
* :py:meth:`menus.modifiers.Level.mark_levels()`
* return the nodes to the context in the variable ``children``
Let's look at an example using the ``{% show_menu %}`` template tag. It will be different for other
template tags, and your applications might have their own menu classes. But this should help
explain what's going on and what the menu system is doing.

One thing to understand is that the system passes around a list of ``nodes``, doing various things
to it.

Many of the methods below pass this list of nodes to the ones it calls, and return them to the ones
that they were in turn called by.


The ``ShowMenu.get_context()`` method
=====================================

When the Django template engine encounters the ``{% show_menu %}`` template tag, it calls
the :py:meth:`get_context() <menus.templatetags.menu_tags.ShowMenu.get_context()>` of the ``ShowMenu`` class. ``get_context()``:

* calls :py:meth:`menus.menu_pool.MenuPool.get_nodes()` (see :ref:`get_nodes_method` below)
* cuts any nodes other than its descendants (if a ``root_id`` has been provided)
* calls :py:meth:`menus.templatetags.menu_tags.cut_levels()` to remove unwanted levels
* calls :py:meth:`menus.menu_pool.MenuPool.apply_modifiers()` with ``post_cut = True``
* return the nodes to the context in the variable ``children``


.. _get_nodes_method:

The ``MenuPool.get_nodes()`` method
===================================

:py:meth:`menus.menu_pool.MenuPool.get_nodes()` calls three other methods of ``MenuPool`` in turn:

* :py:meth:`menus.menu_pool.MenuPool.discover_menus()`

Checks every application's ``cms_menus.py``, and registers:
* Menu classes, placing them in the ``self.menus`` dict
* Modifier classes, placing them in the self.modifiers list

* :py:meth:`menus.menu_pool.MenuPool._build_nodes()`

* checks the cache to see if it should return cached nodes
* loops over the Menus in self.menus (note: by default the only generator is
:py:class:`cms.menu.CMSMenu`); for each:

* calls its :py:meth:`menus.base.Menu.get_nodes()` - the menu generator
* :py:func:`menus.menu_pool._build_nodes_inner_for_one_menu()`
* adds all nodes into a big list

* :py:meth:`menus.menu_pool.MenuPool.apply_modifiers()`

* :py:meth:`menus.menu_pool.MenuPool._mark_selected()`
* loops over each node, comparing its URL with the request.path_info, and marks the best match
as ``selected``
* loops over the Modifiers (see :ref:`menu-modifiers` below) in ``self.modifiers`` calling each
one's
:py:meth:`~menus.base.Modifier.modify()` with ``post_cut=False``.


.. _menu-modifiers:

Menu Modifiers
==============

Each ``Modifier`` manipulates menu nodes and their attributes.

The default Modifiers, in the order they are called, are:

* :py:class:`cms.menu.NavExtender`
* :py:class:`cms.menu.SoftRootCutter`

If ``post_cut`` is ``True``, removes all nodes below the appropriate soft root; otherwise,
returns immediately.

* :py:class:`menus.modifiers.Marker`

If ``post_cut`` or ``breadcrumb`` is ``True``, returns immediately; otherwise, loops over all
nodes; finds selected, marks its ancestors, siblings and children

* :py:class:`menus.modifiers.AuthVisibility`

Removes nodes that require authorisation to see

* :py:class:`menus.modifiers.Level`

Loops over all nodes; for each one that is a root node (``level == 0``) passes it to:

* :py:meth:`~menus.modifiers.Level.mark_levels()` recurses over a node's descendants marking
their levels

0 comments on commit 9863ecd

Please sign in to comment.