CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-cms

Lean enterprise content management powered by Django.

Pending
Overview
Eval results
Files

menus.mddocs/

Menu System

Django CMS includes a sophisticated menu framework that automatically generates navigation from page structure while allowing custom menu providers, menu modifications, and flexible template-based rendering.

Capabilities

Menu Base Classes

Foundation classes for creating custom menu providers and modifying existing menus.

class Menu:
    """
    Base class for menu providers that generate navigation nodes.
    
    Attributes:
        renderer: Menu renderer instance
        request: Current HTTP request
    """
    
    def get_nodes(self, request):
        """
        Generate menu nodes for navigation.
        
        Args:
            request (HttpRequest): Current request
            
        Returns:
            list: List of NavigationNode instances
        """

class Modifier:
    """
    Base class for menu modifiers that alter navigation structure.
    
    Used to modify menu nodes after generation, such as:
    - Adding CSS classes
    - Filtering nodes based on permissions
    - Marking active/ancestor nodes
    - Adding custom attributes
    """
    
    def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
        """
        Modify navigation nodes.
        
        Args:
            request (HttpRequest): Current request
            nodes (list): Navigation nodes to modify
            namespace (str): Menu namespace
            root_id (str): Root node identifier
            post_cut (bool): Whether cutting has been applied
            breadcrumb (bool): Whether this is breadcrumb rendering
            
        Returns:
            list: Modified navigation nodes
        """

class NavigationNode:
    """
    Represents a single navigation menu item.
    
    Attributes:
        title: Display title for menu item
        url: URL for navigation link
        id: Unique node identifier
        parent_id: Parent node identifier for hierarchy
        parent_namespace: Parent node namespace
        attr: Dictionary for custom attributes
        visible: Whether node should be displayed
        selected: Whether node is currently selected
        ancestor: Whether node is ancestor of current page
        sibling: Whether node is sibling of current page
        descendant: Whether node is descendant of current page
    """
    
    def __init__(self, title, url, id, parent_id=None, parent_namespace=None, attr=None, visible=True):
        """Initialize navigation node."""
        
    def get_descendants(self):
        """Get all descendant nodes."""
        
    def get_ancestors(self):
        """Get all ancestor nodes."""
        
    def get_absolute_url(self):
        """Get absolute URL for node."""

Menu Pool Management

Global registry for menu providers and menu processing.

class MenuPool:
    """
    Menu pool for registering and managing menu providers.
    
    Manages menu discovery, caching, and rendering coordination.
    """
    
    def register_menu(self, menu_class):
        """
        Register a menu provider.
        
        Args:
            menu_class (Menu): Menu class to register
        """
    
    def unregister_menu(self, menu_class):
        """
        Unregister a menu provider.
        
        Args:
            menu_class (Menu): Menu class to unregister
        """
    
    def register_modifier(self, modifier_class):
        """
        Register a menu modifier.
        
        Args:
            modifier_class (Modifier): Modifier class to register
        """
    
    def get_nodes(self, request, namespace=None, root_id=None):
        """
        Get navigation nodes from all registered menus.
        
        Args:
            request (HttpRequest): Current request
            namespace (str, optional): Menu namespace filter
            root_id (str, optional): Root node filter
            
        Returns:
            list: Combined navigation nodes
        """

# Global menu pool instance
menu_pool = MenuPool()

Menu Template Tags

Template tags for rendering navigation menus with extensive customization options.

# Template tag usage (in Django templates)
{% show_menu from_level to_level extra_inactive extra_active template namespace root_id %}
{% show_menu 0 100 100 100 %}
{% show_menu 0 2 0 1 "menu/custom_menu.html" %}

{% show_menu_below_id menu_id from_level to_level extra_inactive extra_active template namespace %}
{% show_menu_below_id "main-nav" 0 100 100 100 %}

{% show_sub_menu levels template namespace %}
{% show_sub_menu 1 %}
{% show_sub_menu 2 "menu/submenu.html" %}

{% show_breadcrumb from_level template namespace %}
{% show_breadcrumb 0 %}
{% show_breadcrumb 1 "menu/breadcrumb.html" %}

Usage Examples

Custom Menu Provider

from menus.base import Menu, NavigationNode
from menus.menu_pool import menu_pool
from django.urls import reverse

class ProductMenu(Menu):
    """Custom menu for product categories."""
    
    def get_nodes(self, request):
        """Generate product category navigation."""
        nodes = []
        
        # Main products node
        products_node = NavigationNode(
            title="Products",
            url=reverse("product_list"),
            id="products",
            attr={'class': 'products-menu'}
        )
        nodes.append(products_node)
        
        # Category subnodes
        from myapp.models import Category
        categories = Category.objects.filter(active=True)
        
        for category in categories:
            category_node = NavigationNode(
                title=category.name,
                url=reverse("category_detail", args=[category.slug]),
                id=f"category-{category.id}",
                parent_id="products",
                attr={
                    'class': 'category-item',
                    'data-category-id': category.id
                }
            )
            nodes.append(category_node)
        
        return nodes

# Register the custom menu
menu_pool.register_menu(ProductMenu)

Custom Menu Modifier

from menus.base import Modifier
from menus.menu_pool import menu_pool

