Skip to main content

django-flex-menus

Tests Build codecov PyPI Python License

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.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-flex-menus 0.4.6
File Size Uploaded
django_flex_menus-0.4.6.tar.gz 20.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-flex-menus 0.4.6
File Interpreter ABI Platform
django_flex_menus-0.4.6-py3-none-any.whl Python 3 none any Details

Total release size: 42.1 kB

Release files / django_flex_menus-0.4.6.tar.gz

Download URL django_flex_menus-0.4.6.tar.gz
Size 20.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0b3bba51246143206dbc5891467ab1927fb56296582855911afe43393e4c031f
BLAKE2b-256 checksum
How to use checksums
58bf1eff7e9c5ad33f4c310e2e11611c33bc85237bcdb75f73fc5471da1f21bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / django_flex_menus-0.4.6-py3-none-any.whl

Download URL django_flex_menus-0.4.6-py3-none-any.whl
Size 21.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05a20d93c315ceacb3350dea6363fafa755a7d564da3ee58acf5aa7b51824b56
BLAKE2b-256 checksum
How to use checksums
a0133e7e8b7bed83cb1d0978d4de507c9348cd97abde436f37671c4954bd37d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.6 This release

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page