Welcome to the wagtailmenus documentation!

wagtailmenus is an open-source extension for Wagtail CMS to help you define, manage and render menus in a consistent, yet flexible way.

The current version is tested for compatiblily with the following:

  • Wagtail versions 1.5 to 1.13
  • Django versions 1.8 to 1.11
  • Python versions 2.7, 3.3, 3.4, 3.5 and 3.6

To find out more about what wagtailmenus does and why, see Overview and key concepts

To view the code, open an issue, or submit a pull request, view the wagtailmenus project on github.

Below are some useful links to help you get you started:

Full index

Overview and key concepts

Better control over top-level menu items

When you have a ‘main navigation’ menu powered purely by the page tree, things can get a little tricky, as they are often designed in a way that is very sensitive to change. Some moderators might not understand that publishing a new page at the top level (or reordering those pages) will dramatically affect the main navigation (and possibly even break the design). And really, why should they?

Wagtailmenus solves this problem by allowing you to choose exactly which pages should appear as top-level items. It adds new functionality to Wagtail’s CMS, allowing you to simply and effectively create and manage menus, using a familiar interface.

You can also use Wagtail’s built-in group permissions system to control which users have permission to make changes to menus.

Multi-level menus generated from your existing page tree

We firmly beleive that your page tree is the best place to define the structure, and the ‘natural order’ of pages within your site. Wagtailmenus only allows you to define the top-level items for each menu, because offering anything more would inevitably lead to site managers redefining parts of the page tree in multiple places, doomed to become outdated as the original tree changes over time.

To generate multi-level menus, wagtailmenus takes the top-level items you define for each menu and automatically combines it with your page tree, efficiently identifying ancestors for each selected pages and outputting them as sub-menus following the same structure and order.

You can prevent any page from appearing menus simply by setting show_in_menus to False. Pages will also no longer be included in menus if they are unpublished.

Define menus for all your project needs

Have you ever hard-coded a menu into a footer at the start of a project, only for those pages never to come into existence? Or maybe the pages were created, but their URLs changed later on, breaking the hard-coded links? How about ‘secondary navigation’ menus in headers?

As well as giving you control over your ‘main menu’, wagtailmenus allows you to manage any number of additional menus via the CMS as ‘flat menus’, meaning they too can benefit from page links that dynamically update to reflect tree position or status changes.

Don’t hard-code another menu again! CMS-managed menus allow you to make those ‘emergency changes’ and ‘last-minute tweaks’ without having to touch a single line of code.

Note

Despite the name, ‘flat menus’ can be configured to render as multi-level menus if you need them to.

Suitable for single-site or multi-site projects

While main menus always have to be defined for each site, for flat menus, you can support multiple sites using any of the following approaches:

  • Define a new menu for each site
  • Define a menu for your default site and reuse it for the others
  • Create new menus for some sites, but use the default site’s menu for others

You can even use different approaches for different flat menus in the same project. If you’d like to learn more, take a look at the fall_back_to_default_site_menus option in Supported arguments

A copy feature in also available from the flat menu management interface, allowing you to quickly and easily copy existing menus from one site to another.

In a multi-site project, you can also configure wagtailmenus to use separate sets of templates for each site for rendering (See Using preferred paths and names for your templates)

Use the default menu templates for rendering, or easily add your own

Each menu tag comes with a default template that’s designed to be fully accessible and compatible with Bootstrap 3. However, if you don’t want to use the default templates, wagtailmenus makes it easy to use your own, using whichever approach works best for you:

Installing wagtailmenus

  1. Install the package using pip:

    pip install wagtailmenus
    
  2. Add wagtailmenus and wagtail.contrib.modeladmin to the INSTALLED_APPS setting in your project settings:

    INSTALLED_APPS = [
        ...
        'wagtail.contrib.modeladmin',  # Don't repeat if it's there already
        'wagtailmenus',
    ]
    
  3. Add wagtailmenus.context_processors.wagtailmenus to the context_processors list in your TEMPLATES setting. The setting should look something like this:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            '   DIRS': [
                os.path.join(PROJECT_ROOT, 'templates'),
            ],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.contrib.auth.context_processors.auth',
                    'django.template.context_processors.debug',
                    'django.template.context_processors.i18n',
                    'django.template.context_processors.media',
                    'django.template.context_processors.request',
                    'django.template.context_processors.static',
                    'django.template.context_processors.tz',
                    'django.contrib.messages.context_processors.messages',
                    'wagtail.contrib.settings.context_processors.settings',
                    'wagtailmenus.context_processors.wagtailmenus',
                ],
            },
        },
    ]
    
  4. Run migrations to create database tables for wagtailmenus:

    python manage.py migrate wagtailmenus
    
  5. This step is optional. If you’re adding wagtailmenus to an existing project, and the tree for each site follows a structure similar to the example below, you may find it useful to run the ‘autopopulate_main_menus’ command to populate main menus for your site(s).

    However, this will only yield useful results if the ‘root page’ you’ve set for your site(s) is what you consider to be the ‘Home’ page, and the pages directly below that are the pages you’d like to link to in your main menu.

    For example, if your page structure looked like the following:

    Home (Set as 'root page' for the site)
    ├── About us
    ├── What we do
    ├── Careers
    |   ├── Vacancy one
    |   └── Vacancy two
    ├── News & events
    |   ├── News
    |   └── Events
    └── Contact us
    

    Running the command from the console:

    python manage.py autopopulate_main_menus
    

    Would create a main menu with the following items:

    • About us
    • What we do
    • Careers
    • News & events
    • Contact us

    If you’d like wagtailmenus to also include a link to the ‘home page’, you can use the ‘–add-home-links’ option, like so:

    python manage.py autopopulate_main_menus --add-home-links=True
    

    This would create a main menu with the following items:

    • Home
    • About us
    • What we do
    • Careers
    • News & events
    • Contact us

    Note

    The ‘autopopulate_main_menus’ command is meant as ‘run once’ command to help you get started, and will only affect menus that do not already have any menu items defined. Running it more than once won’t have any effect, even if you make changes to your page tree before running it again.

Installing wagtail-condensedinlinepanel

Although doing so is entirely optional, for an all-round better menu editing experience, we recommend using wagtailmenus together with wagtail-condensedinlinepanel (version 0.3 or above).

wagtail-condensedinlinepanel offers a React-powered alternative to Wagtail’s built-in InlinePanel with some great extra features that make it perfect for managing menu items; including drag-and-drop reordering and the ability to add new items at any position.

If you’d like to give it a try, follow the installation instructions below, and wagtailmenus will automatically use the app’s CollapsedInlinePanel class.

  1. Install the package using pip:

    pip install wagtail-condensedinlinepanel>=0.4
    
  2. Add condensedinlinepanel to the INSTALLED_APPS setting in your project settings:

    INSTALLED_APPS = [
        ...
        'condensedinlinepanel',
        ...
    ]
    

Note

If for some reason you want to use wagtail-condensedinlinepanel for other things, but would prefer NOT to use it for editing menus, you can make wagtailmenus revert to using standard InlinePanel by adding WAGTAILMENUS_USE_CONDENSEDINLINEPANEL = False to your project settings.

Managing main menus via the CMS

  1. Log into the Wagtail CMS for your project (as a superuser).

  2. Click on Settings in the side menu, then select Main menu from the options that appear.

  3. You’ll be automatically redirected to the an edit page for the current site (or the ‘default’ site, if the current site cannot be identified). For multi-site projects, a ‘site switcher’ will appear in the top right, allowing you to edit main menus for each site.

    Screenshot of main menu edit page in Wagtail admin
  4. Use the MENU ITEMS inline panel to define the root-level items. If you wish, you can use the handle field to specify an additional value for each item, which you’ll be able to access in a custom main menu template.

    Note

    Even if selected as menu items, pages must be ‘live’ and have a show_in_menus value of True in order to appear in menus. If you’re expecting to see new page links in a menu, but the pages are not showing up, edit the page and check whether the “Show in menus” checkbox is checked (found under the “Promote” tab by default).

  5. At the very bottom of the form, you’ll find the ADVANCED SETTINGS panel, which is collapsed by default. Click on the arrow icon next to the heading to reveal the Maximum levels and Specific usage fields, which you can alter to fit the needs of your project. For more information about specific usage see ‘Specific’ pages and menus.

  6. Click on the Save button at the bottom of the page to save your changes.

Managing flat menus via the CMS

The flat menu list

All of the flat menus created for a project will appear in the menu list page making it easy to find, update, copy or delete your menus later. As soon as you create menus for more than one site in a multi-site project, the listing page will give you additional information and filters to help manage your menus:

Screenshot showing the FlatMenu listing page for a multi-site setup

To access the flat menu list, do the following:

  1. Log into the Wagtail CMS for your project (as a superuser).
  2. Click on “Settings” in the side menu, then on “Flat menus”.

Adding a new flat menu

  1. From the listing page above, click the “Add flat menu” button

    Screenshot indicating the location of the "Add flat menu" button
  2. Fill out the form, choosing a unique-for-site “handle”, which you’ll use to reference the menu when using the {% flat_menu %} tag.

    Screenshot showing the FlatMenu edit interface

    Note

    If you know in advance what menus you’re likely to have in your project, you can define some pre-set choices for the handle field using the WAGTAILMENUS_FLAT_MENUS_HANDLE_CHOICES setting. When used, the handle field will become a select field, saving you from having to enter values manually.

  3. Use the “MENU ITEMS” inline panel to define the links you want the menu to have. If you wish, you can use the handle field to specify an additional value for each item, which you’ll be able to access in from within menu templates.

    Note

    Even if selected as menu items, pages must be ‘live’ and have a show_in_menus value of True in order to appear in menus. If you’re expecting to see new page links in a menu, but the pages are not showing up, edit the page and check whether the “Show in menus” checkbox is checked (found under the “Promote” tab by default).

  4. At the very bottom of the form, you’ll find the “ADVANCED SETTINGS” panel, which is collapsed by default. Click on the arrow icon next to the heading to reveal the Maximum levels and Specific usage fields, which you can alter to fit the needs of your project. For more information about usage specific pages in menus, see ‘Specific’ pages and menus

  5. Click on the Save button at the bottom of the page to save your changes.

Rendering menus

Template tags reference

The main_menu tag

The main_menu tag allows you to display the MainMenu defined for the current site in your Wagtail project, with CSS classes automatically applied to each item to indicate the current page or ancestors of the current page. It also does a few sensible things, like never adding the ‘ancestor’ class for a homepage link, or outputting children for it.

Example usage
...
{% load menu_tags %}
...
{% main_menu max_levels=3 use_specific=USE_SPECIFIC_TOP_LEVEL template="menus/custom_main_menu.html" sub_menu_template="menus/custom_sub_menu.html" %}
...
Supported arguments
show_multiple_levels
Required? Expected value type Default value
No bool True

Adding show_multiple_levels=False to the tag in your template is essentially a more descriptive way of adding max_levels to 1.


max_levels
Required? Expected value type Default value
No int None

Provide an integer value to override the max_levels field value defined on your menu. Controls how many levels should be rendered (when show_multiple_levels is True).


use_specific
Required? Expected value type Default value
No int (see Supported values for fetching specific pages) None

Provide a value to override the use_specific field value defined on your main menu. Allows you to control how wagtailmenus makes use of PageQuerySet.specific() and Page.specific when rendering the menu. For more information and examples, see: Using the use_specific template tag argument.