class ActiveMenuModifier(Modifier):
    """Add CSS classes based on current page."""
    
    def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
        """Add active/current classes to menu nodes."""
        current_path = request.path
        
        for node in nodes:
            # Mark exact matches as current
            if node.url == current_path:
                node.selected = True
                if 'class' in node.attr:
                    node.attr['class'] += ' current'
                else:
                    node.attr['class'] = 'current'
            
            # Mark ancestors as active
            elif current_path.startswith(node.url) and node.url != '/':
                node.ancestor = True
                if 'class' in node.attr:
                    node.attr['class'] += ' active'
                else:
                    node.attr['class'] = 'active'
        
        return nodes

class PermissionMenuModifier(Modifier):
    """Filter menu nodes based on user permissions."""
    
    def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
        """Remove nodes user doesn't have permission to view."""
        if not request.user.is_authenticated:
            # Filter out staff-only menu items
            nodes = [node for node in nodes 
                    if not node.attr.get('staff_only', False)]
        
        return nodes

# Register modifiers
menu_pool.register_modifier(ActiveMenuModifier)
menu_pool.register_modifier(PermissionMenuModifier)

Menu Template Usage

{% load menu_tags %}

<!-- Main navigation menu -->
<nav class="main-menu">
    {% show_menu 0 2 100 100 "menu/main_navigation.html" %}
</nav>

<!-- Sidebar submenu -->
<aside class="sidebar">
    <h3>Section Menu</h3>
    {% show_sub_menu 1 "menu/sidebar_menu.html" %}
</aside>

<!-- Breadcrumb navigation -->
<div class="breadcrumb">
    {% show_breadcrumb 0 "menu/breadcrumb.html" %}
</div>

<!-- Footer menu -->
<footer>
    {% show_menu_below_id "footer" 0 1 0 0 "menu/footer_menu.html" %}
</footer>

Custom Menu Templates

<!-- menu/main_navigation.html -->
{% load menu_tags %}

{% for child in children %}
    <li class="nav-item {% if child.selected %}current{% endif %} {% if child.ancestor %}active{% endif %}">
        <a href="{{ child.url }}" 
           class="nav-link"
           {% if child.attr.class %}class="{{ child.attr.class }}"{% endif %}>
            {{ child.title }}
        </a>
        
        {% if child.children %}
            <ul class="dropdown-menu">
                {% show_menu_below_id child.id 0 1 0 0 "menu/dropdown_item.html" %}
            </ul>
        {% endif %}
    </li>
{% endfor %}
<!-- menu/breadcrumb.html -->
{% load menu_tags %}

<ol class="breadcrumb">
    {% for ancestor in ancestors %}
        <li class="breadcrumb-item">
            <a href="{{ ancestor.url }}">{{ ancestor.title }}</a>
        </li>
    {% endfor %}
    
    {% if current %}
        <li class="breadcrumb-item active" aria-current="page">
            {{ current.title }}
        </li>
    {% endif %}
</ol>

Working with Menu Nodes

from menus.menu_pool import menu_pool
from django.http import HttpRequest

# Get menu nodes programmatically
request = HttpRequest()
request.user = some_user
request.path = '/products/category-1/'

# Get all menu nodes
all_nodes = menu_pool.get_nodes(request)

# Get nodes for specific namespace
product_nodes = menu_pool.get_nodes(request, namespace="products")

# Process nodes
for node in all_nodes:
    print(f"Node: {node.title} -> {node.url}")
    
    if node.selected:
        print(f"  Current page: {node.title}")
    
    if node.ancestor:
        print(f"  Ancestor: {node.title}")
    
    # Get node hierarchy
    descendants = node.get_descendants()
    ancestors = node.get_ancestors()
    
    print(f"  Descendants: {len(descendants)}")
    print(f"  Ancestors: {len(ancestors)}")

Advanced Menu Configuration

# settings.py menu configuration
CMS_MENUS = {
    'product_menu': {
        'name': 'Product Navigation',
        'enabled': True,
        'cache_timeout': 3600,
    }
}

# Custom menu with caching
class CachedProductMenu(Menu):
    """Product menu with custom caching."""
    
    def get_nodes(self, request):
        cache_key = f"product_menu_{request.LANGUAGE_CODE}"
        nodes = cache.get(cache_key)
        
        if nodes is None:
            nodes = self._generate_nodes(request)
            cache.set(cache_key, nodes, timeout=3600)
        
        return nodes
    
    def _generate_nodes(self, request):
        """Generate fresh menu nodes."""
        # Implementation here
        pass

Types

class MenuRenderer:
    """
    Menu rendering engine.
    
    Handles menu node processing, template rendering,
    and caching coordination.
    """
    
    def render(self, context, nodes, template):
        """Render menu nodes with template."""

class MenuModifierPool:
    """Registry for menu modifiers."""
    
    def register_modifier(self, modifier_class):
        """Register menu modifier."""
        
    def get_modifiers(self):
        """Get all registered modifiers."""

Install with Tessl CLI

npx tessl i tessl/pypi-django-cms

docs

index.md

menus.md

pages.md

permissions.md

plugins.md

templates.md

utilities.md

tile.json