0
# Menu System
1
2
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.
3
4
## Capabilities
5
6
### Menu Base Classes
7
8
Foundation classes for creating custom menu providers and modifying existing menus.
9
10
```python { .api }
11
class Menu:
12
"""
13
Base class for menu providers that generate navigation nodes.
14
15
Attributes:
16
renderer: Menu renderer instance
17
request: Current HTTP request
18
"""
19
20
def get_nodes(self, request):
21
"""
22
Generate menu nodes for navigation.
23
24
Args:
25
request (HttpRequest): Current request
26
27
Returns:
28
list: List of NavigationNode instances
29
"""
30
31
class Modifier:
32
"""
33
Base class for menu modifiers that alter navigation structure.
34
35
Used to modify menu nodes after generation, such as:
36
- Adding CSS classes
37
- Filtering nodes based on permissions
38
- Marking active/ancestor nodes
39
- Adding custom attributes
40
"""
41
42
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
43
"""
44
Modify navigation nodes.
45
46
Args:
47
request (HttpRequest): Current request
48
nodes (list): Navigation nodes to modify
49
namespace (str): Menu namespace
50
root_id (str): Root node identifier
51
post_cut (bool): Whether cutting has been applied
52
breadcrumb (bool): Whether this is breadcrumb rendering
53
54
Returns:
55
list: Modified navigation nodes
56
"""
57
58
class NavigationNode:
59
"""
60
Represents a single navigation menu item.
61
62
Attributes:
63
title: Display title for menu item
64
url: URL for navigation link
65
id: Unique node identifier
66
parent_id: Parent node identifier for hierarchy
67
parent_namespace: Parent node namespace
68
attr: Dictionary for custom attributes
69
visible: Whether node should be displayed
70
selected: Whether node is currently selected
71
ancestor: Whether node is ancestor of current page
72
sibling: Whether node is sibling of current page
73
descendant: Whether node is descendant of current page
74
"""
75
76
def __init__(self, title, url, id, parent_id=None, parent_namespace=None, attr=None, visible=True):
77
"""Initialize navigation node."""
78
79
def get_descendants(self):
80
"""Get all descendant nodes."""
81
82
def get_ancestors(self):
83
"""Get all ancestor nodes."""
84
85
def get_absolute_url(self):
86
"""Get absolute URL for node."""
87
```
88
89
### Menu Pool Management
90
91
Global registry for menu providers and menu processing.
92
93
```python { .api }
94
class MenuPool:
95
"""
96
Menu pool for registering and managing menu providers.
97
98
Manages menu discovery, caching, and rendering coordination.
99
"""
100
101
def register_menu(self, menu_class):
102
"""
103
Register a menu provider.
104
105
Args:
106
menu_class (Menu): Menu class to register
107
"""
108
109
def unregister_menu(self, menu_class):
110
"""
111
Unregister a menu provider.
112
113
Args:
114
menu_class (Menu): Menu class to unregister
115
"""
116
117
def register_modifier(self, modifier_class):
118
"""
119
Register a menu modifier.
120
121
Args:
122
modifier_class (Modifier): Modifier class to register
123
"""
124
125
def get_nodes(self, request, namespace=None, root_id=None):
126
"""
127
Get navigation nodes from all registered menus.
128
129
Args:
130
request (HttpRequest): Current request
131
namespace (str, optional): Menu namespace filter
132
root_id (str, optional): Root node filter
133
134
Returns:
135
list: Combined navigation nodes
136
"""
137
138
# Global menu pool instance
139
menu_pool = MenuPool()
140
```
141
142
### Menu Template Tags
143
144
Template tags for rendering navigation menus with extensive customization options.
145
146
```python { .api }
147
# Template tag usage (in Django templates)
148
{% show_menu from_level to_level extra_inactive extra_active template namespace root_id %}
149
{% show_menu 0 100 100 100 %}
150
{% show_menu 0 2 0 1 "menu/custom_menu.html" %}
151
152
{% show_menu_below_id menu_id from_level to_level extra_inactive extra_active template namespace %}
153
{% show_menu_below_id "main-nav" 0 100 100 100 %}
154
155
{% show_sub_menu levels template namespace %}
156
{% show_sub_menu 1 %}
157
{% show_sub_menu 2 "menu/submenu.html" %}
158
159
{% show_breadcrumb from_level template namespace %}
160
{% show_breadcrumb 0 %}
161
{% show_breadcrumb 1 "menu/breadcrumb.html" %}
162
```
163
164
## Usage Examples
165
166
### Custom Menu Provider
167
168
```python
169
from menus.base import Menu, NavigationNode
170
from menus.menu_pool import menu_pool
171
from django.urls import reverse
172
173
class ProductMenu(Menu):
174
"""Custom menu for product categories."""
175
176
def get_nodes(self, request):
177
"""Generate product category navigation."""
178
nodes = []
179
180
# Main products node
181
products_node = NavigationNode(
182
title="Products",
183
url=reverse("product_list"),
184
id="products",
185
attr={'class': 'products-menu'}
186
)
187
nodes.append(products_node)
188
189
# Category subnodes
190
from myapp.models import Category
191
categories = Category.objects.filter(active=True)
192
193
for category in categories:
194
category_node = NavigationNode(
195
title=category.name,
196
url=reverse("category_detail", args=[category.slug]),
197
id=f"category-{category.id}",
198
parent_id="products",
199
attr={
200
'class': 'category-item',
201
'data-category-id': category.id
202
}
203
)
204
nodes.append(category_node)
205
206
return nodes
207
208
# Register the custom menu
209
menu_pool.register_menu(ProductMenu)
210
```
211
212
### Custom Menu Modifier
213
214
```python
215
from menus.base import Modifier
216
from menus.menu_pool import menu_pool
217
218
class ActiveMenuModifier(Modifier):
219
"""Add CSS classes based on current page."""
220
221
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
222
"""Add active/current classes to menu nodes."""
223
current_path = request.path
224
225
for node in nodes:
226
# Mark exact matches as current
227
if node.url == current_path:
228
node.selected = True
229
if 'class' in node.attr:
230
node.attr['class'] += ' current'
231
else:
232
node.attr['class'] = 'current'
233
234
# Mark ancestors as active
235
elif current_path.startswith(node.url) and node.url != '/':
236
node.ancestor = True
237
if 'class' in node.attr:
238
node.attr['class'] += ' active'
239
else:
240
node.attr['class'] = 'active'
241
242
return nodes
243
244
class PermissionMenuModifier(Modifier):
245
"""Filter menu nodes based on user permissions."""
246
247
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
248
"""Remove nodes user doesn't have permission to view."""
249
if not request.user.is_authenticated:
250
# Filter out staff-only menu items
251
nodes = [node for node in nodes
252
if not node.attr.get('staff_only', False)]
253
254
return nodes
255
256
# Register modifiers
257
menu_pool.register_modifier(ActiveMenuModifier)
258
menu_pool.register_modifier(PermissionMenuModifier)
259
```
260
261
### Menu Template Usage
262
263
```html
264
{% load menu_tags %}
265
266
<!-- Main navigation menu -->
267
<nav class="main-menu">
268
{% show_menu 0 2 100 100 "menu/main_navigation.html" %}
269
</nav>
270
271
<!-- Sidebar submenu -->
272
<aside class="sidebar">
273
<h3>Section Menu</h3>
274
{% show_sub_menu 1 "menu/sidebar_menu.html" %}
275
</aside>
276
277
<!-- Breadcrumb navigation -->
278
<div class="breadcrumb">
279
{% show_breadcrumb 0 "menu/breadcrumb.html" %}
280
</div>
281
282
<!-- Footer menu -->
283
<footer>
284
{% show_menu_below_id "footer" 0 1 0 0 "menu/footer_menu.html" %}
285
</footer>
286
```
287
288
### Custom Menu Templates
289
290
```html
291
<!-- menu/main_navigation.html -->
292
{% load menu_tags %}
293
294
{% for child in children %}
295
<li class="nav-item {% if child.selected %}current{% endif %} {% if child.ancestor %}active{% endif %}">
296
<a href="{{ child.url }}"
297
class="nav-link"
298
{% if child.attr.class %}class="{{ child.attr.class }}"{% endif %}>
299
{{ child.title }}
300
</a>
301
302
{% if child.children %}
303
<ul class="dropdown-menu">
304
{% show_menu_below_id child.id 0 1 0 0 "menu/dropdown_item.html" %}
305
</ul>
306
{% endif %}
307
</li>
308
{% endfor %}
309
```
310
311
```html
312
<!-- menu/breadcrumb.html -->
313
{% load menu_tags %}
314
315
<ol class="breadcrumb">
316
{% for ancestor in ancestors %}
317
<li class="breadcrumb-item">
318
<a href="{{ ancestor.url }}">{{ ancestor.title }}</a>
319
</li>
320
{% endfor %}
321
322
{% if current %}
323
<li class="breadcrumb-item active" aria-current="page">
324
{{ current.title }}
325
</li>
326
{% endif %}
327
</ol>
328
```
329
330
### Working with Menu Nodes
331
332
```python
333
from menus.menu_pool import menu_pool
334
from django.http import HttpRequest
335
336
# Get menu nodes programmatically
337
request = HttpRequest()
338
request.user = some_user
339
request.path = '/products/category-1/'
340
341
# Get all menu nodes
342
all_nodes = menu_pool.get_nodes(request)
343
344
# Get nodes for specific namespace
345
product_nodes = menu_pool.get_nodes(request, namespace="products")
346
347
# Process nodes
348
for node in all_nodes:
349
print(f"Node: {node.title} -> {node.url}")
350
351
if node.selected:
352
print(f" Current page: {node.title}")
353
354
if node.ancestor:
355
print(f" Ancestor: {node.title}")
356
357
# Get node hierarchy
358
descendants = node.get_descendants()
359
ancestors = node.get_ancestors()
360
361
print(f" Descendants: {len(descendants)}")
362
print(f" Ancestors: {len(ancestors)}")
363
```
364
365
### Advanced Menu Configuration
366
367
```python
368
# settings.py menu configuration
369
CMS_MENUS = {
370
'product_menu': {
371
'name': 'Product Navigation',
372
'enabled': True,
373
'cache_timeout': 3600,
374
}
375
}
376
377
# Custom menu with caching
378
class CachedProductMenu(Menu):
379
"""Product menu with custom caching."""
380
381
def get_nodes(self, request):
382
cache_key = f"product_menu_{request.LANGUAGE_CODE}"
383
nodes = cache.get(cache_key)
384
385
if nodes is None:
386
nodes = self._generate_nodes(request)
387
cache.set(cache_key, nodes, timeout=3600)
388
389
return nodes
390
391
def _generate_nodes(self, request):
392
"""Generate fresh menu nodes."""
393
# Implementation here
394
pass
395
```
396
397
## Types
398
399
```python { .api }
400
class MenuRenderer:
401
"""
402
Menu rendering engine.
403
404
Handles menu node processing, template rendering,
405
and caching coordination.
406
"""
407
408
def render(self, context, nodes, template):
409
"""Render menu nodes with template."""
410
411
class MenuModifierPool:
412
"""Registry for menu modifiers."""
413
414
def register_modifier(self, modifier_class):
415
"""Register menu modifier."""
416
417
def get_modifiers(self):
418
"""Get all registered modifiers."""
419
```