# Usage
These guides cover specific features of `django-simple-nav`. If you're new to the library, start with [Getting Started](getting-started.md).
## Permissions
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.
```python
NavItem(title="Dashboard", url="/dashboard/", permissions=["is_authenticated"])
```
### User attribute checks
Use a string matching a boolean attribute on `request.user`:
```python
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"])
```
### Django permissions
Use standard Django permission strings:
```python
NavItem(title="Edit Posts", url="/posts/edit/", permissions=["blog.change_post"])
```
Multiple permissions require the user to have all of them:
```python
NavItem(
title="Manage Posts",
url="/posts/manage/",
permissions=["blog.change_post", "blog.delete_post"],
)
```
### Callable permissions
Pass any callable that takes an `HttpRequest` and returns a `bool`:
```python
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])
```
### Combining permission types
You can mix types in a single list. All must pass:
```python
NavItem(
title="Admin Panel",
url="/admin/",
permissions=["is_authenticated", "myapp.access_admin"],
)
```
For OR logic, use a callable:
```python
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 groups
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.
```python
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"],
),
],
)
```
## Extra Context
To pass additional data to your templates, use `extra_context`:
```python
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:
```htmldjango
{% if item.icon %}{% endif %}
{{ item.title }}
{% if item.badge_count %}{{ item.badge_count }}{% endif %}
```
The keys `title`, `url`, `active`, and `items` are reserved and cannot be overridden by `extra_context`.
## Jinja2
`django-simple-nav` works with Django's Jinja2 template backend. Register the template function in your Jinja2 environment:
```python
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:
```jinja
```
You can pass a `Nav` instance or override the template, just like the Django template tag:
```jinja
{{ 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:
```jinja
{% if item['items'] %}
{% for subitem in item['items'] %}
...
{% endfor %}
{% endif %}
```
## Overriding the Template at Render Time
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:
```htmldjango
{% load django_simple_nav %}
{% django_simple_nav "config.nav.MainNav" %}
```
## Customizing Template Resolution
Under the hood, `Nav` resolves templates through two methods you can override: `get_template_name()` and `get_template()`.
### Dynamic template names with `get_template_name`
Override `get_template_name()` to choose which template to load at runtime:
```python
class MainNav(Nav):
items = [...]
def get_template_name(self) -> str:
if some_condition:
return "nav/compact.html"
return "nav/full.html"
```
### Full control with `get_template`
`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:
```python
class InlineNav(Nav):
items = [
NavItem(title="Home", url="/"),
NavItem(title="About", url="/about/"),
]
def get_template(self, template_name=None):
return """
"""
```
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:
```python
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")
```
### How it fits together
When a `Nav` is rendered, the call chain is:
1. **`render(request, template_name=None)`** — entry point
2. **`get_template(template_name=None)`** — loads the template; override for inline templates or custom loading
3. **`get_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.
## Passing a Nav Instance from a View
Instead of an import path string, you can pass a `Nav` instance through your view context. This lets you construct the navigation dynamically:
```python
# views.py
from config.nav import MainNav
def example_view(request):
return render(request, "example.html", {"nav": MainNav()})
```
```htmldjango
{% load django_simple_nav %}
```
## Programmatic Navigation
Instead of subclassing `Nav`, you can construct one directly. This is useful when you want to build the items list with conditionals or loops:
```python
from django_simple_nav.nav import Nav, NavItem
items = [NavItem(title="Home", url="/")]
if show_dashboard:
items.append(NavItem(title="Dashboard", url="/dashboard/"))
main_nav = Nav(template_name="main_nav.html", items=items)
```
### Factory functions
For conditions that depend on the request (user authentication, permissions, session data), define a callable that accepts the request and returns a `Nav`:
```python
# config/nav.py
from django.http import HttpRequest
from django_simple_nav.nav import Nav, NavItem
def main_nav(request: HttpRequest) -> Nav:
items = [NavItem(title="Home", url="/")]
if request.user.is_authenticated:
items.append(NavItem(title="Dashboard", url="/dashboard/"))
if request.user.is_staff:
items.append(NavItem(title="Admin", url="/admin/"))
return Nav(template_name="main_nav.html", items=items)
```
Use the dotted import path in the template tag, the same way you would with a `Nav` class:
```htmldjango
{% load django_simple_nav %}
{% django_simple_nav "config.nav.main_nav" %}
```
The callable receives the current `request` and its return value is rendered as the navigation. This works in both Django templates and Jinja2.
## Self-Rendering Items
Instead of writing the HTML for each item manually, you can use `{{ item }}` to let items render themselves:
```htmldjango
```
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`:
```python
NavItem(title="Home", url="/", template_name="myapp/custom_item.html")
```
Or override it on a subclass:
```python
class DropdownNavGroup(NavGroup):
template_name = "myapp/dropdown.html"
```
You can also call `render()` directly on any item:
```python
item = NavItem(title="Home", url="/")
html = item.render(request)
```
```{note}
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.
```