Skip to content

Releases: jazzband/wagtailmenus

v2.6.0

22 Dec 21:56
Compare
Choose a tag to compare

Wagtailmenus 2.6 is designated a Long Term Support (LTS) release. Long Term Support releases will continue to receive maintenance updates as necessary to address security and data-loss related issues, up until the next LTS release (for at least 8 months).

Wagtailmenus 2.6 will be the last LTS release to support Python 2 or Python 3.3.

Wagtailmenus 2.6 will be the last LTS release to support Wagtail versions 1.5 to 1.9.

What's new?

Improved compatibility with alternative template backends

Wagtailmenus has been updated to use backend-specific templates for rendering, making it compatible with template backends other than Django's default backend (such as jinja2).

Although the likelihood of this new behavior introducing breaking changes to projects is minute, it is turned OFF by default for now, in order to give developers time to make any necessary changes. However, by version 2.8 the updated behaviour will replace the old behaviour completely, becoming non optional.

To start using wagtailmenus with an alternative backend now (or to test your project's compatibility in advance), you can turn the updated behaviour ON by adding the following to your project's settings:

WAGTAILMENUS_USE_BACKEND_SPECIFIC_TEMPLATES = True

Thank you to Nguyễn Hồng Quân (hongquan) for contributing this!

New tabbed interface for menu editing

In an effort to improve the menu editing UI, Wagtail's TabbedInterface is now used to split a menu's fields into two tabs for editig: Content and Settings; with the latter including panels for the max_levels and use_specific fields (which were previously tucked away at the bottom of the edit page), and the former for everything else.

Two new attributes, content_panels and settings_panels have also been added to AbstractMainMenu and AbstractFlatMenu to allow the panels for each tab to be updated independently.

If for any reason you don't wish to use the tabbed interface for editing custom menu models, the panels attribute is still supported, and will setting that will result in all fields appearing in a single list (as before). However, the panels attribute currently present on the AbstractFlatMenu and AbstractMainMenu models is now deprecated and will be removed in the future releases (see below for more info).

Built-in compatibility with wagtail-condensedinlinepanel

In an effort to improve the menu editing UI, wagtailmenus now has baked-in compatibility with wagtail-condensedinlinepanel. As long as a compatible version (at least 0.3) of the app is installed, wagtailmenus will automatically use CondensedInlinePanel instead of Wagtail's built-in InlinePanel for listing menu items, giving menu editors some excellent additional features, including drag-and-drop reordering and the ability to add a new item into any position.

If you have custom Menu models in your project that use the panels attribute to customise arrangement of fields in the editing UI, you might need to change the your panel list slightly in order to see the improved menu items list after installing. Where you might currently have something like:

class CustomMainMenu(AbstractMainMenu):
    ...

    panels = (
        ...
        InlinePanel('custom_menu_items'),
        ..
    )


class CustomFlatMenu(AbstractFlatMenu):
    ...

    panels = (
        ...
        InlinePanel('custom_menu_items'),
        ..
    )

You should import MainMenuItemsInlinePanel and FlatMenuItemsInlinePanel from wagtailmenus.panels and use them instead like so:

from wagtailmenus.panels import FlatMenuItemsInlinePanel, MainMenuItemsInlinePanel


class CustomMainMenu(AbstractMainMenu):
    ...

    panels = (
        ...
        MainMenuItemsInlinePanel(),  # no need to pass any arguments!
        ..
    )


class CustomFlatMenu(AbstractFlatMenu):
    ...

    panels = (
        ...
        FlatMenuItemsInlinePanel(),  # no need to pass any arguments!
        ..
    )

Minor changes & bug fixes

  • Updated tests to test compatibility with Wagtail 1.13.

Deprecations

Menu.get_template_engine()

This method is deprecated in favour of using Django's generic 'get_template' and 'select_template' methods, which return backend-specific template instances instead of django.template.Template instances.

AbstractMainMenu.panels and AbstractFlatMenu.panels

If you are referencing AbstractMainMenu.panels or AbstractFlatMenu.panels anywhere, you should update your code to reference the content_panels or settings_panels attribute instead, depending on which panels you're trying to make use of.

If you're overriding the panels attribute on a custom menu model in order to make additional fields available in the editing UI (or change the default field display order), you might also want to think about updating your code to override the content_panels and settings_panels attributes instead, which will result in fields being split between two tabs (Content and Settings). However, this is entirely optional.

Upgrade considerations

v2.5.2

18 Dec 13:56
Compare
Choose a tag to compare

There are no changes in this release

v2.5.1

28 Oct 16:31
Compare
Choose a tag to compare

This is a maintenence release to fix a bug that was resulting in Django creating additional migrations for the app when running the makemigrations command after changing the project's default language to something other than "en".

Thanks to @philippbosch from A Color Bright for submitting the fix.

v2.4.3

28 Oct 16:32
Compare
Choose a tag to compare

This is a maintenence release to fix a bug that was resulting in Django creating additional migrations for the app when running the makemigrations command after changing the project's default language to something other than "en".

Thanks to @philippbosch from A Color Bright for submitting the fix.

v2.4.2

28 Oct 16:32
Compare
Choose a tag to compare

There are no changes in this release

v2.5.0

14 Oct 19:59
Compare
Choose a tag to compare

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:

  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 indentifies 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: 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 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 pag...

Read more

v2.5rc1

02 Oct 10:56
Compare
Choose a tag to compare
v2.5rc1 Pre-release
Pre-release

This is a release candidate version. Please report any bugs or issues to the tracker at https://github.com/rkhleics/wagtailmenus/issues

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:

  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 indentifies 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 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:

.. 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 subclasssing MenuItemManger to create manage...

Read more

v2.4.1

16 Sep 09:57
Compare
Choose a tag to compare

This is a maintenence release to add a migration that should have been included in the previous release, but wasn't. Thanks to Stuart George (@stuartaccent) for reporting and submitting the fix.

If you experience problems after upgrading from 2.4.0 to 2.4.1 (due to your project creating it's own, conflicting migration), try running pip uninstall wagtailmenus first, before installing the latest version

v2.4.0

04 Aug 22:39
Compare
Choose a tag to compare

What's new?

Check out the new documentation!

It's been a long wait, but I finally got around to making it happen. Wagtailmenus now has
easily navigatable and searchable documentation, kindly hosted by readthedocs.org. Find it at http://wagtailmenus.readthedocs.io/

New get_text_for_repeated_menu_item() method on MenuPageMixin and MenuPage models

The new method is called by get_repeated_menu_item() to get a string to use to populate the text attribute on repeated menu items.

The method is designed to be overriden in cases where the text value needs to come from different fields. e.g. in multilingual site where different translations of 'repeated_item_text' must be surfaced.

By default, if the repeated_item_text field is left blank, the WAGTAILMENUS_PAGE_FIELD_FOR_MENU_ITEM_TEXT is respected, instead of
just returning Page.title.

New use_absolute_page_urls param added to template tags

The new parameter allows you to render menus that use 'absolute' URLs for pages (including the protocol/domain derived from the relevant wagtailcore.models.Site object), instead of the 'relative' URLs used by default.

Other minor changes

  • Adjusted Meta classes on menu item models so that common behaviour is defined once in AbastractMenuItem.Meta.
  • Refactored the AbstractMenuItem's menu_text property method to improve code readability, and better handle instances where neither link_text or link_page are set.

Upgrade considerations

The signature of the modify_submenu_items() and get_repeated_menu_item() methods on MenuPage and MenuPageMixin models has been updated to accept a new use_absolute_page_urls keyword argument.

If you're overrding either of these methods in your project, you should think about updating the signatures of those methods to accept the new argument and pass it through when calling super(), like in the following example:

    from wagtailmenus.models import MenuPage


    class ContactPage(MenuPage):
        ...

        def modify_submenu_items(
            self, menu_items, current_page, current_ancestor_ids, 
            current_site, allow_repeating_parents, apply_active_classes,
            original_menu_tag, menu_instance, request, use_absolute_page_urls,
        ):
            # Apply default modifications first of all
            menu_items = super(ContactPage, self).modify_submenu_items(
                menu_items, current_page, current_ancestor_ids, current_site, allow_repeating_parents, apply_active_classes, original_menu_tag,
                menu_instance, request, use_absolute_page_urls
            )
            """
            If rendering a 'main_menu', add some additional menu items to the end
            of the list that link to various anchored sections on the same page
            """
            if original_menu_tag == 'main_menu':
                base_url = self.relative_url(current_site)
                menu_items.extend((
                    {
                        'text': 'Get support',
                        'href': base_url + '#support',
                        'active_class': 'support',
                    },
                    {
                        'text': 'Speak to someone',
                        'href': base_url + '#call',
                        'active_class': 'call',
                    },
                    {
                        'text': 'Map & directions',
                        'href': base_url + '#map',
                        'active_class': 'map',
                    },
                ))
            return menu_items

        def get_repeated_menu_item(
            self, current_page, current_site, apply_active_classes,
            original_menu_tag, request, use_absolute_page_urls,
        ):
            item = super(ContactPage, self).get_repeated_menu_item(
                current_page, current_site, apply_active_classes,
                original_menu_tag, request, use_absolute_page_urls,
            )
            item.text = 'Eat. Sleep. Rave. Repeat!'
            return item

If you choose NOT to update your versions of those methods to accept the use_absolute_page_urls keyword argument, you will continue to see deprecation warnings until version 2.6.0, when it will be a requirement, and your existing code will no longer work.

You might want to consider adopting a more future-proof approach to overriding the methods from MenuPage and MenuPageMixin, so that new keyword arguments added in future will be catered for automatically.

Below shows a version of the above code example, modified to use **kwargs in methods:

    from wagtailmenus.models import MenuPage


    class ContactPage(MenuPage):
        ...

        def modify_submenu_items(self, menu_items, **kwargs):
            # Apply default modifications first of all
            menu_items = super(ContactPage, self).modify_submenu_items(menu_items, **kwargs)
            """
            If rendering a 'main_menu', add some additional menu items to the end
            of the list that link to various anchored sections on the same page
            """
            if kwargs['original_menu_tag'] == 'main_menu':
                base_url = self.relative_url(kwargs['current_site'])
                menu_items.extend((
                    {
                        'text': 'Get support',
                        'href': base_url + '#support',
                        'active_class': 'support',
                    },
                    {
                        'text': 'Speak to someone',
                        'href': base_url + '#call',
                        'active_class': 'call',
                    },
                    {
                        'text': 'Map & directions',
                        'href': base_url + '#map',
                        'active_class': 'map',
                    },
                ))
            return menu_items

        def get_repeated_menu_item(self, current_page, **kwargs):
            item = super(ContactPage, self).get_repeated_menu_item(current_page, **kwargs)
            item.text = 'Eat. Sleep. Rave. Repeat!'
            return item

v2.3.2

21 Jul 11:17
Compare
Choose a tag to compare

This is a minor maintenance / bug fix release.

  • Fixed a bug that would result in {% sub_menu %} being called recursively (until raising a "maximum recursion depth exceeded" exception) if a 'repeated menu item' was added at anything past the 2nd level. Thanks to @pyMan for raising/investigating.