Wagtailmenus 2.5.0 release notes

What’s new?

Class-based rendering behaviour for menus

This version of wagtailmenus sees quite a large refactor in an attempt to address a large amount of repetition and inconsistency in template tag code, and to also break the process of rendering menus down into more clearly defined steps that can be overridden individually where needed.

While the existing template tags remain, and are still the intended method for initiating rendering of menus from templates, their responsibilities have diminished somewhat. They are now only really responsible for performing basic validation on the option values passed to them - everything else is handled by the relevant Menu class.

The base Menu class defines the default ‘rendering’ logic and establishes a pattern of behaviour for the other classes to follow. Then, the more specific classes simply override the methods they need to produce the same results as before.

Below is an outline of the new process, once the menu tag has prepared it’s option values and is ready to hand things over to the menu class:

  1. The menu class’s render_from_tag() method is called. It takes the current context, as well as any ‘option values’ passed to / prepared by the template tag.

  2. render_from_tag() calls the class’s get_contextual_vals_from_context() method, which analyses the current context and returns a ContextualVals instance, which will serve as a convenient (read-only) reference for ‘contextual’ data throughout the rest of the process.

  3. render_from_tag() calls the class’s get_option_vals_from_options() method, which analyses the provided option values and returns an OptionVals instance, which will serve as a convenient (read-only) reference for ‘option’ data throughout the rest of the process. The most common attributes are accessible directly (e.g. opt_vals.max_levels and opt_vals.template_name), but some menu-specific options, or any additional values passed to the tag, will be stored as a dictionary, available as opt_vals.extra.

  4. render_from_tag() calls the class’s get_instance_for_rendering() method, which takes the prepared ContextualVals and OptionVals instances, and uses them to get or create and return a relevant instance to use for rendering.

  5. In order for the menu instance to handle the rest of the rendering process, it needs to be able to access the ContextualVals and OptionVals instances that have already been prepared, so those values are passed to the instance’s prepare_to_render() method, where references to them are saved on the instance as private attributes; self._contextual_vals and self._option_vals.

  6. With access to everything it needs, the instance’s render_to_template() method is called. This in turn calls two more instance methods.

  7. The get_context_data() method creates and returns a dictionary of values that need to be available in the template. This includes obvious things such as a list of menu_items for the current level, and other not-so-obvious things, which are intended to be picked up by the sub_menu tag (if it’s being used in the template to render additional levels). The menu items are provided by the get_menu_items_for_rendering() method, which in turn splits responsibility for sourcing, priming, and modifying menu items between three other methods: get_raw_menu_items(), prime_menu_items() and modify_menu_items(), respectively.

  8. The get_template() method identifies and returns an appropriate Template instance that can be used for rendering.

  9. With the data and template gathered, render_to_template() then converts the data into a Context object and sends it to the template’s render() method, creating a string representation of the menu, which is sent back for inclusion in the original template.

Hooks added to give developers more options for manipulating menus

While wagtailmenus has long supported the use of custom classes for most things (allowing developers to override methods as they see fit), for a long time, I’ve felt that it should be easier to override some core/shared behaviour without the technical overhead of having to create and maintain multiple custom models and classes. So, wagtailmenus now supports several ‘hooks’, which allow you to do just that.

They use the hooks mechanism from Wagtail, so you may already be familiar with the concept. For more information and examples, see the new section of the documentation: Using hooks to modify menus.

New ‘autopopulate_main_menus’ command added

The ‘autopopulate_main_menus’ command has been introduced to help developers integrate wagtailmenus into an existing project, by removing some of the effort that is often needed to populating main menu for each project from scratch. It’s been introduced as an extra (optional) step to the instruction in: Installing wagtailmenus.

Utilises the new add_menu_items_for_pages() method, mentioned below.

New ‘add_menu_items_for_pages()’ method added for main & flat menus

For each page in the provided PageQuerySet a menu item will be added to the menu, linking to that page. The method has was added to the MenuWithMenuItems model class, which is subclassed by AbstractMainMenu and AbstractFlatMenu, so you should be able to use it on custom menu model objects, as well as objects using the default models.

Overriding ‘get_base_page_queryset()’ now effects top-level menu items too

Previously, if you overrode get_base_page_queryset() on a custom main menu or flat menu model, the page-tree driven part of the menu (anything below the top-level) would respect that, but top-level menu items linking to pages excluded by get_base_page_queryset() would still be rendered.

Now, ‘top_level_items’ has been refactored to call get_base_page_queryset() to filter down and return page data for items at the top level too, so developers can always expect changes to get_base_page_queryset() to be reflected throughout entire menus.

Minor changes & bug fixes

  • Fixed an issue with runtests.py that was causing tox builds in Travis CI to report as successful, even when tests were failing. Contributed by Oliver Bestwalter (@obestwalter).

  • The stop_at_this_level argument for the sub_menu tag has been officially deprecated and the feature removed from documentation. It hasn’t worked for a few versions and nobody has mentioned it, so this is the first step to removing it completely.

  • Made the logic in ‘pages_for_display’ easier to override on custom menu classes by breaking it out into a separate ‘get_pages_for_display()’ method (that isn’t decorated with cached_property).

  • Added support for Wagtail 1.12

Upgrade considerations

The ChildrenMenu’s ‘root_page’ attribute is deprecated in favour of ‘parent_page’

In previous versions, the ChildrenMenu and SectionMenu classes both extended the same MenuFromRootPage class, which takes root_page as an init argument, then stores a reference to that page using an attribute of the same name.

The ChildrenMenu class has now been updated to use parent_page as an init argument and attribute name instead, which feels like a much better fit. This same terminology has also been adopted for the SubMenu class too.

If you’re subclassing the ChildrenMenu class in your project, please update any code referencing root_page to use parent_page instead. Support for the old name will be removed in version 2.7.

The sub_menu tag will raise an error if used in a non-menu template

Despite the docs always having stated that the ‘sub_menu’ tag is only intended for use in menu templates for other types of menu; Up until now, it has functioned similarly to the ‘children_menu’ tag if used in a regular Django template. But, if you try to call ‘sub_menu’ from anything other than a menu template now, a SubMenuUsageError error will now be raised.

I highly doubt this will trip anybody up, but sorry if it does. Recent versions of Django seem to swallow deprecation warnings when they occur in the course of rendering a template tag, so even if there were a deprecation period for this, the warnings probably wouldn’t have been seen by anyone.

wagtailmenus.models.menus.MenuFromRootPage is deprecated

With ChildrenMenu being refactored to use ‘parent_page’ as an attribute instead of ‘root_page’, and the new SubMenu menu class taking a similar approach, the MenuFromRootPage name only seems relevant to SectionMenu, so it has been deprecated in favour of using a more generically-named MenuFromPage class, which is subclassed by all three.

wagtailmenus.menu_tags.prime_menu_items() is deprecated

The method has been superseded by new logic added to the Menu class.

wagtailmenus.menu_tags.get_sub_menu_items_for_page() is deprecated

The method has been superseded by new logic added to the Menu class.

wagtailmenus.utils.misc.get_attrs_from_context() is deprecated

The method has been superseded by new logic added to the Menu class.

wagtailmenus.utils.template.get_template_names() is deprecated

The method has been superseded by new logic added to the Menu class.

wagtailmenus.utils.template.get_sub_menu_template_names() is deprecated

The method has been superseded by new logic added to the Menu class.