These guides cover specific features of django-simple-nav. If you're new to the library, start with Getting Started.
To control which navigation items a user can see, pass a permissions list. All permissions in the list must pass for the item to be shown.
NavItem(title="Dashboard", url="/dashboard/", permissions=["is_authenticated"])Use a string matching a boolean attribute on request.user:
NavItem(title="Login", url="/login/", permissions=["is_anonymous"])
NavItem(title="Dashboard", url="/dashboard/", permissions=["is_authenticated"])
NavItem(title="Dashboard", url="/dashboard/", permissions=["is_active"])
NavItem(title="Staff Area", url="/staff/", permissions=["is_staff"])Use standard Django permission strings:
NavItem(title="Edit Posts", url="/posts/edit/", permissions=["blog.change_post"])Multiple permissions require the user to have all of them:
NavItem(
title="Manage Posts",
url="/posts/manage/",
permissions=["blog.change_post", "blog.delete_post"],
)Pass any callable that takes an HttpRequest and returns a bool:
from django.http import HttpRequest
def is_beta_user(request: HttpRequest) -> bool:
return hasattr(request.user, "profile") and request.user.profile.is_beta
NavItem(title="Beta Feature", url="/beta/", permissions=[is_beta_user])You can mix types in a single list. All must pass:
NavItem(
title="Admin Panel",
url="/admin/",
permissions=["is_authenticated", "myapp.access_admin"],
)For OR logic, use a callable:
def is_staff_or_beta(request: HttpRequest) -> bool:
return request.user.is_staff or is_beta_user(request)
NavItem(title="Feature", url="/feature/", permissions=[is_staff_or_beta])Permissions on a NavGroup work the same as on NavItem. If a NavGroup has no url and all of its children are hidden by permissions, the group hides itself automatically.
NavGroup(
title="Admin",
permissions=["is_staff"],
items=[
NavItem(title="Users", url="/admin/users/", permissions=["auth.view_user"]),
NavItem(
title="Settings",
url="/admin/settings/",
permissions=["myapp.change_settings"],
),
],
)To pass additional data to your templates, use extra_context:
NavItem(
title="Blog",
url="/blog/",
extra_context={"icon": "book", "badge_count": 3},
)
NavGroup(
title="Settings",
items=[...],
extra_context={"icon": "gear"},
)Access the values directly on the item in your template:
<a href="{{ item.url }}">
{% if item.icon %}<span class="icon-{{ item.icon }}"></span>{% endif %}
{{ item.title }}
{% if item.badge_count %}<span class="badge">{{ item.badge_count }}</span>{% endif %}
</a>The keys title, url, active, and items are reserved and cannot be overridden by extra_context.
django-simple-nav works with Django's Jinja2 template backend. Register the template function in your Jinja2 environment:
from django_simple_nav.jinja2 import django_simple_nav
environment.globals.update({"django_simple_nav": django_simple_nav})Then use it as a function call in your templates:
<nav>
{{ django_simple_nav("config.nav.MainNav") }}
</nav>You can pass a Nav instance or override the template, just like the Django template tag:
{{ django_simple_nav(nav) }}
{{ django_simple_nav("config.nav.MainNav", "footer_nav.html.j2") }}In Jinja2 navigation templates, use bracket notation to access child items — item['items'] instead of item.items — because item.items calls the dict .items() method in Jinja2:
{% if item['items'] %}
{% for subitem in item['items'] %}
...
{% endfor %}
{% endif %}To render the same navigation with a different template — say, a header and a footer with different markup — pass a second argument to the template tag:
{% load django_simple_nav %}
<header>
{% django_simple_nav "config.nav.MainNav" %}
</header>
<footer>
{% django_simple_nav "config.nav.MainNav" "footer_nav.html" %}
</footer>Under the hood, Nav resolves templates through two methods you can override: get_template_name() and get_template().
Override get_template_name() to choose which template to load at runtime:
class MainNav(Nav):
items = [...]
def get_template_name(self) -> str:
if some_condition:
return "nav/compact.html"
return "nav/full.html"get_template() is called by render() to load the template. By default it calls get_template_name() and loads the template from disk. You can override it to return a raw template string instead:
class InlineNav(Nav):
items = [
NavItem(title="Home", url="/"),
NavItem(title="About", url="/about/"),
]
def get_template(self, template_name=None):
return """
<nav>
<ul>
{% for item in items %}
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
{% endfor %}
</ul>
</nav>
"""When get_template returns a string, render() compiles it as an inline template using the configured template engine.
You can also use this to customize how the template is loaded — for example, to add caching or fall back to a default:
from django.template.loader import get_template
class MainNav(Nav):
template_name = "nav/main.html"
items = [...]
def get_template(self, template_name=None):
try:
return super().get_template(template_name)
except TemplateDoesNotExist:
return super().get_template("nav/default.html")When a Nav is rendered, the call chain is:
render(request, template_name=None)— entry pointget_template(template_name=None)— loads the template; override for inline templates or custom loadingget_template_name()— returns the template name; override for dynamic selection
If template_name is passed (e.g. from the template tag's second argument), it skips get_template_name() and uses that name directly.
Instead of an import path string, you can pass a Nav instance through your view context. This lets you construct the navigation dynamically:
# views.py
from config.nav import MainNav
def example_view(request):
return render(request, "example.html", {"nav": MainNav()}){% load django_simple_nav %}
<nav>
{% django_simple_nav nav %}
</nav>Instead of writing the HTML for each item manually, you can use {{ item }} to let items render themselves:
<nav>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</nav>Each item renders using a default template — django_simple_nav/navitem.html for NavItem and django_simple_nav/navgroup.html for NavGroup. Self-rendering and dict-style access ({{ item.title }}, {{ item.url }}, etc.) work side by side.
To customize per item, set template_name:
NavItem(title="Home", url="/", template_name="myapp/custom_item.html")Or override it on a subclass:
class DropdownNavGroup(NavGroup):
template_name = "myapp/dropdown.html"You can also call render() directly on any item:
item = NavItem(title="Home", url="/")
html = item.render(request)Self-rendering requires the default templates to be discoverable by Django's template loader. With `APP_DIRS = True` (the default), this works automatically. With `APP_DIRS = False`, dict-style access still works — the default templates are only loaded when `{{ item }}` is used.