Tutorial¶
In this tutorial, we’ll add navigation to the Django polls app — first by hand, then by refactoring to use django-simple-nav. By the end, you’ll have a navigation bar with links, active page highlighting, and permission-based visibility, all defined in Python.
We’re picking up where Django’s official polls tutorial leaves off. If you haven’t gone through it, you’ll need a working polls app with these views:
Polls list —
polls:indexPoll detail —
polls:detailPoll results —
polls:results
You’ll also need django.contrib.auth set up so you can log in to the admin.
The starting point¶
Right now the polls app doesn’t have any navigation. We’ll add a nav bar to every page with these links:
Polls — always visible, links to the polls list
Admin — only visible to staff users
Log out / Log in — changes based on whether the user is authenticated
Let’s start by doing it the way you would without any library.
Adding a group¶
Let’s say we want to group some links together. We can add a “Results” dropdown under each poll. But for our simple polls nav, let’s group the authentication links:
Update polls/nav.py:
from django.http import HttpRequest
from django_simple_nav.nav import Nav
from django_simple_nav.nav import NavGroup
from django_simple_nav.nav import NavItem
def is_anonymous(request: HttpRequest) -> bool:
return not request.user.is_authenticated
class PollsNav(Nav):
template_name = "polls_nav.html"
items = [
NavItem(title="Polls", url="polls:index"),
NavItem(title="Admin", url="admin:index", permissions=["is_staff"]),
NavGroup(
title="Account",
items=[
NavItem(
title="Log out",
url="admin:logout",
permissions=["is_authenticated"],
),
NavItem(
title="Log in",
url="admin:login",
permissions=[is_anonymous],
),
],
),
]
And update templates/polls_nav.html to handle groups:
<nav>
{% for item in items %}
{% if item.items %}
<span>{{ item.title }}:
{% for subitem in item.items %}
<a href="{{ subitem.url }}"{% if subitem.active %} class="active"{% endif %}>
{{ subitem.title }}
</a>
{% endfor %}
</span>
{% else %}
<a href="{{ item.url }}"{% if item.active %} class="active"{% endif %}>
{{ item.title }}
</a>
{% endif %}
{% endfor %}
</nav>
Reload and you’ll see “Account:” followed by either “Log out” or “Log in” depending on your login state. A NavGroup that has no visible children hides itself automatically, so the “Account” label won’t appear as an orphan.
What we built¶
Starting from the Django polls tutorial, we:
Built a navigation bar by hand — mixing URLs, permissions, and active state into template logic.
Installed
django-simple-navand moved the navigation structure into a Python class.Replaced the hand-written template logic with a clean loop over pre-resolved, pre-filtered items.
Added a
NavGroupto organize related links.
The navigation is now defined in one place, tested by one set of rules, and rendered by a template that only cares about markup.
Alternatives¶
There are other ways to approach navigation in Django. Here’s how they compare to what we just built.
{% include %} with context¶
You can put nav HTML in a partial template and include it everywhere:
{% include "nav.html" %}
This avoids copy-pasting the nav markup, but the permission checks and active state logic still live in the template. As the nav grows, the template grows with it. There’s no central Python definition of “what’s in the nav.”
Custom inclusion template tag¶
You can write your own template tag that builds the nav data and renders a template:
@register.inclusion_tag("nav.html", takes_context=True)
def my_nav(context):
request = context["request"]
items = [...]
return {"items": items}
This is essentially what django-simple-nav does under the hood — but you’d be writing the URL resolution, active state detection, and permission filtering yourself. If that’s all you need for a small project, it’s a reasonable approach. django-simple-nav provides it as a tested, reusable package.
Context processor¶
A context processor can inject nav data into every template:
def nav_context(request):
return {"nav_items": [...]}
This makes nav data available globally, but it runs on every request — even ones that don’t render a nav. It also doesn’t give you a clean separation between nav structure and nav rendering.
Other packages¶
The Django Packages navigation grid lists other options. A few worth noting:
django-simple-menu — A well-established library that takes a class-based approach similar to
django-simple-nav, with a focus on menu hierarchies and visibility conditions. It has been around longer and has a large user base.django-navutils — Provides breadcrumbs and menus with a node-based API.
Each takes a slightly different approach to the same problem. Pick the one that fits how you think about navigation in your project.