or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdmenus.mdpages.mdpermissions.mdplugins.mdtemplates.mdutilities.md

menus.mddocs/

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

```