allow_repeating_parents
Required? Expected value type Default value
No bool True

Item repetition settings set on each page are respected by default, but you can add allow_repeating_parents=False to ignore them, and not repeat any pages in sub-menus when rendering multiple levels.


apply_active_classes
Required? Expected value type Default value
No bool True

The tag will attempt to add ‘active’ and ‘ancestor’ CSS classes to the menu items (where applicable) to indicate the active page and ancestors of that page. To disable this behaviour, add apply_active_classes=False to the tag in your template.

You can change the CSS class strings used to indicate ‘active’ and ‘ancestor’ statuses by utilising the WAGTAILMENUS_ACTIVE_CLASS and WAGTAILMENUS_ACTIVE_ANCESTOR_CLASS settings.


template
Required? Expected value type Default value
No Template path (str) ''

Lets you render the menu to a template of your choosing. If not provided, wagtailmenus will attempt to find a suitable template automatically.

For more information about overriding templates, see: Using your own menu templates.

For a list of preferred template paths this tag, see: Preferred template paths for {% main_menu %}.



use_absolute_page_urls
Required? Expected value type Default value
No bool False

By default, relative page URLs are used for the href attribute on page links when rendering your menu. If you wish to use absolute page URLs instead, add use_absolute_page_urls=True to the main_menu tag in your template. The preference will also be respected automatically by any subsequent calls to {% sub_menu %} during the course of rendering the menu (unless explicitly overridden in custom menu templates ).


The flat_menu tag
Example usage
...
{% load menu_tags %}
...
{% flat_menu 'footer' max_levels=1 show_menu_heading=False  use_specific=USE_SPECIFIC_TOP_LEVEL  fall_back_to_default_site_menus=True %}
...
Supported arguments
handle
Required? Expected value type Default value
Yes str None

The unique handle for the flat menu you want to render, e.g. 'info', 'contact', or 'services'. You don’t need to include the handle key if supplying as the first argument to the tag (you can just do {% flat_menu 'menu_handle' %}).


show_menu_heading
Required? Expected value type Default value
No bool True

Passed through to the template used for rendering, where it can be used to conditionally display a heading above the menu.


show_multiple_levels
Required? Expected value type Default value
No bool True

Flat menus are designed for outputting simple, flat lists of links. But, you can alter the max_levels field value on your FlatMenu objects in the CMS to enable multi-level output for specific menus. If you want to absolutely never show anything but the MenuItem objects defined on the menu, you can override this behaviour by adding show_multiple_levels=False to the tag in your template.


max_levels
Required? Expected value type Default value
No int None

Provide an integer value to override the max_levels field value defined on your menu. Controls how many levels should be rendered (when show_multiple_levels is True).


use_specific
Required? Expected value type Default value
No int (see Supported values for fetching specific pages) None

Provide a value to override the use_specific field value defined on your flat menu. Allows you to control how wagtailmenus makes use of PageQuerySet.specific() and Page.specific when rendering the menu.

For more information and examples, see: Using the use_specific template tag argument.


apply_active_classes
Required? Expected value type Default value
No bool False

Unlike main_menu and section_menu, flat_menu will NOT attempt to add 'active' and 'ancestor' classes to the menu items by default, as this is often not useful. You can override this by adding apply_active_classes=true to the tag in your template.

You can change the CSS class strings used to indicate ‘active’ and ‘ancestor’ statuses by utilising the WAGTAILMENUS_ACTIVE_CLASS and WAGTAILMENUS_ACTIVE_ANCESTOR_CLASS settings.


allow_repeating_parents
Required? Expected value type Default value
No bool True

Repetition-related settings on your pages are respected by default, but you can add allow_repeating_parents=False to ignore them, and not repeat any pages in sub-menus when rendering. Please note that using this option will only have an effect if use_specific has a value of 1 or higher.


fall_back_to_default_site_menus
Required? Expected value type Default value
No bool False

When using the flat_menu tag, wagtailmenus identifies the ‘current site’, and attempts to find a menu for that site, matching the handle provided. By default, if no menu is found for the current site, nothing is rendered. However, if fall_back_to_default_site_menus=True is provided, wagtailmenus will search search the ‘default’ site (In the CMS, this will be the site with the ‘Is default site’ checkbox ticked) for a menu with the same handle, and use that instead before giving up.

The default value can be changed to True by utilising the WAGTAILMENUS_FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS setting.


template
Required? Expected value type Default value
No Template path (str) ''

Lets you render the menu to a template of your choosing. If not provided, wagtailmenus will attempt to find a suitable template automatically.

For more information about overriding templates, see: Using your own menu templates.

For a list of preferred template paths this tag, see: Preferred template paths for {% flat_menu %}.


sub_menu_template
Required? Expected value type Default value
No Template path (str) ''

Lets you specify a template to be used for rendering sub menus (if enabled using show_multiple_levels). All subsequent calls to {% sub_menu %} within the context of the flat menu will use this template unless overridden by providing a template value to {% sub_menu %} directly in a menu template. If not provided, wagtailmenus will attempt to find a suitable template automatically.

For more information about overriding templates, see: Using your own menu templates.

For a list of preferred template paths this tag, see: Preferred template paths for {% flat_menu %}.


use_absolute_page_urls
Required? Expected value type Default value
No bool False

By default, relative page URLs are used for the href attribute on page links when rendering your menu. If you wish to use absolute page URLs instead, add use_absolute_page_urls=True to the {% flat_menu %} tag in your template. The preference will also be respected automatically by any subsequent calls to {% sub_menu %} during the course of rendering the menu (unless explicitly overridden in custom menu templates).


The section_menu tag

The section_menu tag allows you to display a context-aware, page-driven menu in your project’s templates, with CSS classes automatically applied to each item to indicate the active page or ancestors of the active page.

Example usage
...
{% load menu_tags %}
...
{% section_menu max_levels=3 use_specific=USE_SPECIFIC_OFF template="menus/custom_section_menu.html" sub_menu_template="menus/custom_section_sub_menu.html" %}
...
Supported arguments
show_section_root
Required? Expected value type Default value
No bool True

Passed through to the template used for rendering, where it can be used to conditionally display the root page of the current section.


max_levels
Required? Expected value type Default value
No int 2

Lets you control how many levels of pages should be rendered (the section root page does not count as a level, just the first set of pages below it). If you only want to display the first level of pages below the section root page (whether pages linked to have children or not), add max_levels=1 to the tag in your template. You can display additional levels by providing a higher value.

The default value can be changed by utilising the WAGTAILMENUS_DEFAULT_SECTION_MENU_MAX_LEVELS setting.


use_specific
Required? Expected value type Default value
No int (see Supported values for fetching specific pages) 1 (Auto)

Allows you to control how wagtailmenus makes use of PageQuerySet.specific() and Page.specific when rendering the menu, helping you to find the right balance between functionality and performance.

For more information and examples, see: Using the use_specific template tag argument.

The default value can be altered by utilising the WAGTAILMENUS_DEFAULT_SECTION_MENU_USE_SPECIFIC setting.


show_multiple_levels
Required? Expected value type Default value
No bool True

Adding show_multiple_levels=False to the tag in your template essentially overrides max_levels to 1. It’s just a little more descriptive.


apply_active_classes
Required? Expected value type Default value
No bool True

The tag will add ‘active’ and ‘ancestor’ classes to the menu items where applicable, to indicate the active page and ancestors of that page. To disable this behaviour, add apply_active_classes=False to the tag in your template.

You can change the CSS class strings used to indicate ‘active’ and ‘ancestor’ statuses by utilising the WAGTAILMENUS_ACTIVE_CLASS and WAGTAILMENUS_ACTIVE_ANCESTOR_CLASS settings.


allow_repeating_parents
Required? Expected value type Default value
No bool True

Repetition-related settings on your pages are respected by default, but you can add allow_repeating_parents=False to ignore them, and not repeat any pages in sub-menus when rendering. Please note that using this option will only have an effect if use_specific has a value of 1 or higher.


template
Required? Expected value type Default value
No Template path (str) ''

Lets you render the menu to a template of your choosing. If not provided, wagtailmenus will attempt to find a suitable template automatically.

For more information about overriding templates, see: Using your own menu templates.

For a list of preferred template paths this tag, see: Preferred template paths for {% section_menu %}.


sub_menu_template
Required? Expected value type Default value
No Template path (str) ''

Lets you specify a template to be used for rendering sub menus. All subsequent calls to {% sub_menu %} within the context of the section menu will use this template unless overridden by providing a template value to {% sub_menu %} in a menu template. If not provided, wagtailmenus will attempt to find a suitable template automatically.

For more information about overriding templates, see: Using your own menu templates.

For a list of preferred template paths this tag, see: Preferred template paths for {% section_menu %}.


use_absolute_page_urls
Required? Expected value type Default value
No bool False

By default, relative page URLs are used for the href attribute on page links when rendering your menu. If you wish to use absolute page URLs instead, add use_absolute_page_urls=True to the {% section_menu %} tag in your template. The preference will also be respected automatically by any subsequent calls to {% sub_menu %} during the course of rendering the menu (unless explicitly overridden in custom menu templates).


The children_menu tag

The children_menu tag can be used in page templates to display a menu of children of the current page. You can also use the parent_page argument to show children of a different page.

Example usage
...
{% load menu_tags %}
...
{% children_menu some_other_page max_levels=2 use_specific=USE_SPECIFIC_OFF template="menus/custom_children_menu.html" sub_menu_template="menus/custom_children_sub_menu.html" %}
...
Supported arguments
parent_page
Required? Expected value type Default value
No A Page object None

Allows you to specify a page to output children for. If no alternate page is specified, the tag will automatically use self from the context to render children pages for the current/active page.


max_levels
Required? Expected value type Default value
No int 1

Allows you to specify how many levels of pages should be rendered. For example, if you want to display the direct children pages and their children too, add max_levels=2 to the tag in your template.

The default value can be changed by utilising the WAGTAILMENUS_DEFAULT_CHILDREN_MENU_MAX_LEVELS setting.


use_specific
Required? Expected value type Default value
No int (see Supported values for fetching specific pages) 1 (Auto)

Allows you to specify how wagtailmenus makes use of PageQuerySet.specific() and Page.specific when rendering the menu.

For more information and examples, see: Using the use_specific template tag argument.

The default value can be altered by adding a WAGTAILMENUS_DEFAULT_CHILDREN_MENU_USE_SPECIFIC setting to your project’s settings.


apply_active_classes
Required? Expected value type Default value
No bool False

