v2.5.0
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 tempate 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:
-
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. -
render_from_tag()
calls the class'sget_contextual_vals_from_context()
method, which analyses the current context and returns aContextualVals
instance, which will serve as a convenient (read-only) reference for 'contextual' data throughout the rest of the process. -
render_from_tag()
calls the class'sget_option_vals_from_options()
method, which analyses the provided option values and returns anOptionVals
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
andopt_vals.template_name
), but some menu-specific options, or any additional values passed to the tag, will be stored as a dictionary, available asopt_vals.extra
. -
render_from_tag()
calls the class'sget_instance_for_rendering()
method, which takes the preparedContextualVals
andOptionVals
instances, and uses them to get or create and return a relevant instance to use for rendering. -
In order for the menu instance to handle the rest of the rendering process, it needs to be able to access the
ContextualVals
andOptionVals
instances that have already been prepared, so those values are passed to the instance'sprepare_to_render()
method, where references to them are saved on the instance as private attributes;self._contextual_vals
andself._option_vals
. -
With access to everything it needs, the instance's
render_to_template()
method is called. This in turn calls two more instance methods. -
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 ofmenu_items
for the current level, and other not-so-obvious things, which are intended to be picked up by thesub_menu
tag (if it's being used in the template to render additional levels). The menu items are provided by theget_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()
andmodify_menu_items()
, respectively. -
The
get_template()
method indentifies and returns an appropriateTemplate
instance that can be used for rendering. -
With the data and template gathered,
render_to_template()
then converts the data into aContext
object and sends it to the template'srender()
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: :ref:hooks
.
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.
'MenuItemManager.for_display()' now returns all items, regardless of the status of linked pages
When sourcing data for a main or flat menu, it doesn't make sense to apply two sets of filters relating to pages status/visibility, so 'for_display' now simply returns ALL menu items defined for a menu, and any unsuitable page links are filtered out in a menu instances 'top_level_items' by calling upon 'get_base_page_queryset'.
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 thesub_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 withcached_property
). - Added support for Wagtail 1.12
Upgrade considerations
The ChildrenMenu's 'root_page' attribute is deprectated 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.
'MenuWithMenuItems.get_base_menuitem_queryset()' no longer filters the queryset
By default, the queryset returned by 'get_base_menuitem_queryset' on menu instances will now return ALL menu items defined for that menu, regardless of the status / visibility of any linked pages.
Previously, the result was filtered to only include pages with 'live' status, and with a True 'show_in_menus' value.
If you're calling 'get_base_menuitem_queryset' anywhere in your project, and are relying on the original method to return the same value as it did before, you will need to apply the additional filters to the queryset, like so:
from django.db.models import Q
...
menu_item_qs = menu.get_base_menuitem_queryset()
menu_item_qs = menu_item_qs.filter(
Q(link_page__isnull=True) |
Q(link_page__live=True)
Q(link_page__expired=False) &
Q(link_page__show_in_menus=True)
)
'MenuItemManager.for_display()' no longer filters the queryset
If you are subclasssing MenuItemManger
to create managers for your custom menu item models, and are relying on the original 'for_display' method to filter out links based on their linked page's status/visibility, you may wish to revise your code to filter out the pages as before, like so:
from django.db.models import Q
from wagtailmenus.managers import MenuItemManager
...
class CustomMenuItemManager(MenuItemManager):
def for_display(self):
qs = super(CustomMenuItemManager, self).for_display()
qs = qs.filter(
Q(link_page__isnull=True) |
Q(link_page__live=True) &
Q(link_page__expired=False) &
Q(link_page__show_in_menus=True)
)
# Now apply any custom filters
...
# Return queryset
return qs
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 relevent 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.