An app to help you manage menus in your Wagtail projects more consistently.
Mixins and base classes for integrating Wagtail pages with the WagtailMenus system. These components enable pages to participate in navigation structures, control their menu behavior, and provide specialized page types for menu functionality.
A mixin class that adds menu-related functionality to any Wagtail page, providing settings and behavior for controlling how pages appear in menus.
class MenuPageMixin:
"""
Mixin for pages with menu functionality.
Adds menu-related fields and methods to control how pages
participate in menu structures and navigation.
"""
# Menu behavior fields
repeat_in_subnav: models.BooleanField # Whether to repeat in sub-navigation
repeated_item_text: models.CharField # Custom text for repeated items
# Menu settings panels for admin
menu_settings_panels: list
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=None, request=None, use_absolute_page_urls=False
):
"""
Modify menu items for sub-menu display.
Args:
menu_items: List of menu items to modify
current_page: The current page being viewed
current_ancestor_ids: IDs of current page ancestors
current_site: Current site object
allow_repeating_parents (bool): Whether to show repeating parent items
apply_active_classes (bool): Whether to apply active CSS classes
original_menu_tag (str): Name of original menu tag ('main_menu', etc.)
menu_instance: Menu instance calling this method
request: HTTP request object
use_absolute_page_urls (bool): Whether to use absolute URLs
Returns:
Modified menu items list
"""
def has_submenu_items(self, current_page, allow_repeating_parents,
original_menu_tag, menu_instance=None, request=None):
"""
Check if this page has items for sub-menu display.
Args:
current_page: The current page being viewed
allow_repeating_parents (bool): Allow parent items in sub-menus
current_site: Current site object
check_for_children (bool): Whether to check for child pages
Returns:
bool: True if page has sub-menu items
"""An abstract page class that combines MenuPageMixin with Wagtail's Page model, providing a complete base for pages that participate in menu systems.
class MenuPage(MenuPageMixin, Page):
"""
Abstract page with menu functionality.
Extends Wagtail's Page model with menu-related fields and methods.
Use as a base class for pages that need menu integration.
"""
class Meta:
abstract = TrueAn abstract page type specifically designed for menu items that link to external URLs or provide redirects.
class AbstractLinkPage:
"""
Base class for link pages that redirect to external URLs.
Provides functionality for pages that exist primarily to
link to external resources or redirect to other locations.
"""
link_url: str # External URL to redirect to
link_text: str # Display text for the link
def get_sitemap_urls(self, request=None):
"""
Return empty list as link pages shouldn't appear in sitemaps.
Args:
request: HTTP request object
Returns:
list: Empty list (link pages not included in sitemaps)
"""
def serve(self, request):
"""
Serve a redirect to the external URL.
Args:
request: HTTP request object
Returns:
HttpResponseRedirect: Redirect to link_url
"""A mixin for pages that need to define custom templates for their sub-menu rendering.
class DefinesSubMenuTemplatesMixin:
"""
Mixin for sub-menu template handling.
Allows pages to specify custom templates for rendering
their sub-menus at different levels.
"""
def get_sub_menu_template_names(self, current_level=1):
"""
Get template names for sub-menu rendering at specific levels.
Args:
current_level (int): Current menu depth level
Returns:
list: Template names for sub-menu rendering
"""from wagtailmenus.models import MenuPageMixin
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
class CustomPage(MenuPageMixin, Page):
"""Custom page with menu functionality."""
body = RichTextField()
content_panels = Page.content_panels + [
FieldPanel('body'),
]
# Add menu settings to the settings tab
settings_panels = Page.settings_panels + MenuPageMixin.menu_settings_panelsfrom wagtailmenus.models import MenuPage
from wagtail.fields import RichTextField
from wagtail.admin.panels import FieldPanel
class ArticlePage(MenuPage):
"""Article page with built-in menu functionality."""
body = RichTextField()
author = models.CharField(max_length=100)
content_panels = Page.content_panels + [
FieldPanel('body'),
FieldPanel('author'),
]
# menu_settings_panels are automatically includedfrom wagtailmenus.models import AbstractLinkPage
from wagtail.models import Page
from wagtail.admin.panels import FieldPanel
from django.db import models
class ExternalLinkPage(AbstractLinkPage, Page):
"""Page that redirects to external URLs."""
# Additional fields beyond AbstractLinkPage
description = models.TextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('link_url'),
FieldPanel('link_text'),
FieldPanel('description'),
]
def get_link_text(self):
"""Get display text for menu items."""
return self.link_text or self.titleclass SpecialPage(MenuPageMixin, Page):
"""Page with custom menu behavior."""
def modify_submenu_behaviour(self, menu_items, current_page,
current_ancestor_ids, current_site,
check_for_children=True):
"""Custom sub-menu behavior."""
# Add custom menu items
custom_items = self.get_custom_menu_items()
menu_items.extend(custom_items)
# Filter items based on user permissions
if hasattr(current_page, 'request'):
user = getattr(current_page.request, 'user', None)
if user and not user.is_staff:
menu_items = [item for item in menu_items
if not getattr(item, 'staff_only', False)]
return menu_items
def has_submenu_items(self, current_page, allow_repeating_parents=True,
current_site=None, check_for_children=True):
"""Check for custom sub-menu items."""
# Always return True if we have custom items
if self.get_custom_menu_items():
return True
# Otherwise use default behavior
return super().has_submenu_items(
current_page, allow_repeating_parents,
current_site, check_for_children
)from wagtailmenus.models import DefinesSubMenuTemplatesMixin
class CategoryPage(DefinesSubMenuTemplatesMixin, MenuPageMixin, Page):
"""Page with custom sub-menu templates."""
category_type = models.CharField(max_length=50, choices=[
('products', 'Products'),
('services', 'Services'),
('resources', 'Resources'),
])
def get_sub_menu_template_names(self, current_level=1):
"""Return category-specific sub-menu templates."""
templates = []
if self.category_type == 'products':
templates.append('menus/product_submenu.html')
elif self.category_type == 'services':
templates.append('menus/service_submenu.html')
# Add level-specific templates
templates.append(f'menus/submenu_level_{current_level}.html')
templates.append('menus/submenu.html') # Fallback
return templates<!-- Check if current page supports menu functionality -->
{% if page.show_in_menus %}
<nav class="page-navigation">
{% children_menu parent_page=page max_levels=1 %}
</nav>
{% endif %}
<!-- Show sub-navigation if page has menu items -->
{% if page.has_submenu_items %}
<aside class="subnav">
{% section_menu show_section_root=False %}
</aside>
{% endif %}# In your page models, configure menu settings
class MyPage(MenuPageMixin, Page):
# ... other fields
settings_panels = Page.settings_panels + [
# Menu settings are added via MenuPageMixin.menu_settings_panels
FieldPanel('show_in_menus'),
FieldPanel('repeat_in_subnav'),
] + MenuPageMixin.menu_settings_panels# Page integration field types
class PageIntegrationTypes:
show_in_menus: bool
repeat_in_subnav: bool
link_url: str
link_text: str
menu_settings_panels: list
# Method parameter and return types
class PageMethodTypes:
menu_items: list
current_page: 'Page'
current_ancestor_ids: list[int]
current_site: 'Site'
check_for_children: bool
allow_repeating_parents: bool
current_level: intInstall with Tessl CLI
npx tessl i tessl/pypi-wagtailmenus