Unlike main_menu and section_menu`, children_menu will NOT attempt to add 'active' and 'ancestor' classes to the menu items by default, as this is often not useful. You can override this by adding apply_active_classes=true to the tag in your template.

You can change the CSS class strings used to indicate ‘active’ and ‘ancestor’ statuses by utilising the WAGTAILMENUS_ACTIVE_CLASS and WAGTAILMENUS_ACTIVE_ANCESTOR_CLASS settings.


allow_repeating_parents
Required? Expected value type Default value
No bool True

Repetition-related settings on your pages are respected by default, but you can add allow_repeating_parents=False to ignore them, and not repeat any pages in sub-menus when rendering. Please note that using this option will only have an effect if use_specific has a value of 1 or higher.


template
Required? Expected value type Default value
No Template path (str) ''

Lets you render the menu to a template of your choosing. If not provided, wagtailmenus will attempt to find a suitable template automatically (see below for more details).

For more information about overriding templates, see: Using your own menu templates

For a list of preferred template paths this tag, see: Preferred template paths for {% children_menu %}


sub_menu_template
Required? Expected value type Default value
No Template path (str) ''

Lets you specify a template to be used for rendering sub menus. All subsequent calls to {% sub_menu %} within the context of the section menu will use this template unless overridden by providing a template value to {% sub_menu %} in a menu template. If not provided, wagtailmenus will attempt to find a suitable template automatically

For more information about overriding templates, see: Using your own menu templates

For a list of preferred template paths this tag, see: Preferred template paths for {% children_menu %}


use_absolute_page_urls
Required? Expected value type Default value
No bool False

By default, relative page URLs are used for the href attribute on page links when rendering your menu. If you wish to use absolute page URLs instead, add use_absolute_page_urls=True to the {% children_menu %} tag in your template. The preference will also be respected automatically by any subsequent calls to {% sub_menu %} during the course of rendering the menu (unless explicitly overridden in custom menu templates).


The sub_menu tag

The sub_menu tag is used within menu templates to render additional levels of pages within a menu. It’s designed to pick up on variables added to the context by the other menu tags, and so can behave a little unpredictably if called directly, without those context variables having been set. It requires only one parameter to work, which is menuitem_or_page.

Example usage
...
{% load menu_tags %}
...
{% for item in menu_items %}
    <li class="{{ item.active_class }}">
        <a href="{{ item.href }}">{{ item.text }}</a>
        {% if item.has_children_in_menu %}
            {% sub_menu item %}
        {% endif %}
    </li>
{% endfor %}
...

Using your own menu templates


Writing custom menu templates
What context variables are available to use?

The following variables are added to the context by all included template tags, which you can make use of in your templates:

menu_items:

A list of MenuItem or Page objects with some additional attributes added to help render menu items for the current level.

For more details on the attribute values added by wagtailmenus, see: Attributes added to each item in menu_items.

current_level:

An integer indicating the current level being rendered. This starts at 1 for the initial template tag call, then increments each time sub_menu is called recursively for rendering a particular branch of a menu.

max_levels:

An integer indicating the maximum number of levels that should be rendered for the current menu, as determined by the original main_menu, section_menu, flat_menu or children_menu tag call.

current_template:
 

The name of the template currently being used for rendering. This is most useful when rendering a sub_menu template that calls sub_menu recursively, and you wish to use the same template for all recursions.

sub_menu_template:
 

The name of the template that should be used for rendering any further levels (should be picked up automatically by the sub_menu tag).

original_menu_tag:
 

A string value indicating the name of tag was originally called in order to render the branch currently being rendered. The value will be one of "main_menu", "flat_menu", "section_menu", "children_menu" or "sub_menu".

allow_repeating_parents:
 

A boolean indicating whether MenuPage fields should be respected when rendering further menu levels.

apply_active_classes:
 

A boolean indicating whether sub_menu tags should attempt to add ‘active’ and ‘ancestor’ classes to menu items when rendering further menu levels.

use_absolute_page_urls:
 

A boolean indicating whether absolute page URLs should be used for page links when rendering.

Attributes added to each item in menu_items
href:

The URL that the menu item should link to.

text:

The text that should be used for the menu item.

You can change the field or attribute used to populate the text attribute by utilising the WAGTAILMENUS_PAGE_FIELD_FOR_MENU_ITEM_TEXT setting.

active_class:

A class name to indicate the ‘active’ state of the menu item. The value will be ‘active’ if linking to the current page, or ‘ancestor’ if linking to one of it’s ancestors.

You can change the CSS class strings used to indicate ‘active’ and ‘ancestor’ statuses by utilising the WAGTAILMENUS_ACTIVE_CLASS and WAGTAILMENUS_ACTIVE_ANCESTOR_CLASS settings.

has_children_in_menu:
 

A boolean indicating whether the menu item has children that should be output as a sub-menu.


Getting wagtailmenus to use your custom menu templates
Using preferred paths and names for your templates

This is the easiest (and recommended) approach for getting wagtailmenus to use your custom menu templates for rendering.

When use don’t use template or sub_menu_template arguments to explicitly specify templates for each tag, wagtailmenus looks in a list of gradually less specific paths for templates to use. If you’re familiar with Django, you’ll probably already be familiar with this approach. Essentially, you can override existing menu templates or add custom ones simply by putting them at a preferred location within your project.

If you have multi-site project, and want to be able to use different templates for some or all of those sites, wagtailmenus can be configured to look for additional ‘site specific’ paths for each template. To enable this feature, you need to add the following to your project’s settings:

WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS = True

With this set, tags will look for a request value in the context, and try to identify the current site being viewed by looking for a site attribute on request (which is set by wagtail.wagtailcore.middleware.SiteMiddleware). It then uses the domain field from that Site object to look for templates with that domain name included.

The following sections outline the preferred path locations for each tag, in the order that they are searched (most specific first).

Preferred template paths for {% main_menu %}

Note

Template paths marked with an asterisk (*) are only included if you’ve set the WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS setting to True in your project settings. They are not used by default.

For the menu itself:

  • "menus/{{ request.site.domain }}/main/menu.html" *
  • "menus/{{ request.site.domain }}/main_menu.html" *
  • "menus/main/menu.html"
  • "menus/main_menu.html"

For any sub-menus:

  • "menus/{{ request.site.domain }}/sub_menu.html" *
  • "menus/{{ request.site.domain }}/main_sub_menu.html" *
  • "menus/{{ request.site.domain }}/sub_menu.html" *
  • "menus/main/sub_menu.html"
  • "menus/main_sub_menu.html"
  • "menus/sub_menu.html"
Preferred template paths for {% flat_menu %}

For flat menus, the tag also uses the handle field of the specific menu being rendered, so that you can have wagtailmenus use different templates for different menus.

Note

Template paths marked with an asterisk (*) are only included if you’ve set the WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS setting to True in your project settings. They are not used by default.

For the menu itself:

  • "menus/{{ request.site.domain }}/flat/{{ menu.handle }}/menu.html" *
  • "menus/{{ request.site.domain }}/flat/{{ menu.handle }}.html" *
  • "menus/{{ request.site.domain }}/{{ menu.handle }}/menu.html" *
  • "menus/{{ request.site.domain }}/{{ menu.handle }}.html" *
  • "menus/{{ request.site.domain }}/flat/menu.html" *
  • "menus/{{ request.site.domain }}/flat/default.html" *
  • "menus/{{ request.site.domain }}/flat_menu.html" *
  • "menus/flat/{{ menu.handle }}/menu.html"
  • "menus/flat/{{ menu.handle }}.html"
  • "menus/{{ menu.handle }}/menu.html"
  • "menus/{{ menu.handle }}.html"
  • "menus/flat/default.html"
  • "menus/flat/menu.html"
  • "menus/flat_menu.html"

For any sub-menus:

  • "menus/{{ request.site.domain }}/flat/{{ menu.handle }}/sub_menu.html" *
  • "menus/{{ request.site.domain }}/flat/{{ menu.handle }}_sub_menu.html" *
  • "menus/{{ request.site.domain }}/{{ menu.handle }}/sub_menu.html" *
  • "menus/{{ request.site.domain }}/{{ menu.handle }}_sub_menu.html" *
  • "menus/{{ request.site.domain }}/flat/sub_menu.html" *
  • "menus/{{ request.site.domain }}/sub_menu.html" *
  • "menus/flat/{{ menu.handle }}/sub_menu.html"
  • "menus/flat/{{ menu.handle }}_sub_menu.html"
  • "menus/{{ menu.handle }}/sub_menu.html"
  • "menus/{{ menu.handle }}_sub_menu.html"
  • "menus/flat/sub_menu.html"
  • "menus/sub_menu.html"
Preferred template paths for {% section_menu %}

Note

Template paths marked with an asterisk (*) are only included if you’ve set the WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS setting to True in your project settings. They are not used by default.

For the menu itself:

  • "menus/{{ request.site.domain }}/section/menu.html" *
  • "menus/{{ request.site.domain }}/section_menu.html" *
  • "menus/section/menu.html"
  • "menus/section_menu.html"

For any sub-menus:

  • "menus/{{ request.site.domain }}/section/sub_menu.html" *
  • "menus/{{ request.site.domain }}/section_sub_menu.html" *
  • "menus/{{ request.site.domain }}/sub_menu.html" *
  • "menus/section/sub_menu.html"
  • "menus/section_sub_menu.html"
  • "menus/sub_menu.html"
Preferred template paths for {% children_menu %}

Note

Template paths marked with an asterisk (*) are only included if you’ve set the WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS setting to True in your project settings. They are not used by default.

For the menu itself:

  • "menus/{{ request.site.domain }}/children/menu.html" *
  • "menus/{{ request.site.domain }}/children_menu.html" *
  • "menus/children/menu.html"
  • "menus/children_menu.html"

For any sub-menus:

  • "menus/{{ request.site.domain }}/children/sub_menu.html" *
  • "menus/{{ request.site.domain }}/children_sub_menu.html" *
  • "menus/{{ request.site.domain }}/sub_menu.html" *
  • "menus/children/sub_menu.html"
  • "menus/children_sub_menu.html"
  • "menus/sub_menu.html"
Specifying menu templates using template tag parameters

All template tags included in wagtailmenus support template and sub_menu_template arguments to allow you to explicitly override the templates used during rendering.

For example, if you had created the following templates in your project’s root ‘templates’ directory:

  • "templates/custom_menus/main_menu.html"
  • "templates/custom_menus/main_menu_sub_menu.html"

You could make The main_menu tag use those templates for rendering by specifying them in your template, like so:

{% main_menu max_levels=2 template="custom_menus/main_menu.html" sub_menu_template="templates/custom_menus/main_menu_sub_menu.html" %}

Or you could just override one or the other (you don’t have to override both). e.g:

{# Just override the template for the top-level #}
{% main_menu max_levels=2 template="custom_menus/main_menu.html" %}

{# Just override the template used for sub-menus #}
{% main_menu max_levels=2 sub_menu_template="custom_menus/main_menu.html" %}

The MenuPage and MenuPageMixin models

The MenuPageMixin and MenuPage models were created specifically to solve the problem of important page links becoming merely toggles in multi-level menus, preventing users from accessing them easily.

A typical scenario

Let’s say you have an About Us section on your site. The top-level “About Us” page has content on it that is just as important as it’s children (e.g. “Meet the team”, “Our mission and values”, “Staff vacancies”). Because of this, you’d like visitors to be able to access the root page as easily as those pages. But, your site uses some form of collapsible multi-level navigation, and the About Us page link has become merely a toggle for hiding and showing its sub-pages, making it difficult to get to directly:

Screenshot showing a multi-level navigation where a parent page becomes a toggle for accessing children

Implementing MenuPage into your project

  1. Subclass wagtailmenus.models.MenuPage on your model instead of the usual wagtail.wagtacore.models.Page, just like in the following example:

    # appname/models.py
    
    from wagtailmenus.models import MenuPage
    
    
    class GenericPage(MenuPage):
        """
        This model will gain the fields, methods and 'setting_panels' attribute
        from MenuPage.
        """
        ...
    

    Or, if you’re using an abstract ‘base’ model in you project to improve consistency of common functionality, you could update the base model, like so:

    # appname/models.py
    
    from wagtailmenus.models import MenuPage
    
    
    class BaseProjectPage(MenuPage):
        ...
    
    
    class GenericPage(BaseProjectPage):
        ...
    
    
    class ContactPage(BaseProjectPage):
        ...
    
  2. If you’re not overriding the settings_panels attribute on any of the models involved, you can skip this step. But, if you are overriding the settings_panels attribute on a custom model to surface other custom fields in that tab, you’ll need to include additional panels to surface the new MenuPage fields in the page edit interface. Wagtailmenus includes a pre-defined menupage_panel to make this easier, which you can use like this:

    # appname/models.py
    
    from wagtailmenus.models import MenuPage
    from wagtailmenus.panels import menupage_panel
    
    
    class GenericPage(MenuPage):
        """
        This model will gain the fields, methods and `setting_panels` attribute
        from `MenuPage`, but `settings_panels` is being overridden to include
        other fields in the `Settings` tab.
        """
    
        custom_settings_field_one = BooleanField(default=False)
        custom_settings_field_two = BooleanField(default=True)
    
        # 'menupage_panel' is a collapsible `MultiFieldPanel` with the important
        # fields already grouped together, making it easy to include in custom
        # panel definitions, like so:
        settings_panels = [
            FieldPanel('custom_settings_field_one'),
            FieldPanel('custom_settings_field_two'),
            menupage_panel
        ]
        ...
    
  3. Create migtations for any models you’ve updated by running:

    python manage.py makemigrations appname
    
  4. Apply the new migrations by running:

    python manage.py migrate appname
    

Implementing MenuPageMixin into your project

Wagtail has a restriction that forbids models from subclassing more than one other class derived from Page, and that single page-derived class must be the left-most item when subclassing more than one model class. Most of the time, that doesn’t cause any noticeable issues. But, in some cases, it can make it difficult to swap out base model classes used for page models. In these cases, you can use wagtailmenus.models.MenuPageMixin instead of MenuPage.

Note

MenuPageMixin doesn’t change make any changes to the panel configuration on your model in order to surface it’s new fields in the page editing interface. If you want those fields to appear, you’ll have to override settings_panels on your model to include menupage_panel

  1. Subclass wagtailmenus.models.MenuPageMixin to create your model, including it to the right of any other class that subclasses Page:
# appname/models.py

from wagtail.wagtailforms.models import AbstractEmailForm
from wagtailmenus.models import MenuPageMixin
from wagtailmenus.panels import menupage_panel


class MyEmailFormPage(AbstractEmailForm, MenuPageMixin):
    """This page will gain the same fields and methods as if it extended
    `wagtailmenus.models.MenuPage`"""

    ...

    # It's not possible for MenuPageMixin to set `settings_panel`, so you must
    # override `settings_panels` yourself, and include `menupage_panel` in
    # order to surface additional field in the 'Settings' tab of the editor
    # interface
    settings_panels = [
        FieldPanel('custom_settings_field_one'),
        FieldPanel('custom_settings_field_two'),
        menupage_panel
    ]
    ...
  1. Create migtations for any models you’ve updated by running:
python manage.py makemigrations appname
  1. Apply the new migrations by running:
python manage.py migrate appname

Using MenuPage to manipulating sub-menu items

When a page model subclasses MenuPage or MenuPageMixin, pages of that type are given special treatment by the menu generation template tags included in wagtailmenus, allowing them to make changes to the sub-menu items that get rendered below them.

The functionaliy exists to allow MenuPage pages to add repeating links to themselves into a sub-menu, but can be extended to meet any custom needs you might have.

For example, if you had a ContactPage model, and in main menus, you wanted to add some additional links below each ContactPage, you could achieve that by overriding the modify_submenu_items() and has_submenu_items() methods like so:

# appname/models.py

from wagtailmenus.models import MenuPage


class ContactPage(MenuPage):
    ...

    current_page, current_ancestor_ids,
    current_site, allow_repeating_parents, apply_active_classes,
    original_menu_tag, menu_instance, request, use_absolute_page_urls

    def modify_submenu_items(self, 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.

        We're only making use 'original_menu_tag' and 'current_site' in this
        example, but `kwargs` should have all of the following keys:

        * 'current_page'
        * 'current_ancestor_ids'
        * 'current_site'
        * 'allow_repeating_parents'
        * 'apply_active_classes'
        * 'original_menu_tag'
        * 'menu_instance'
        * 'request'
        * 'use_absolute_page_urls'
        """

        # Start by applying default modifications
        menu_items = super(ContactPage, self).modify_submenu_items(menu_items, **kwargs)

        if kwargs['original_menu_tag'] == 'main_menu':
            base_url = self.relative_url(kwargs['current_site'])
            """
            Additional menu items can be objects with the necessary attributes,
            or simple dictionaries. `href` is used for the link URL, and `text`
            is the text displayed for each link. Below, I've also used
            `active_class` to add some additional CSS classes to these items,
            so that I can target them with additional CSS
            """
            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 has_submenu_items(self, **kwargs):
        """
        Because `modify_submenu_items` is being used to add additional menu
        items, we need to indicate in menu templates that `ContactPage` objects
        do have submenu items in main menus, even if they don't have children
        pages.

        We're only making use 'original_menu_tag' in this example, but
        `kwargs` should have all of the following keys:

        * 'current_page'
        * 'allow_repeating_parents'
        * 'original_menu_tag'
        * 'menu_instance'
        * 'request'
        """

        if kwargs['original_menu_tag'] == 'main_menu':
            return True
        # Resort to default behaviour
        return super(ContactPage, self).has_submenu_items(**kwargs)

The above changes would result in the following HTML output when rendering a ContactPage instance in a main menu:

...
<li class=" dropdown">
    <a href="/contact-us/" class="dropdown-toggle" id="ddtoggle_18" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Contact us <span class="caret"></span></a>
    <ul class="dropdown-menu" aria-labelledby="ddtoggle_18">
        <li class="support"><a href="/contact-us/#support">Get support</a></li>
        <li class="call"><a href="/contact-us/#call">Speak to someone</a></li>
        <li class="map"><a href="/contact-us/#map">Map &amp; directions</a></li>
    </ul>
</li>
...

You can also modify sub-menu items based on field values for specific instances, rather than doing the same for every page of the same type. Here’s another example:

# appname/models.py

from django.db import models
from wagtailmenus.models import MenuPage

class SectionRootPage(MenuPage):
    add_submenu_item_for_news = models.BooleanField(default=False)

    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
    ):
        menu_items = super(SectionRootPage,self).modify_menu_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 self.add_submenu_item_for_news:
            menu_items.append({
                'href': '/news/',
                'text': 'Read the news',
                'active_class': 'news-link',
            })
        return menu_items

    def has_submenu_items(
        self, current_page, allow_repeating_parents, original_menu_tag,
        menu_instance, request
    ):

        if self.add_submenu_item_for_news:
            return True
        return super(SectionRootPage, self).has_submenu_items(
            current_page, allow_repeating_parents, original_menu_tag,
            menu_instance, request)

Note

If you’re overriding modify_submenu_items(), please ensure that ‘repeated menu items’ are still added as the first item in the returned menu_items list. If not, active class highlighting might not work as expected.

The AbstractLinkPage model

Because main and flat menus only allow editors to define the top-level items in a menu, the AbstractLinkPage model was introduced to give them a way to easily add additional links to menus, by adding additional pages to the page tree.

Just like menu items defined for a menu via the CMS, link pages can link to other pages or custom URLs, and if linking to another page, the link will automatically become hidden if the target page is unpublished, expires, or is set to no longer show in menus. It will also appear again if the target page is published or set to show in menus again.

By default, link pages are not allowed to have children pages, and shouldn’t appear in wagtail-generated sitemaps or search results.

Implementing AbstractLinkPage into your project

Like MenuPage, AbstractLinkPage is an abstract model, so in order to use it in your project, you need to subclass it.

  1. Subclass AbstractLinkPage to create a new page type model in your project:
# appname/models.py

from wagtailmenus.models import AbstractLinkPage


class LinkPage(AbstractLinkPage):
    pass
  1. Create migtations for any models you’ve updated by running:
python manage.py makemigrations appname
  1. Apply the new migrations by running:
python manage.py migrate appname

Advanced topics

‘Specific’ pages and menus

For pages, Wagtail makes use of a technique in Django called ‘multi-table inheritance’. In simple terms, this means that when you create an instance of a custom page type model, the data is saved in two different database tables:

  • All of the standard fields from Wagtail’s Page model are stored in one table
  • Any data for additional fields specific to your custom model are saved in another one

Because of this, in order for Django to return ‘specific’ page type instance (e.g. an EventPage), it needs to fetch and join data from both tables; which has a negative effect on performance.

Menu generation is particularly resource intensive, because a menu needs to know a lot of data about a lot of pages. Add a need for ‘specific’ page instances to that mix (perhaps you need to access multlingual field values that only exist in the specific database table, or you want to use other custom field values in your menu templates), and that intensity is understandably greater, as the data will likely be spread over many tables (depending on how many custom page types you are using), needing lots of database joins to put everything together.

Because every project has different needs, wagtailmenus gives you some fine grained control over how ‘specific’ pages should be used in your menus. When defining a MainMenu or FlatMenu in the CMS, the Specific page use field allows you to choose one of the following options, which can also be passed to any of the included template tags using the use_specific parameter.

Supported values for fetching specific pages
  • Off (value: 0): Use only standard Page model data and methods, and make the minimum number of database methods when rendering. If you aren’t using wagtailmenu’s MenuPage model in your project, and don’t need to access any custom page model fields or methods in you menu templates, and aren’t overriding get_url_parts() or other Page methods concerned with URL generation, you should use this option for optimal performance.

  • Auto (value: 1): Only fetch and use specific pages when needed for MenuPage operations (e.g. for ‘repeating menu item’ behaviour, and manipulation of sub-menu items via has_submenu_items() and modify_submenu_items() methods).

  • Top level (value: 2): Only fetch and return specific page instances for the top-level menu items (pages that were manually added as menu items via the CMS), but only use vanilla Page objects for any additional levels.

    Note

    Although accepted by all menu tags, using use_specific=2 will only really effect main_menu and flat_menu tags. All other tags will behave the same as if you’d supplied a value of Auto (1).

  • Always (value: 3): Fetch and return specific page instances for ALL pages, so that custom page-type data and methods can be accessed in all menu templates. If you have a multilingual site and want to output translated page content in menus, or if you have models that override get_url_parts(), relative_url() or other Page methods involved in returning URLs, this is the option you should use in order to fetch the data as efficiently as possible.

Using the use_specific template tag argument

All of the template tags included in wagtailmenus accept a use_specific argument, allowing you to override any default settings, or the settings applied via the CMS to individual MainMenu and FlatMenu objects. As a value, you can pass in the integer value of any of the above options, for example:

...
{% main_menu use_specific=2 %}
...
{% section_menu use_specific=3 %}
...
{% children_menu use_specific=1 %}

Or, the following variables should be available in the context for you to use instead:

  • USE_SPECIFIC_OFF (value: 0)
  • USE_SPECIFIC_AUTO (value 1)
  • USE_SPECIFIC_TOP_LEVEL (value 2)
  • USE_SPECIFIC_ALWAYS (value 3)

For example:

...
{% main_menu use_specific=USE_SPECIFIC_TOP_LEVEL %}
...
{% section_menu use_specific=USE_SPECIFIC_ALWAYS %}
...
{% children_menu use_specific=USE_SPECIFIC_AUTO %}

Using hooks to modify menus

On loading, Wagtail will search for any app with the file wagtail_hooks.py and execute the contents. This provides a way to register your own functions to execute at certain points in Wagtail’s execution, such as when a Page object is saved or when the main menu is constructed.

Registering functions with a Wagtail hook is done through the @hooks.register decorator:

from wagtail.wagtailcore import hooks

@hooks.register('name_of_hook')
def my_hook_function(arg1, arg2...)
    # your code here

Alternatively, hooks.register can be called as an ordinary function, passing in the name of the hook and a handler function defined elsewhere:

hooks.register('name_of_hook', my_hook_function)

Wagtailmenus utilises this same ‘hooks’ mechanism to allow you make modifications to menus at certain points during the rendering process.

Hooks for modifying QuerySets

When a menu instance is gathering the data it needs to render itself, it typically uses one or more QuerySets to fetch Page and MenuItem data from the database. These hooks allow you to modify those QuerySets before they are evaluated, allowing you to efficiently control menu contents.

If you need to override a lot of menu class behaviour, and you’re comfortable with the idea of subclassing the existing classes and models to override the necessary methods, you might want to look at Using custom menu classes and models. But, if all you want to do is change the result of a menu’s get_base_page_queryset() or get_base_menuitem_queryset() (say, to limit the links that appear based on the permissions of the currently logged-in user), you may find it quicker & easier to use the following hooks instead.

Hooks for modifying menu items

While the above tags are concerned with modifying the data used in a menu, the following hooks are called later on in the rendering process, and allow you to modify the list of MenuItem or Page objects before they are sent to a template to be rendered.

There are two hooks you can use to modify menu items, which are called at different stages of preparation.

Argument reference

In the above examples, **kwargs is used in hook method signatures to make them accepting of other keyword arguments, without having to declare every single argument that should be passed in. Using this approach helps create leaner, tidier code, and also makes it more ‘future-proof’, since the methods will automatically accept any new arguments that may be added by wagtailmenus in future releases.

Below is a full list of the additional arguments that are passed to methods using the above hooks:

request
The HttpRequest instance that the menu is currently being rendered for.
parent_context
The Context instance that the menu is being rendered from.
parent_page
If the menu being rendered is showing ‘children’ of a specific page, this will be the Page instance who’s children pages are being displayed. The value might also be None if no parent page is involved. For example, if rendering the top level items of a main or flat menu.
menu_tag
The name of the tag that was called to render the current part of the menu. If rendering the first level of a menu, this will have the same value as original_menu_tag. If not, it will have the value ‘sub_menu’ (unless you’re using custom tags that pass a different ‘tag_name’ value to the menu classe’s ‘render_from_tag’ method)
original_menu_tag
The name of the tag that was called to initiate rendering of the menu that is currently being rendered. For example, if you’re using the main_menu tag to render a multi-level main menu, even though sub_menu may be called to render subsequent additional levels, ‘original_menu_tag’ should retain the value 'main_menu'. Should be one of: 'main_menu', 'flat_menu', 'section_menu' or 'children_menu'. Comparable to the menu_type values supplied to other hooks.
menu_instance
The menu instance that is supplying the data required to generate the current menu. This could be an instance of a model class, like MainMenu or FlatMenu, or a standard python class like ChildrenMenu or SectionMenu.
original_menu_instance
The menu instance that is supplying the data required to generate the current menu. This could be an instance of a model class, like MainMenu or FlatMenu, or a standard python class like ChildrenMenu or SectionMenu.
current_level
An integer value indicating the ‘level’ or ‘depth’ that is currently being rendered in the process of rendering a multi-level menu. This will start at 1 for the first/top-level items of a menu, and increment by 1 for each additional level.
max_levels
An integer value indicatiing the maxiumum number of levels that should be rendered for the current menu. This will either have been specified by the developer using the max_levels argument of a menu tag, or might have been set in the CMS for a specific MainMenu or FlatMenu instance.
current_site
A Wagtail Site instance, indicating the site that the current request is for (usually also available as request.site)
current_page
A Wagtail Page instance, indicating what wagtailmenus beleives to be the page that is currently being viewed / requested by a user. This might be None if you’re using additional views in your project to provide functionality at URLs that don’t map to a Page in Wagtail.
current_page_ancestor_ids
A list of ids of Page instances that are an ‘ancestor’ of current_page.
current_section_root_page

If current_page has a value, this will be the top-most ancestor of that page, from just below the site’s root page. For example, if your page tree looked like the following:

::
Home (Set as ‘root page’ for the site) ├── About us ├── What we do ├── Careers | ├── Vacancy one | └── Vacancy two ├── News & events | ├── News | | ├── Article one | | └── Article two | └── Events └── Contact us

if the current page was ‘Vacancy one’, the section root page would be ‘Careers’. Or, if the current page was ‘Article one’, the section root page would be ‘News & events’.

use_specific
An integer value indicating the preferred policy for using PageQuerySet.specific() and Page.specific in rendering the current menu. For more information see: ‘Specific’ pages and menus.
allow_repeating_parents
A boolean value indicating the preferred policy for having pages that subclass MenuPageMixin add a repeated versions of themselves to it’s children pages (when rendering a sub_menu for that page). For more information see: The MenuPage and MenuPageMixin models.
apply_active_classes
A boolean value indicating the preferred policy for setting active_class attributes on menu items for the current menu.
use_absolute_page_urls
A boolean value indicating the preferred policy for using full/absolute page URLs for menu items representing pages (observed by prime_menu_items() when setting the href attribute on each menu item). In most cases this will be False, as the default behaviour is to use ‘relative’ URLs for pages.

Using custom menu classes and models

Overriding the models used for main menus

There are a couple of different approaches for overriding the models used for defining / rendering main menus. The best approach for your project depends on which models you need to override.

Replacing the MainMenuItem model only

If you’re happy with the default MainMenu model, but wish customise the menu item model (e.g. to add images, description fields, or extra fields for translated strings), you can use the WAGTAILMENUS_MAIN_MENU_ITEMS_RELATED_NAME setting to have main menus use a different model, both within Wagtail’s CMS, and for generating the list of menu_items used by menu templates.

  1. Within your project, define your custom model by subclassing AbstractMainMenuItem:

    # appname/models.py
    
    from django.db import models
    from django.utils.translation import ugettext_lazy as _
    from modelcluster.fields import ParentalKey
    from wagtail.wagtailimages import get_image_model_string
    from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
    from wagtail.wagtailadmin.edit_handlers import FieldPanel, PageChooserPanel
    from wagtailmenus.models import AbstractMainMenuItem
    
    
    class CustomMainMenuItem(AbstractMainMenuItem):
        """A custom menu item model to be used by ``wagtailmenus.MainMenu``"""
    
        menu = ParentalKey(
            'wagtailmenus.MainMenu',
            on_delete=models.CASCADE,
            related_name="custom_menu_items", # important for step 3!
        )
        image = models.ForeignKey(
            get_image_model_string(),
            blank=True,
            null=True,
            on_delete=models.SET_NULL,
        )
        hover_description = models.CharField(
            max_length=250,
            blank=True
        )
    
        # Also override the panels attribute, so that the new fields appear
        # in the admin interface
        panels = (
            PageChooserPanel('link_page'),
            FieldPanel('link_url'),
            FieldPanel('url_append'),
            FieldPanel('link_text'),
            ImageChooserPanel('image'),
            FieldPanel('hover_description'),
            FieldPanel('allow_subnav'),
        )
    
  2. Create migtations for the new model by running:

    python manage.py makemigrations appname
    
  3. Apply the new migrations by running:

    python manage.py migrate appname
    
  4. Add a setting to your project to instruct wagtailmenus to use your custom model instead of the default:

    # Set this to the 'related_name' attribute used on the ParentalKey field
    WAGTAILMENUS_MAIN_MENU_ITEMS_RELATED_NAME = "custom_menu_items"
    
  5. That’s it! The custom models will now be used instead of the default ones.

    Note

    Although you won’t be able to see them in the CMS any longer, the default models and any data that was in the original database table will remain intact.

Replacing both the MainMenu and MainMenuItem models

If you also need to override the MainMenu model, that’s possible too. But, because the MainMenuItem model is tied to MainMenu, you’ll also need to create custom menu item model (whether you wish to add fields / change their behaviour, or not).

  1. Within your project, define your custom models by subclassing the AbstractMainMenu and AbstractMainMenuItem model classes:

    # appname/models.py
    
    from django.db import models
    from django.utils import translation
    from django.utils.translation import ugettext_lazy as _
    from django.utils import timezone
    from modelcluster.fields import ParentalKey
    from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, PageChooserPanel
    from wagtailmenus import app_settings
    from wagtailmenus.models import AbstractMainMenu, AbstractMainMenuItem
    
    
    class LimitedMainMenu(AbstractMainMenu):
        limit_from = models.TimeField()
        limit_to = models.TimeField()
    
        def get_base_page_queryset(self):
            """
            If the current time is between 'limit_from' and 'limit_to',
            only surface pages that are owned by the logged in user
            """
            if(
                self.request.user and
                self.limit_from < timezone.now() < self.limit_to
            ):
    
                return self.request.user.owned_pages.filter(
                    live=True, expired=False, show_in_menus=True
                )
            return Page.objects.none()
    
        # Like pages, panels for menus are split into multiple tabs.
        # To update the panels in the 'Content' tab, override 'content_panels'
        # To update the panels in the 'Settings' tab, override 'settings_panels'
        settings_panels = AbstractMainMenu.setting_panels += (
            MultiFieldPanel(
                heading=_('Time limit settings'),
                children=(
                    FieldPanel('limit_from'),
                    FieldPanel('limit_to'),
                ),
            ),
        )
    
    class CustomMainMenuItem(AbstractMainMenuItem):
        """A minimal custom menu item model to be used by `LimitedMainMenu`.
        No additional fields / method necessary
        """
        menu = ParentalKey(
            LimitedMainMenu, # we can use the model from above
            on_delete=models.CASCADE,
            related_name=app_settings.MAIN_MENU_ITEMS_RELATED_NAME,
        )
    
  2. Create migtations for the new models by running:

    python manage.py makemigrations appname
    
  3. Apply the new migrations by running:

    python manage.py migrate appname
    
  4. Add a setting to your project to tell wagtailmenus to use your custom menu model instead of the default one. e.g:

    # settings.py
    
    WAGTAILMENUS_MAIN_MENU_MODEL = "appname.LimitedMainMenu"
    
  5. That’s it! The custom models will now be used instead of the default ones.

    Note

    Although you won’t be able to see them in the CMS any longer, the default models and any data that was in the original database table will remain intact.

Overriding the models used for flat menus

There are a couple of different approaches for overriding the models used for defining / rendering flat menus. The best approach for your project depends on which models you need to override.

Replacing the FlatMenuItem model only

If you’re happy with the default FlatMenu model, but wish customise the menu item models (e.g. to add images, description fields, or extra fields for translated strings), you can use the WAGTAILMENUS_FLAT_MENU_ITEMS_RELATED_NAME setting to have flat menus use a different model, both within Wagtail’s CMS, and for generating the list of menu_items used by menu templates.

  1. Within your project, define your custom model by subclassing AbstractFlatMenuItem:

    # apname/models.py
    
    from django.db import models
    from django.utils.translation import ugettext_lazy as _
    from modelcluster.fields import ParentalKey
    from wagtail.wagtailimages import get_image_model_string
    from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
    from wagtail.wagtailadmin.edit_handlers import FieldPanel, PageChooserPanel
    from wagtailmenus.models import AbstractFlatMenuItem
    
    
    class CustomFlatMenuItem(AbstractFlatMenuItem):
        """A custom menu item model to be used by ``wagtailmenus.FlatMenu``"""
    
        menu = ParentalKey(
            'wagtailmenus.FlatMenu',
            on_delete=models.CASCADE,
            related_name="custom_menu_items", # important for step 3!
        )
        image = models.ForeignKey(
            get_image_model_string(),
            blank=True,
            null=True,
            on_delete=models.SET_NULL,
        )
        hover_description = models.CharField(
            max_length=250,
            blank=True
        )
    
        # Also override the panels attribute, so that the new fields appear
        # in the admin interface
        panels = (
            PageChooserPanel('link_page'),
            FieldPanel('link_url'),
            FieldPanel('url_append'),
            FieldPanel('link_text'),
            ImageChooserPanel('image'),
            FieldPanel('hover_description'),
            FieldPanel('allow_subnav'),
        )
    
  2. Create migtations for the new models by running:

    python manage.py makemigrations appname
    
  3. Apply the new migrations by running:

    python manage.py migrate appname
    
  4. Add a setting to your project to tell wagtailmenus to use your custom model instead of the default one. e.g:

    # settings.py
    
    # Use the 'related_name' attribute you used on your custom model's ParentalKey field
    WAGTAILMENUS_FLAT_MENU_ITEMS_RELATED_NAME = "custom_menu_items"
    
  5. That’s it! The custom models will now be used instead of the default ones.

    Note

    Although you won’t be able to see them in the CMS any longer, the default models and any data that was in the original database table will remain intact.

Replacing both the FlatMenu and FlatMenuItem models

If you also need to override the FlatMenu model, that’s possible too. But, because the FlatMenuItem model is tied to FlatMenu, you’ll also need to create custom menu item model (whether you wish to add fields or their behaviour or not).

  1. Within your project, define your custom models by subclassing the AbstractFlatMenu and AbstractFlatMenuItem model classes:

    # appname/models.py
    
    from django.db import models
    from django.utils import translation
    from django.utils.translation import ugettext_lazy as _
    from modelcluster.fields import ParentalKey
    from wagtail.wagtailadmin.edit_handlers import FieldPanel, MultiFieldPanel, PageChooserPanel
    from wagtailmenus import app_settings
    from wagtailmenus.panels import FlatMenuItemsInlinePanel
    from wagtailmenus.models import AbstractFlatMenu, AbstractFlatMenuItem
    
    
    class TranslatedField(object):
        """
        A class that can be used on models to return a 'field' in the
        desired language, where there a multiple versions of a field to
        cater for multiple languages (in this case, English, German & French)
        """
        def __init__(self, en_field, de_field, fr_field):
            self.en_field = en_field
            self.de_field = de_field
            self.fr_field = fr_field
    
        def __get__(self, instance, owner):
            active_language = translation.get_language()
            if active_language == 'de':
                return getattr(instance, self.de_field)
            if active_language == 'fr':
                return getattr(instance, self.fr_field)
            return getattr(instance, self.en_field)
    
    
    class TranslatedFlatMenu(AbstractFlatMenu):
        heading_de = models.CharField(
            verbose_name=_("heading (german)"),
            max_length=255,
            blank=True,
        )
        heading_fr = models.CharField(
            verbose_name=_("heading (french)"),
            max_length=255,
            blank=True,
        )
        translated_heading = TranslatedField('heading', 'heading_de', 'heading_fr')
    
        # Like pages, panels for menus are split into multiple tabs.
        # To update the panels in the 'Content' tab, override 'content_panels'
        # To update the panels in the 'Settings' tab, override 'settings_panels'
        content_panels = (
            MultiFieldPanel(
                heading=_("Settings"),
                children=(
                    FieldPanel("title"),
                    FieldPanel("site"),
                    FieldPanel("handle"),
                )
            ),
            MultiFieldPanel(
                heading=_("Heading"),
                children=(
                    FieldPanel("heading"),
                    FieldPanel("heading_de"),
                    FieldPanel("heading_fr"),
                ),
                classname='collapsible'
            ),
            FlatMenuItemsInlinePanel(),
        )
    
    class TranslatedFlatMenuItem(AbstractFlatMenuItem):
        """A custom menu item model to be used by ``TranslatedFlatMenu``"""
    
        menu = ParentalKey(
            TranslatedFlatMenu, # we can use the model from above
            on_delete=models.CASCADE,
            related_name=app_settings.FLAT_MENU_ITEMS_RELATED_NAME,
        )
        link_text_de = models.CharField(
            verbose_name=_("link text (german)"),
            max_length=255,
            blank=True,
        )
        link_text_fr = models.CharField(
            verbose_name=_("link text (french)"),
            max_length=255,
            blank=True,
        )
        translated_link_text = TranslatedField('link_text', 'link_text_de', 'link_text_fr')
    
        @property
        def menu_text(self):
            """Use `translated_link_text` instead of just `link_text`"""
            return self.translated_link_text or getattr(
                self.link_page,
                app_settings.PAGE_FIELD_FOR_MENU_ITEM_TEXT,
                self.link_page.title
            )
    
        # Also override the panels attribute, so that the new fields appear
        # in the admin interface
        panels = (
            PageChooserPanel("link_page"),
            FieldPanel("link_url"),
            FieldPanel("url_append"),
            FieldPanel("link_text"),
            FieldPanel("link_text_de"),
            FieldPanel("link_text_fr"),
            FieldPanel("handle"),
            FieldPanel("allow_subnav"),
        )
    
  2. Create migtations for the new models by running:

    python manage.py makemigrations appname
    
  3. Apply the new migrations by running:

    python manage.py migrate appname
    
  4. Add a setting to your project to tell wagtailmenus to use your custom menu model instead of the default one. e.g:

    # settings.py
    
    WAGTAILMENUS_FLAT_MENU_MODEL = "appname.TranslatedFlatMenu"
    
  5. That’s it! The custom models will now be used instead of the default ones.

    Note

    Although you won’t be able to see them in the CMS any longer, the default models and any data that was in the original database table will remain intact.

Overriding the menu class used by {% section_menu %}

Like the main_menu and flat_menu tags, the section_menu tag uses a Menu class to fetch all of the data needed to render a menu. Though, because section menus are driven entirely by your existing page tree (and don’t need to store any additional data), it’s just a plain old Python class and not a Django model.

The class wagtailmenus.models.menus.SectionMenu is used by default, but you can use the WAGTAILMENUS_SECTION_MENU_CLASS_PATH setting in your project to make wagtailmenus use an alternative class (for example, if you want to modify the base queryset that determines which pages should be included when rendering). To implement a custom classes, it’s recommended that you subclass the SectionMenu and override any methods as required, like in the following example:

# mysite/appname/models.py

from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailcore.models import Page
from wagtailmenus.models import SectionMenu


class CustomSectionMenu(SectionMenu):

    def get_base_page_queryset(self):
        # Show draft and expired pages in menu for superusers
        if self.request.user.is_superuser:
            return Page.objects.filter(show_in_menus=True)
        # Resort to default behaviour for everybody else
        return super(CustomSectionMenu, self).get_base_page_queryset()
# settings.py

WAGTAILMENUS_SECTION_MENU_CLASS_PATH = "mysite.appname.models.CustomSectionMenu"
Overriding the menu class used by {% children_menu %}

Like all of the other tags, the children_menu tag uses a Menu class to fetch all of the data needed to render a menu. Though, because children menus are driven entirely by your existing page tree (and do not need to store any additional data), it’s just a plain old Python class and not a Django model.

The class wagtailmenus.models.menus.ChildrenMenu is used by default, but you can use the WAGTAILMENUS_CHILDREN_MENU_CLASS_PATH setting in your project to make wagtailmenus use an alternative class (for example, if you want to modify which pages are included). For custom classes, it’s recommended that you subclass ChildrenMenu and override any methods as required e.g:

# appname/menus.py

from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailcore.models import Page
from wagtailmenus.models import ChildrenMenu


class CustomChildrenMenu(ChildrenMenu):
    def get_base_page_queryset(self):
    # Show draft and expired pages in menu for superusers
    if self.request.user.is_superuser:
        return Page.objects.filter(show_in_menus=True)
    # Resort to default behaviour for everybody else
    return super(CustomChildrenMenu, self).get_base_page_queryset()
# settings.py

WAGTAILMENUS_CHILDREN_MENU_CLASS_PATH = "mysite.appname.models.CustomChildrenMenu"

Settings reference

You can override some of wagtailmenus’ default behaviour by adding one of more of the following to your project’s settings.

Admin / UI settings

WAGTAILMENUS_ADD_EDITOR_OVERRIDE_STYLES

Default value: True

By default, wagtailmenus adds some additional styles to improve the readability of the forms on the menu management pages in the Wagtail admin area. If for some reason you don’t want to override any styles, you can disable this behaviour by setting to False.

WAGTAILMENUS_FLATMENU_MENU_ICON

Default value: ‘list-ol’

Use this to change the icon used to represent ‘Flat menus’ in the Wagtail CMS.

WAGTAILMENUS_FLAT_MENUS_HANDLE_CHOICES

Default value: None

Can be set to a tuple of choices in the standard Django choices format to change the presentation of the FlatMenu.handle field from a text field, to a select field with fixed choices, when adding, editing or copying a flat menus in Wagtail’s CMS.

For example, if your project uses an ‘info’ menu in the header, a ‘footer’ menu in the footer, and a ‘help’ menu in the sidebar, you could do the following:

WAGTAILMENUS_FLAT_MENUS_HANDLE_CHOICES = (
    ('info', 'Info'),
    ('help', 'Help'),
    ('footer', 'Footer'),
)
WAGTAILMENUS_MAINMENU_MENU_ICON

Default value: 'list-ol'

Use this to change the icon used to represent ‘Main menus’ in the Wagtail CMS.

Default templates and template finder settings

WAGTAILMENUS_DEFAULT_CHILDREN_MENU_TEMPLATE

Default value: 'menus/children_menu.html'

The name of the template used for rendering by the {% children_menu %} tag when no other template has been specified using the template parameter.

WAGTAILMENUS_DEFAULT_FLAT_MENU_TEMPLATE

Default value: 'menus/flat_menu.html'

The name of the template used for rendering by the {% flat_menu %} tag when no other template has been specified using the template parameter.

WAGTAILMENUS_DEFAULT_MAIN_MENU_TEMPLATE

Default value: 'menus/main_menu.html'

The name of the template used for rendering by the {% main_menu %} tag when no other template has been specified using the template parameter.

WAGTAILMENUS_DEFAULT_SECTION_MENU_TEMPLATE

Default value: 'menus/section_menu.html'

The name of the template used for rendering by the {% section_menu %} tag when no other template has been specified using the template parameter.

WAGTAILMENUS_DEFAULT_SUB_MENU_TEMPLATE

Default value: 'menus/sub_menu.html'

The name of the template used for rendering by the {% sub_menu %} tag when no other template has been specified using the template parameter or using the sub_menu_template parameter on the original menu tag.

WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS

Default value: False

If you have a multi-site project where each site may require it’s own set of menu templates, you can change this setting to True to have wagtailmenus automatically look in additional site-specific locations when finding templates for rendering.

Default tag behaviour settings

WAGTAILMENUS_FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS

Default value: False

The default value used for fall_back_to_default_site_menus option of the {% flat_menu %} tag when a parameter value isn’t provided.

WAGTAILMENUS_GUESS_TREE_POSITION_FROM_PATH

Default value: True

When not using wagtail’s routing/serving mechanism to serve page objects, wagtailmenus can use the request path to attempt to identify a ‘current’ page, ‘section root’ page, allowing {% section_menu %} and active item highlighting to work. If this functionality is not required for your project, you can disable it by setting this value to False.

WAGTAILMENUS_DEFAULT_CHILDREN_MENU_MAX_LEVELS

Default value: 1

The maximum number of levels rendered by the {% children_menu %} tag when no value has been specified using the max_levels parameter.

WAGTAILMENUS_DEFAULT_SECTION_MENU_MAX_LEVELS

Default value: 2

The maximum number of levels rendered by the {% section_menu %} tag when no value has been specified using the max_levels parameter.

WAGTAILMENUS_DEFAULT_CHILDREN_MENU_USE_SPECIFIC

Default value: 1 (Auto)

Controls how ‘specific’ pages objects are fetched and used during rendering of the {% children_menu %} tag when no use_specific value isn’t supplied.

If you’d like to use custom page fields in your children menus (e.g. translated field values or image fields) or if your page models override get_url_parts(), relative_url() or other Page methods involved in URL generation, you’ll likely want to update this.

To find out more about what values are supported and the effect they have, see: ‘Specific’ pages and menus

WAGTAILMENUS_DEFAULT_SECTION_MENU_USE_SPECIFIC

Default value: 1 (Auto)

Controls how ‘specific’ pages objects are fetched and used during rendering of the {% section_menu %} tag when no alternative value has been specified using the use_specific parameter.

If you’d like to use custom page fields in your section menus (e.g. translated field values, images, or other fields / methods) or if your page models override get_url_parts(), relative_url() or other Page methods involved in URL generation, you’ll likely want to update this.

To find out more about what values are supported and the effect they have, see: ‘Specific’ pages and menus

Miscellaneous settings

WAGTAILMENUS_ACTIVE_CLASS

Default value: 'active'

The class added to menu items for the currently active page (when using a menu template with apply_active_classes=True)

WAGTAILMENUS_ACTIVE_ANCESTOR_CLASS

Default value: 'ancestor'

The class added to any menu items for pages that are ancestors of the currently active page (when using a menu template with apply_active_classes=True)

WAGTAILMENUS_PAGE_FIELD_FOR_MENU_ITEM_TEXT

Default value: 'title'

When preparing menu items for rendering, wagtailmenus looks for a field, attribute or property method on each page with this name to set a text attribute value, which is used in menu templates as the label for each item. The title field is used by default.

Note

wagtailmenus will only be able to access custom page fields or methods if ‘specific’ pages are being used (See ‘Specific’ pages and menus). If no attribute can be found matching the specified name, wagtailmenus will silently fall back to using the page’s title field value.

WAGTAILMENUS_SECTION_ROOT_DEPTH

Default value: 3

Use this to specify the ‘depth’ value of a project’s ‘section root’ pages. For most Wagtail projects, this should be 3 (Root page depth = 1, Home page depth = 2), but it may well differ, depending on the needs of the project.

Contributing to wagtailmenus

Hey! First of all, thanks for considering to help out!

We welcome all support, whether on bug reports, code, reviews, tests, documentation, translations or just feature requests.

Using the issue tracker

The issue tracker is the preferred channel for bug reports, features requests and submitting pull requests. Please don’t use the issue tracker for support requests. If you need help with something that isn’t a bug, you can join our Wagtailmenus support group and ask your question there.

Submitting translations

Please submit any new or improved translations through Transifex.

Contributing code changes via pull requests

If there are any open issues you think you can help with, please comment on the issue and state your intent to help. Or, if you have an idea for a feature you’d like to work on, raise it as an issue. Once a core contributor has responded and is happy for you to proceed with a solution, you should create your own fork of wagtailmenus, make the changes there. Before committing any changes, we highly recommend that you create a new branch, and keep all related changes within that same branch. When you’ve finished making your changes, and the tests are passing, you can then submit a pull request for review.

What your pull request should include

In order to be accepted/merged, your pull request will need to meet the following criteria:

  1. Documentation updates to cover any new features or changes.
  2. If you’re not in the list already, add a new line to CONTRIBUTORS.md (under the ‘Contributors’ heading) with your name, company name, and an optional twitter handle / email address.
  3. For all new features, please add additional unit tests to wagtailmenus.tests, to test what you’ve written. Although the quality of unit tests is the most important thing (they should be readable, and test the correct thing / combination of things), code coverage is important too, so please ensure as many lines of your code as possible are accessed when the unit tests are run.

Developing locally

If you’d like a runnable Django project to help with development of wagtailmenus, follow these steps to get started (Mac only). The development environment has django-debug-toolbar and some other helpful packages installed to help you debug with your code as you develop:

  1. In a Terminal window, cd to the wagtailmenus root directory, and run:

    pip install -e '.[testing,docs]' -U
    pip install -r requirements/development.txt
    
  2. Create a copy of the development settings:

    cp wagtailmenus/settings/development.py.example wagtailmenus/settings/development.py
    
  3. Create a copy of the development urls:

    cp wagtailmenus/development/urls.py.example wagtailmenus/development/urls.py
    
  4. Create manage.py by copying the example provided:

    cp manage.py.example manage.py
    
  5. To load some test data into the database, run:

    python manage.py loaddata wagtailmenus/tests/fixtures/test.json
    
  6. Create a new superuser so that you can log into the CMS:

    python manage.py createsuperuser
    
  7. Run the project using the standard Django command:

    python manage.py runserver
    

Your local copies of settings/development.py and manage.py should be ignored by git when you push any changes, as will anything you add to the wagtailmenus/development/ directory.

Testing locally

It’s important that any new code is tested before submitting. To quickly test code in your active development environment, you should first install all of the requirements by running:

pip install -e '.[testing,docs]' -U

Then, run the following command to execute tests:

python runtests.py

Or if you want to measure test coverage, run:

coverage --source=wagtailmenus runtests.py
coverage report

Testing in a single environment is a quick and easy way to identify obvious issues with your code. However, it’s important to test changes in other environments too, before they are submitted. In order to help with this, wagtailmenus is configured to use tox for multi-environment tests. They take longer to complete, but running them is as simple as running:

tox

Other topics

Release packaging guidelines
Preparing for a new release

Follow the steps outlined below to prep changes in your fork:

  1. Merge any changes from upstream/master into your fork’s master branch.

    git fetch upstream
    git checkout master
    git merge upstream/master
    
  2. From your fork’s master branch, create a new branch for prepping the release, e.g.:

    git checkout -b release-prep/2.X.X
    
  3. Update __version__ in wagtailmenus/__init__.py to reflect the new release version.

  4. Make sure CHANGELOG.md is updated with details of any changes since the last release.

  5. Make sure the release notes for the new version have been created / updated in docs/source/releases/ and are referenced in docs/source/releases/index.rst.

  6. If releasing a ‘final’ version, following an ‘alpha’ or ‘beta’ release, ensure the a or b is removed from the file name for the release, and the reference to it in docs/source/releases/index.rst.

  7. Check that the docs build okay, and fix any errors raised by sphinx:

    make html
    
  8. Commit changes so far:

    git commit -am 'Bumped version and updated release notes'
    
  1. Update the source translation files by running the following from the project’s root directory:

    # Update source files
    django-admin.py makemessages -l en
    
    # Commit the changes
    git commit -am 'Update source translation files'
    
  2. Push all oustanding changes to github:

    git push
    
  3. Submit your changes as a PR to the main repo via https://github.com/rkhleics/wagtailmenus/compare

Packaging and pushing to PyPi

When satisfied with the PR for prepping the files:

  1. From https://github.com/rkhleics/wagtailmenus/pulls, merge the PR into the master branch using the “merge commit” option.

  2. Locally, cd to the project’s root directory, checkout the master branch, and ensure the local copy is up-to-date:

    workon wagtailmenus
    cd ../path-to-original-repo
    git checkout master
    git pull
    
  3. Ensure dependencies are up-to-date by running:

    pip install -e '.[testing,docs]' -U
    
  4. Push any updated translation source files to Transifex:

    tx push -s -l en
    
  5. Pull down updated translations from Transifex:

    tx pull --a
    rm -r wagtailmenus/locale/en_GB/
    git add *.po
    
  6. For each language, convert .po files to .mo by running:

    msgfmt --check-format -o wagtailmenus/locale/lang/LC_MESSAGES/django.mo wagtailmenus/locale/lang/LC_MESSAGES/django.po
    
  7. Commit and push all changes so far:

    git commit -am 'Pulled updated translations from Transifex and converted to .mo'
    git push
    
  8. Create a new tag for the new version and push that too:

    git tag -a v2.X.X
    git push --tags
    
  9. Create a new source distribution and universal wheel for the new version

    python setup.py sdist
    python setup.py bdist_wheel --universal
    
  10. Install twine (if not already installed) and upload to the new distribution files to the PyPi test environment.

    pip install twine
    twine upload dist/* -r pypitest
    
  11. Test that the new test distribution installs okay:

    mktmpenv
    pip install -i https://testpypi.python.org/pypi wagtailmenus
    deactivate
    
  12. If all okay, push distribution files to the live PyPi:

    twine upload dist/* -r pypi
    
  13. Edit the release notes for the release from https://github.com/rkhleics/wagtailmenus/releases, by copying and pasting the content from docs/releases/x.x.x.rst

  14. Crack open a beer - you earned it!

Release notes

Wagtailmenus 2.6.0 release notes

Note

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).

Note

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

Note

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

Wagtailmenus 2.5.2 release notes

There are no changes in this release

Wagtailmenus 2.5.1 release notes

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.

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

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.

Wagtailmenus 2.4.3 release notes

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.

Wagtailmenus 2.4.2 release notes

There are no changes in this release.

Wagtailmenus 2.4.1 release notes

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), please try running pip uninstall wagtailmenus first, then install the latest version.

Wagtailmenus 2.4.0 release notes

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

Wagtailmenus 2.3.2 release notes

This is a maintenence release to fix a bug that was resulting 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.

Wagtailmenus 2.3.1 release notes

This is a maintenence release to fix to address the following:

  • Code example formatting fixes, and better use of headings in README.md.
  • Added ‘on_delete=models.CASCADE’ to all relationship fields on models where no ‘on_delete’ behaviour was previously set (Django 2.0 compatibility).
  • Marked a missing string for translation (@einsfr)
  • Updated translations for Lithuanian, Portuguese (Brazil), and Russian. Many thanks to @mamorim, @treavis and @einsfr!

Wagtailmenus 2.3.0 release notes

What’s new?
Introducing the AbstractLinkPage model!

The newly added AbstractLinkPage model can be easily sub-classed and used in projects to create ‘link pages’ that act in a similar fashion to menu items when appearing in menus, but can be placed in any part of the page tree.

Find out more about this feature

Introducing the MenuPageMixin model!

Most of the functionality from MenuPage model has been abstracted out to a MenuPageMixin model, that can more easily be mixed in to existing page type models.

All Menu classes are now ‘request aware’

A new set_request() method on all Menu classes is used to set a request attribute on the Menu instance, immediately after initialisation, allowing you to referencing self.request from most methods to access the current HttpRequest object

Added get_base_page_queryset() method to all Menu classes

That can be overridden to change the base QuerySet used when identifying pages to be included in a menu when rendering. For example developers could use self.request.user to only ever include pages that the current user has some permissions for.

Overridable menu classes for section_menu and children_menu tags

Added the WAGTAILMENUS_SECTION_MENU_CLASS_PATH setting, which can be used to override the Menu class used when using the {% section_menu %} tag.

Added the WAGTAILMENUS_CHILDREN_MENU_CLASS_PATH setting, which can be used to override the Menu class used when using the {% children_menu %} tag.

Other minor changes
  • Added wagtail 1.10 and django 1.11 test environments to tox
  • Renamed test_frontend.py to test_menu_rendering.py
  • In situations where request.site hasn’t been set by wagtail’s SiteMiddleware, the wagtailmenus context processor will now use the default site to generate menus with.
  • Updated AbstractMenuItem.clean() to only ever return field-specific validation errors, because Wagtail doesn’t render non-field errors for related models added to the editor interface using InlinePanel.
  • Refactored runtest.py to accept a deprecation argument that can be used to surface deprecation warnings that arise when running tests.
  • Added Russian translations (submitted by Alex einsfr).
Upgrade considerations

Several methods on the MenuPage model have been updated to accept a request parameter. If you’re upgrading to version 2.3.0 from a previous version, it’s not necessary to make any changes immediately in order for wagtailmenus to work, but if you’re using the MenuPage class in your project, and are overriding any of the following methods:

  • modify_submenu_items()
  • has_submenu_items()
  • get_repeated_menu_item()

Then you should think about updating the signatures of those methods to accept the new argument and pass it through when calling super(). See the following code for an 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
       ):
           # 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)
           """
           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)
               """
               Additional menu items can be objects with the necessary attributes,
               or simple dictionaries. `href` is used for the link URL, and `text`
               is the text displayed for each link. Below, I've also used
               `active_class` to add some additional CSS classes to these items,
               so that I can target them with additional CSS
               """
               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 has_submenu_items(
           self, current_page, allow_repeating_parents, original_menu_tag,
           menu_instance, request
       ):
           """
           Because `modify_submenu_items` is being used to add additional menu
           items, we need to indicate in menu templates that `ContactPage` objects
           do have submenu items in main menus, even if they don't have children
           pages.
           """
           if original_menu_tag == 'main_menu':
               return True
           return super(ContactPage, self).has_submenu_items(
               current_page, allow_repeating_parents, original_menu_tag,
               menu_instance, request)

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

Wagtailmenus 2.2.3 release notes

This is a maintenence release to fix a bug that was resulting 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.

Wagtailmenus 2.2.2 release notes

This is a maintenence release to improve general code quality and make project management easier. There are no compatiblilty issues to worry about.

What’s changed?
  • Update codebase to better handle situations where request isn’t available in the context when rendering, or request.site hasn’t been set.
  • Got the project set up in Transifex (finally!): https://www.transifex.com/rkhleics/wagtailmenus/
  • Updated translatable strings throughout the project to use named variable substitution, and unmarked a few exception messages.
  • Added Lithuanian translations (submitted by Matas Dailyda).

Wagtailmenus 2.2.1 release notes

This is a maintenence release to improve code quality and project management.

What’s changed?
  • Updated travis/tox test settings to test against Wagtail 1.9 & Django 1.10.
  • Removed a couple of less useful travis/tox environment tests to help with test speed.
  • Made use of ‘extras_require’ in setup.py to replace multiple requirements files.
  • Optimised the app_settings module so that we can ditch the questionable stuff we’re doing with global value manipulation on app load (solution inspired by django-allauth).
  • Added new semantic version solution to the project (inspired by wagtail).

Wagtailmenus 2.2.0 release notes

What’s new?
  • Wagtailmenus now makes use of django’s built-in django.template.loader.select_template() method to provide a more intuitive way for developers to override templates for specific menus without having to explicitly specify alternative templates via settings or via the template and sub_menu_template options for each menu tag.

    See the updated documentation for each tag for information about where wagtailmenus looks for templates.

  • Added the WAGTAILMENUS_SITE_SPECIFIC_TEMPLATE_DIRS setting to allow developers to choose to have wagtailmenus look in additional site-specific locations for templates to render menus.

  • wagtailmenus no longer errors if a page’s relative_url() method raises a TypeError.

  • Brazilian Portuguese language translations added by @MaxKurama.

Wagtailmenus 2.1.4 release notes

This is a maintenence release to fix a bug that was resulting 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.

Wagtailmenus 2.1.3 release notes

This is a maintenence release to fix a bug in the section_menu tag that arose when attempting to apply the correct active class to section_root when the modify_submenu_items() method has been overridden to return additional items without an active_class attribute (like in the example code in README)

Wagtailmenus 2.1.2 release notes

This is a maintenence release to fix a bug that was preventing reordered menu items from retaining their new order after saving (the Meta class on the new abstract models had knocked out the sort_order ordering from wagtail.wagtailcore.models.Orderable).

Wagtailmenus 2.1.1 release notes

This is a maintenence release to fix a bug introduced in 2.1.0 preventing the app from being installed via pip

Wagtailmenus 2.1.0 release notes

There is a know bug with this release when attempting to install using pip. Please update to v2.1.1 instead.

What’s new?
  • Added official support for wagtail v1.8
  • Added WAGTAILMENUS_MAIN_MENU_MODEL and WAGTAILMENUS_FLAT_MENU_MODEL settings to allow the default main and flat menu models to be swapped out for custom models.
  • Added WAGTAILMENUS_MAIN_MENU_ITEMS_RELATED_NAME and WAGTAILMENUS_FLAT_MENU_ITEMS_RELATED_NAME settings to allow the default menu item models to be swapped out for custom models.
  • Added the WAGTAILMENUS_PAGE_FIELD_FOR_MENU_ITEM_TEXT setting to allow developers to specify a page attribute other than title to be used to populate the text attribute for menu items linking to pages.
  • Added german translations by Pierre (@bloodywing).

Wagtailmenus 2.0.3 release notes

This is a maintenence release to fix a migration related issue raised by @urlsangel: https://github.com/rkhleics/wagtailmenus/issues/85

Wagtailmenus 2.0.2 release notes

This release is broken and shouldn’t be use. Skip straight to v2.0.3 instead.

Wagtailmenus 2.0.1 release notes

This is a maintenence release to fix a bug reported by some users when using the {% main_menu %} tag without a max_levels value.

Wagtailmenus 2.0.0 release notes

What’s new?
New use_specific and max_levels fields for menu models

MainMenu and FlatMenu models now have two new fields:

  • use_specific: To allow the default use_specific setting when rendering that menu to be changed via the Wagtail CMS.
  • max_levels: To allow the default max_levels setting when rendering that menu to be changed via the admin area.

Find the field in the collapsed ADVANCED SETTINGS panel at the bottom of the edit form

More use_specific options available

The use_specific menu tag argument can now be one of 4 integer values, allowing for more fine-grained control over the use of Page.specific and PageQuerySet.specific() when rendering menu tags.

Developers not using the MenuPage model or overriding any of wagtail’s Page` methods involved in URL generation can now enjoy better performance by choosing not to fetch any specific pages at all during rendering. Simply pass ``use_specific=USE_SPECIFIC_OFF or use_specific=0 to the tag, or update the use_specific field value on your MainMenu or FlatMenu objects via the Wagtail admin area.

