Menu & Sidebar
The sidebar is driven entirely by the menu list in settings.ADMINLTE. Each item is a dict, and the package runs every item through a filter pipeline that resolves named routes to URLs, computes the active state for the current request, and hides items behind a permission or callable.
A real menu example
ADMINLTE = {
"menu": [
{"text": "Dashboard", "url": "/", "icon": "bi bi-speedometer"},
{"header": "CONTENT"},
{"text": "Posts", "icon": "bi bi-file-post", "submenu": [
{"text": "All posts", "route": "posts:index", "icon": "bi bi-circle"},
{"text": "New post", "route": "posts:create", "icon": "bi bi-circle",
"can": "blog.add_post"}, # hidden unless the user has the perm
]},
{"text": "Reports", "route": ["report", {"type": "sales"}],
"icon": "bi bi-graph-up", "label": "3", "label_color": "danger"},
{"text": "Docs", "url": "https://adminlte.io", "icon": "bi bi-book", "target": "_blank"},
],
}
Item keys
| Key | Purpose |
|---|---|
text | The visible label of the item. |
header | Renders a non-clickable section header instead of a link. |
route | A named Django route; resolved via reverse(). Pass [name, {kwargs}] or [name, [args]] for parameters. |
url | A raw URL (relative paths are prefixed with /; external/mailto:/tel: pass through unchanged). |
icon | An icon class, e.g. bi bi-people (Bootstrap Icons). |
icon_color | Icon colour modifier. |
label / label_color | A badge after the item, with its contextual colour. |
target | Link target, e.g. _blank. |
active | Explicit active patterns (string or list of * wildcards), or a literal boolean. |
can | Permission string, list of strings, or a callable — the item is dropped if denied. |
can_params | Object passed to user.has_perm(perm, obj). |
submenu | A nested list of items, rendered as a collapsible treeview. |
topnav / topnav_right | Place the item in the left / right navbar instead of the sidebar. |
The filter pipeline
Every item flows through an ordered list of filters (ADMINLTE["filters"]), each a class with a transform(item) -> item | None method; returning None drops the item. The defaults run in order:
| Filter | Does |
|---|---|
GateFilter | Drops items the current user may not see (the can key), recursing into submenus. |
HrefFilter | Resolves each item's href from route or url (request-independent). |
ActiveFilter | Marks an item active when request.path matches its patterns; a branch is active if any child is. |
SearchFilter | Normalises navbar-search items (request-independent). |
Per-request filter callables
The can key accepts a callable that receives the current request — ideal for logic that doesn't fit a Django permission string. Define it in your settings module and reference it directly:
def is_staff(request):
return request.user.is_authenticated and request.user.is_staff
ADMINLTE = {
"menu": [
{"text": "Admin tools", "route": "tools", "icon": "bi bi-tools", "can": is_staff},
{"text": "Billing", "route": "billing", "can": ["billing.view", "billing.manage"]},
],
}
You can also add your own filter to the pipeline. Subclass BaseFilter (custom filters default to per_request = True, which is always safe) and add its dotted path to filters:
ADMINLTE = {
"filters": [
"django_adminlte4.menu.filters.GateFilter",
"django_adminlte4.menu.filters.HrefFilter",
"django_adminlte4.menu.filters.ActiveFilter",
"django_adminlte4.menu.filters.SearchFilter",
"myproject.menu.BadgeCountFilter", # your own
],
}
Active-state behaviour
- If you set
activeto a string or list, those patterns (with*wildcards) are matched againstrequest.path. - A literal boolean
activeis respected as-is. - Otherwise patterns are derived from the item's
url/ resolvedhref, soroute:items get active detection automatically. - A parent with a submenu becomes active (and opens) when any child is active;
/only matches the home path, never everything.
Request-independent filters (HrefFilter, SearchFilter) run once per process (keyed by language for i18n_patterns); only the request-dependent ones (gate, active) re-run per request — so a large menu stays cheap.