================================= Wagtailmenus 2.5.0 release notes ================================= .. contents:: :local: :depth: 1 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: :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: :ref:`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 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. '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: .. code-block:: python 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 subclassing ``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: .. code-block:: python 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 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.