Basic argument valdation added to template tags

The max_levels, use_specific, parent_page and menuitem_or_page arguments passed to all template tags are now checked to ensure their values are valid, and if not, raise a ValueError with a helpful message to aid debugging.

Upgrade considerations
Dropped features
  • Dropped support for the WAGTAILMENUS_DEFAULT_MAIN_MENU_MAX_LEVELS and WAGTAILMENUS_DEFAULT_FLAT_MENU_MAX_LEVELS settings. Default values are now set using the max_levels field on the menu objects themselves.
  • Dropped support for the WAGTAILMENUS_DEFAULT_MAIN_MENU_USE_SPECIFIC and WAGTAILMENUS_DEFAULT_FLAT_MENU_USE_SPECFIC settings. Default values are now set using the use_specific field on the menu objects themselves.
  • The has_submenu_items() method on MenuPage no longer accepts the check_for_children argument.
  • The modify_submenu_items() and has_submenu_items() methods on the MenuPage model now both accept an optional menu_instance kwarg.
  • Added the WAGTAILMENUS_ADD_EDITOR_OVERRIDE_STYLES setting to allow override styles to be disabled.
Run migrations after updating!

New fields have been added to MainMenu and FlatMenu models, so you’ll need to run the migrations for those. Run the following:

django manage.py migrate wagtailmenus
Setting max_levels and use_specific on your existing menus

