django-flex-menus
Flexible site menus for Django.
Site navigation usually ends up spread between templates, context processors and a pile of
{% if perms %} blocks, which makes it hard to see what the menu actually contains and harder
still to render the same menu twice in two different shapes. django-flex-menus moves the
structure into Python: you declare a named tree once, attach a visibility rule to any part of
it, and templates ask for it by name and choose how it is drawn.
Scope & philosophy
What it is. A tree of menu items, declared in Python and rendered through a renderer you choose at the point of use. The tree carries structure, destinations and visibility rules. The renderer carries the markup.
What it deliberately is not.
- Not a theme, and not a set of templates. No markup ships as the supported surface. The
example/project contains Bootstrap 5 templates to demonstrate the renderer API, not to be imported from your project. - Not a permissions system. A visibility rule is any predicate — a permission check, a subscription tier, a feature flag, the time of day. The library never decides what makes an item visible, only when to ask.
- Not a database model. Menus are code, defined at startup and versioned with your project. There is no editing interface and no migration.
- Not a URL router. Destinations resolve through Django's own resolver.
When those pull against each other, the structure wins over the markup. Anything that would require the library to know what your HTML looks like belongs in a renderer instead.
Requirements
- Python 3.12+
- Django 5.2 or 6.0
Installation
pip install django-flex-menus
Add the app to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"flex_menu",
]
There are no models, so no migration is needed.
Quick start
Declare a menu in myapp/menus.py. Naming a Menu attaches it to the global tree, which is
what makes it reachable by name from a template:
from flex_menu import Menu, MenuItem
main_nav = Menu(
"main_nav",
children=[
MenuItem(name="home", view_name="home"),
MenuItem(name="dashboard", view_name="dashboard"),
],
)
Point a renderer at your markup in settings.py:
FLEX_MENUS = {
"renderers": {
"navbar": "myapp.renderers.NavbarRenderer",
"sidebar": "myapp.renderers.SidebarRenderer",
},
}
Then render it, as many times and in as many shapes as you need:
{% load flex_menu %}
<nav>{% render_menu 'main_nav' renderer='navbar' %}</nav>
<aside>{% render_menu 'main_nav' renderer='sidebar' %}</aside>
Controlling visibility
Every item takes a check: a boolean, or a callable receiving the request and any keyword
arguments passed to {% render_menu %}. An item whose check returns false is dropped, as is an
item whose URL cannot be resolved.
MenuItem(
name="billing",
view_name="billing",
check=lambda request, **kwargs: request.user.is_authenticated,
)
Working with a menu
main_nav.append(MenuItem(name="reports", view_name="reports"))
main_nav.extend([item_one, item_two])
main_nav.insert(item, 2)
main_nav.insert_after(item, "home")
reports = main_nav.get("reports")
reports.pop()
Run python manage.py render_menu to print the whole tree, or --name <menu> for one of them.
Configuration
Everything lives under a single FLEX_MENUS dict:
FLEX_MENUS = {
"renderers": {
"navbar": "myapp.renderers.NavbarRenderer",
},
# Used when {% render_menu %} is given no renderer
"default_renderer": "navbar",
# Whether unresolvable destinations are logged. Defaults to DEBUG.
"log_url_failures": False,
}
Thread safety
Menus are declared once at startup and shared across the process, so processing never mutates the declared tree — each request works against its own copy. That copy is real work on every render, so keep visibility checks cheap: they run for every item, on every request.
Documentation
Full documentation, including the renderer API and how to write your own, is at https://django-mvp.github.io/django-flex-menus/.
Changelog
See CHANGELOG.md.
License
MIT — see LICENSE.
Metadata
Release files for django-flex-menus 0.4.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_flex_menus-0.4.4.tar.gz | 20.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_flex_menus-0.4.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.6 kB