Releases: jazzband/wagtailmenus
v2.6.0
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
- Following the standard deprecation period, any modify_submenu_items() methods implemented on custom Page type models must now accept a 'use_absolute_page_urls' keyword argument. See the 2.4 release notes for more info: http://wagtailmenus.readthedocs.io/en/stable/releases/2.4.0.html
v2.5.2
v2.5.1
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
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
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 pag...
v2.5rc1
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:
-
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: :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 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:
.. 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...
v2.4.1
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
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
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.