Edit your existing MainMenu and FlatMenu objects via the Wagtail CMS.

You should see a new collapsed ADVANCED SETTINGS panel at the bottom of each form, where both of these fields live.

Default values for MainMenu are max_levels=2 and use_specific=1.

Default values for FlatMenu are max_levels=1 and use_specific=1.

Switch to using the new use_specific options

If you’re passing use_specific=True or use_specific=False to the any of the menu tags, you’ll need to change that to one of the following:

  • use_specific=USE_SPECIFIC_OFF (or use_specific=0)
  • use_specific=USE_SPECIFIC_AUTO (or use_specific=1)
  • use_specific=USE_SPECIFIC_TOP_LEVEL (or use_specific=2)
  • use_specific=USE_SPECIFIC_ALWAYS (or use_specific=3)

See the following section of the README for further info: https://github.com/rkhleics/wagtailmenus/blob/v2.0.0/README.md#using-menupage

Changes to MenuPage.has_submenu_items() and MenuPage.modify_submenu_items()

If you’re extending these methods on your custom page types, you will likely need to make a few changes.

Firstly, the check_for_children argument is no longer supplied to has_submenu_items(), and is will no longer be accepted as a value.

Secondly, both the modify_submenu_items() and has_submenu_items() methods both accept an optional menu_instance argument, which you’ll need to also accept.

See the updated section of the README for corrected code examples: https://github.com/rkhleics/wagtailmenus/blob/v2.0.0/README.md#11-manipulating-sub-menu-items-for-specific-page-types

Adding the context_processor to settings

If you’re upgrading from wagtailmenus version 1.5.1 or lower, you’ll need to update your settings to include a context_processor from wagtailmenus. Your TEMPLATES setting should look something like the example below:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(PROJECT_ROOT, 'templates'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.debug',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.request',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.contrib.messages.context_processors.messages',
                'wagtail.contrib.settings.context_processors.settings',
                'wagtailmenus.context_processors.wagtailmenus',
            ],
        },
    },
]

Release notes for versions preceeding v2.0.0 can be found on Github: https://github.com/rkhleics/wagtailmenus/releases?after=v2.0.0