An app to help you manage menus in your Wagtail projects more consistently.
Settings management, configuration helpers, and utility functions for customizing WagtailMenus behavior across your Django project. This includes Django settings integration, default values, constants, and helper classes for managing menu configuration.
Main settings management class that provides access to configured values and defaults.
class WagtailmenusSettingsHelper:
"""
Main settings helper for WagtailMenus configuration.
Provides centralized access to all WagtailMenus settings with
appropriate defaults and validation.
"""
# Model configuration
MAIN_MENU_MODEL: str = 'wagtailmenus.MainMenu'
FLAT_MENU_MODEL: str = 'wagtailmenus.FlatMenu'
MAIN_MENU_ITEMS_RELATED_NAME: str = 'menu_items'
FLAT_MENU_ITEMS_RELATED_NAME: str = 'menu_items'
# Menu class configuration
CHILDREN_MENU_CLASS: str = 'wagtailmenus.models.ChildrenMenu'
SECTION_MENU_CLASS: str = 'wagtailmenus.models.SectionMenu'
# Template settings
DEFAULT_MAIN_MENU_TEMPLATE: str = 'menus/main_menu.html'
DEFAULT_FLAT_MENU_TEMPLATE: str = 'menus/flat_menu.html'
DEFAULT_SECTION_MENU_TEMPLATE: str = 'menus/section_menu.html'
DEFAULT_CHILDREN_MENU_TEMPLATE: str = 'menus/children_menu.html'
DEFAULT_SUB_MENU_TEMPLATE: str = 'menus/sub_menu.html'
SITE_SPECIFIC_TEMPLATE_DIRS: bool = False
# Default behavior settings
DEFAULT_SECTION_MENU_MAX_LEVELS: int = 2
DEFAULT_CHILDREN_MENU_MAX_LEVELS: int = 1
DEFAULT_ADD_SUB_MENUS_INLINE: bool = False
# Feature flags
FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS: bool = False
GUESS_TREE_POSITION_FROM_PATH: bool = True
# Admin/UI settings
MAIN_MENUS_EDITABLE_IN_WAGTAILADMIN: bool = True
FLAT_MENUS_EDITABLE_IN_WAGTAILADMIN: bool = True
MAIN_MENUS_ADMIN_CLASS: str = 'wagtailmenus.menuadmin.MainMenuAdmin'
FLAT_MENUS_ADMIN_CLASS: str = 'wagtailmenus.menuadmin.FlatMenuAdmin'
MAINMENU_MENU_ICON: str = 'list-ol'
FLATMENU_MENU_ICON: str = 'list-ol'
USE_CONDENSEDINLINEPANEL: bool = True
# Content settings
ACTIVE_CLASS: str = 'active'
ACTIVE_ANCESTOR_CLASS: str = 'ancestor'
PAGE_FIELD_FOR_MENU_ITEM_TEXT: str = 'title'
SECTION_ROOT_DEPTH: int = 3
# Model access properties
@property
def models(self):
"""Access to configured model classes."""
return ModelAccessor()
@property
def objects(self):
"""Access to menu object classes."""
return ObjectAccessor()Constants used throughout WagtailMenus for validation and choices.
# Maximum menu levels choices for admin forms
MAX_LEVELS_CHOICES: tuple[tuple[int, str], ...]
"""
Tuple of choices for maximum menu levels in admin interface.
Format: ((1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'))
"""
# Default menu level limits
DEFAULT_MAIN_MENU_MAX_LEVELS: int
DEFAULT_FLAT_MENU_MAX_LEVELS: int
DEFAULT_SECTION_MENU_MAX_LEVELS: int
DEFAULT_CHILDREN_MENU_MAX_LEVELS: int
# Template name constants
DEFAULT_MAIN_MENU_TEMPLATE: str
DEFAULT_FLAT_MENU_TEMPLATE: str
DEFAULT_SECTION_MENU_TEMPLATE: str
DEFAULT_CHILDREN_MENU_TEMPLATE: str
DEFAULT_SUB_MENU_TEMPLATE: strHelper classes for accessing configured models and menu objects.
class ModelAccessor:
"""Provides access to configured model classes."""
@property
def MAIN_MENU_MODEL(self):
"""Get the configured main menu model class."""
@property
def FLAT_MENU_MODEL(self):
"""Get the configured flat menu model class."""
class ObjectAccessor:
"""Provides access to menu object classes."""
@property
def SECTION_MENU_CLASS(self):
"""Get the section menu class."""
@property
def CHILDREN_MENU_CLASS(self):
"""Get the children menu class."""
@property
def SUB_MENU_CLASS(self):
"""Get the sub-menu class."""Utility functions for validation, inspection, and version management.
def validate_supplied_values(tag_name, **kwargs):
"""
Validate template tag parameters.
Args:
tag_name (str): Name of the template tag being validated
**kwargs: Template tag parameters to validate
Raises:
ValueError: If parameter values are invalid
"""
def accepts_kwarg(func, kwarg_name):
"""
Check if function accepts a specific keyword argument.
Args:
func: Function to inspect
kwarg_name (str): Name of keyword argument to check
Returns:
bool: True if function accepts the keyword argument
"""
def get_version(version_tuple):
"""
Return PEP 386-compliant version string from version tuple.
Args:
version_tuple (tuple): Version tuple (major, minor, patch, release, number)
Returns:
str: Formatted version string
"""
def get_main_version(version_tuple):
"""
Return main version components as string.
Args:
version_tuple (tuple): Version tuple
Returns:
str: Main version (major.minor.patch)
"""
def get_stable_branch_name(version_tuple):
"""
Return stable branch name for the version.
Args:
version_tuple (tuple): Version tuple
Returns:
str: Stable branch name
"""Context processor for adding menu-related variables to template contexts.
def wagtailmenus(request):
"""
Context processor providing menu-related template variables.
Automatically determines current page, section root, and page ancestors
based on the request path and site configuration.
Args:
request: HTTP request object
Returns:
dict: Context containing 'wagtailmenus_vals' with:
- current_page: Current page object (if determinable)
- section_root: Section root page for current location
- current_page_ancestor_ids: Tuple of ancestor page IDs
"""Exception classes for WagtailMenus-specific errors.
class RequestUnavailableError(Exception):
"""
Error when request is unavailable in multisite setup.
Raised when menu rendering requires request context but
none is available (e.g., in management commands).
"""
class SubMenuUsageError(Exception):
"""
Error for improper sub_menu tag usage.
Raised when sub_menu template tag is used outside of
proper menu rendering context.
"""Warning classes for deprecated functionality.
class RemovedInWagtailMenus32Warning(PendingDeprecationWarning):
"""Deprecation warning for functionality removed in version 3.2."""
class RemovedInWagtailMenus33Warning(PendingDeprecationWarning):
"""Pending deprecation warning for functionality to be removed in version 3.3."""# settings.py
# Basic WagtailMenus configuration
INSTALLED_APPS = [
# ... other apps
'wagtailmenus',
]
# Context processor (required for template tags)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'OPTIONS': {
'context_processors': [
# ... other processors
'wagtailmenus.context_processors.wagtailmenus',
],
},
},
]
# Optional: Custom model configuration
WAGTAILMENUS_MAIN_MENU_MODEL = 'myapp.CustomMainMenu'
WAGTAILMENUS_FLAT_MENU_MODEL = 'myapp.CustomFlatMenu'
# Optional: Template customization
WAGTAILMENUS_DEFAULT_MAIN_MENU_TEMPLATE = 'menus/custom_main_menu.html'
WAGTAILMENUS_DEFAULT_FLAT_MENU_TEMPLATE = 'menus/custom_flat_menu.html'
# Optional: Behavior settings
WAGTAILMENUS_DEFAULT_MAIN_MENU_MAX_LEVELS = 3
WAGTAILMENUS_DEFAULT_FLAT_MENU_MAX_LEVELS = 2
WAGTAILMENUS_FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS = True# settings.py - Advanced configuration
# Custom template settings
WAGTAILMENUS_SECTION_MENU_USE_SPECIFIC_TEMPLATES = True
WAGTAILMENUS_CHILDREN_MENU_USE_SPECIFIC_TEMPLATES = True
# Performance settings
WAGTAILMENUS_ACTIVE_ANCESTOR_CACHE_TIMEOUT = 300 # 5 minutes
# Multi-site behavior
WAGTAILMENUS_FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS = False
# Debug settings (development only)
WAGTAILMENUS_ADD_MENU_ITEMS_INLINE = Truefrom wagtailmenus.conf import settings
# Access configured models
MainMenu = settings.models.MAIN_MENU_MODEL
FlatMenu = settings.models.FLAT_MENU_MODEL
# Access configuration values
max_levels = settings.DEFAULT_MAIN_MENU_MAX_LEVELS
template_name = settings.DEFAULT_MAIN_MENU_TEMPLATE
# Check feature flags
if settings.FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS:
# Handle fallback behavior
passfrom wagtailmenus.conf.settings import WagtailmenusSettingsHelper
class CustomSettingsHelper(WagtailmenusSettingsHelper):
"""Custom settings helper with additional functionality."""
@property
def CUSTOM_MENU_FEATURE_ENABLED(self):
"""Check if custom menu feature is enabled."""
return getattr(settings, 'CUSTOM_MENU_FEATURE', False)
@property
def MENU_CACHE_TIMEOUT(self):
"""Get menu cache timeout with default."""
return getattr(settings, 'WAGTAILMENUS_CACHE_TIMEOUT', 300)
# Use custom settings helper
custom_settings = CustomSettingsHelper()
if custom_settings.CUSTOM_MENU_FEATURE_ENABLED:
cache_timeout = custom_settings.MENU_CACHE_TIMEOUTfrom wagtailmenus.utils.misc import validate_supplied_values
def custom_menu_tag(context, max_levels=None, **kwargs):
"""Custom menu template tag with validation."""
# Validate parameters
validate_supplied_values('custom_menu', max_levels=max_levels)
# Process menu rendering
if max_levels and max_levels > 5:
raise ValueError("max_levels cannot exceed 5")
# ... menu rendering logicfrom wagtailmenus.errors import RequestUnavailableError, SubMenuUsageError
def render_menu_safely(menu_type, **kwargs):
"""Safely render menu with error handling."""
try:
if menu_type == 'main':
return main_menu(**kwargs)
elif menu_type == 'section':
return section_menu(**kwargs)
except RequestUnavailableError:
# Handle missing request context
return render_fallback_menu()
except SubMenuUsageError:
# Handle improper sub-menu usage
return render_simple_menu()# The context processor automatically adds these variables to templates:
# In templates:
# {{ wagtailmenus_site }} - Current site
# {{ wagtailmenus_current_page }} - Current page
# {{ wagtailmenus_section_root }} - Section root page
# {{ wagtailmenus_ancestors }} - Page ancestors
# You can also access them in views:
def my_view(request):
context_data = wagtailmenus(request)
current_site = context_data['wagtailmenus_site']
current_page = context_data['wagtailmenus_current_page']
# ... use in view logic# Settings and configuration types
class ConfigurationTypes:
VERSION: tuple[int, int, int, str, int]
MAX_LEVELS_CHOICES: tuple[tuple[int, str], ...]
# Template settings
DEFAULT_MAIN_MENU_TEMPLATE: str
DEFAULT_FLAT_MENU_TEMPLATE: str
DEFAULT_SECTION_MENU_TEMPLATE: str
DEFAULT_CHILDREN_MENU_TEMPLATE: str
DEFAULT_SUB_MENU_TEMPLATE: str
# Level settings
DEFAULT_MAIN_MENU_MAX_LEVELS: int
DEFAULT_FLAT_MENU_MAX_LEVELS: int
DEFAULT_SECTION_MENU_MAX_LEVELS: int
DEFAULT_CHILDREN_MENU_MAX_LEVELS: int
# Feature flags
FLAT_MENUS_FALL_BACK_TO_DEFAULT_SITE_MENUS: bool
SECTION_MENU_USE_SPECIFIC_TEMPLATES: bool
CHILDREN_MENU_USE_SPECIFIC_TEMPLATES: bool
# Context processor return type
class ContextProcessorReturn:
wagtailmenus_site: 'Site'
wagtailmenus_current_page: 'Page'
wagtailmenus_section_root: 'Page'
wagtailmenus_ancestors: list['Page']Install with Tessl CLI
npx tessl i tessl/pypi-wagtailmenus