Crowdbotics Logo

About Us arrow

We are on a mission to radically transform the software development lifecycle.

Home Blog ...

App Development

How to Add A Navigation Menu In Django Admin

I’ve praised Django Admin in the past. It’s one of the most useful features of Django. Despite all the advantages of Django Admin, it could still use a few improvements. One such tweak is adding a navigation menu in Django Admin. This tutorial will show you how to add one.

6 October 2021

by Kalpit Jain

Despite all the advantages of Django Admin, it could still use a few improvements. One such tweak is adding a navigation menu in Django Admin. This tutorial will show you how to add one.

When I started working with Django Admin I found it counter-intuitive to go back to homepage every time I needed to move to another module’s page. It’s something I wasn’t used to. I’m sure you’ve felt the same way (that’s why you’re here, I’m hoping!).

As developers, we’re used to navigating from one module to another directly from the navigation menu in every Admin Panel we’ve ever used. (The same goes for the client or employer you’re developing the admin panel for.) Sooner or later, you’ll likely find yourself tinkering with Django Admin templates, trying to add links to all the modules in the menu. This can get tricky.

Since the purpose of this post is to explain the procedure to add a navigation menu, we won’t be paying much attention to the design part. I’ll be using Bootstrap Navbar Component to create the UI, but you can use any other library or create your own UI from scratch.

Here’s what what our navigation menu in Django Admin will look like at the end of this tutorial:

Bootstrap Navbar in default Django Admin

Let’s create a new Django Project first.

Creating a Django Project

I’m using Python 3.6 and Django 2.1, but these instructions are more or less similar for other recent versions of Python & Django.

Create a new directory and execute the command below to create a python virtual environment, and activate it.

# Creates a virtual environment in subdirectory "venv"python -m venv venv
# Activate itsource venv/bin/activate  # LinuxvenvScriptsactivate  # Windows

While you’re in the virtual environment, install Django.

pip install Django  # to install the latest stable releasepip install Django==2.1  # to install a specific version

Once installed, you may configure the database and other settings. Without diving deeper into the setup instructions, let’s quickly create a project and fire up the development server.

django-admin startproject navbar  # create a Django project
python manage.py migrate  # run migrations
python manage.py createsuperuser  # create a super user
python manage.py runserver  # start the development server
Django development server started successfully

Now, when you navigate to http://127.0.0.1:8000/ it should show you Django’s default welcome page.

Django’s default welcome page

To access the admin panel, go to http://127.0.0.1:8000/admin/ then login using the credentials you created with python manage.py createsuperuser command.

This is what you get out of the box. A fully functional Django Admin — without navigation menu bar.

Default Django Admin without Navbar

Add Bootstrap Dependencies

Now that we have a functional Django Admin, let’s add a navigation menu.

We won’t be writing any Python/Django code yet. We’ll just replace Django Admin’s default header with a static bootstrap Navbar.

First create a new app. Creating a separate app has its benefits. I’ll cover that in a bit.

python manage.py startapp my_admin

Once done, create directories /templates/admin in my_admin app. Copy default Django Admin’s base.html and base_site.html to this directory.

In my case the default directory was found at /venv/Lib/site-packages/django/contrib/admin/templates/admin/.

We’ll be working on the newly copied files, i.e. files in /my_admin/templates/admin/.

Now, to use bootstrap Navbar, first we need to add relevant bootstrap CSS and JavaScript files. To do so, download bootstrap source files from their official website. We’re downloading source files because we won’t be using all features of Bootstrap. We’ll be using SASS preprocessor to pick only the parts we need.

Extract the downloaded files to /my_admin/static/admin/

With that completed, let’s start editing some HTML.

Open /my_admin/templates/admin/base.html and add the following snippet in the <head> section to add bootstrap CSS. Make sure you write correct path to bootstrap.scss file.

{% load compress %}{% compress css %}<link rel=”stylesheet” href=”{% static “admin/bootstrap-4.1.3/scss/bootstrap.scss” %}” type=”text/x-scss” charset=”utf-8">{% endcompress %}

Since we just need the Navbar component, you can remove the imports from bootstrap.scss which are not required, otherwise it might conflict with existing Django CSS. Here’s my version of bootstrap.scss for reference:

/*!
Bootstrap v4.1.3 (https://getbootstrap.com/)
Copyright 2011-2018 The Bootstrap Authors
Copyright 2011-2018 Twitter, Inc.
Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)*/
@import "functions";@import "variables";@import "mixins";//@import "root";//@import "reboot";//@import "type";//@import "images";//@import "code";//@import "grid";//@import "tables";//@import "forms";//@import "buttons";@import "transitions";@import "dropdown";//@import "button-group";//@import "input-group";//@import "custom-forms";//@import "nav";@import "navbar";//@import "card";//@import "breadcrumb";//@import "pagination";//@import "badge";//@import "jumbotron";//@import "alert";//@import "progress";//@import "media";//@import "list-group";//@import "close";//@import "modal";//@import "tooltip";//@import "popover";//@import "carousel";@import "utilities";//@import "print";

Similarly, add the following links to bootstrap JS modules. To do so, paste the following code just before closing </body> tag.

{# bootstrap js #}
{% compress js %}<script src=”{% static ‘admin/bootstrap-4.1.3/js/dist/util.js’ %}” type=”text/javascript” charset=”utf-8"><script src=”{% static ‘admin/bootstrap-4.1.3/js/dist/dropdown.js’ %}” type=”text/javascript” charset=”utf-8"><script src=”{% static ‘admin/bootstrap-4.1.3/js/dist/collapse.js’ %}” type=”text/javascript” charset=”utf-8">{% endcompress %}
{# END bootstrap js #}

                                                                       .    .    .

Handling ".scss Files

You may have noticed that we’re using .scss files in HTML directly, and the compress block is not supported by Django out of the box. If you’re using lint checks, your code editor is probably showing warnings by now.

This tag is provided by django-compressor to process .scss (and other) files. You may choose to use any other library or none at all. For the sake of this tutorial we’re using django-compressor.

To install and configure Django Compressor, follow these steps:

  1. Run pip install django-compressor django-libsass while you’re in Python virtual environment.
  2. Add compressor to INSTALLED_APPS in settings.py.
  3. Define COMPRESS_ROOT or STATIC_ROOT in settings.py.
  4. Add COMPRESS_PRECOMPILERS = ((‘text/x-scss’, ‘django_libsass.SassCompiler’),) to settings.py.
  5. Add the following in settings.py:
STATICFILES_FINDERS = (‘django.contrib.staticfiles.finders.FileSystemFinder’,‘django.contrib.staticfiles.finders.AppDirectoriesFinder’,# other finders..‘compressor.finders.CompressorFinder’,)

Detailed installation instructions of Django Compressor are available on readthedocs.

                                                                  .    .    .

Adding Navbar HTML

Now, let’s replace the default header with Bootstrap Navbar. Don’t bother with Django code yet, we’ll make the menu dynamic in next section. Delete (or comment out) the existing header code, it looks similar to this:

<div id="header">    <div id="branding">    {% block branding %}{% endblock %}    </div>    {% block usertools %}    {% if has_permission %}    <div id="user-tools">        {% block welcome-msg %}            {% trans 'Welcome,' %}            <strong>{% firstof user.get_short_name user.get_username %}</strong>.        {% endblock %}        {% block userlinks %}            {% if site_url %}                <a href="{{ site_url }}">{% trans 'View site' %}</a> /            {% endif %}            {% if user.is_active and user.is_staff %}                {% url 'django-admindocs-docroot' as docsroot %}                {% if docsroot %}                    <a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /                {% endif %}            {% endif %}            {% if user.has_usable_password %}            <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /            {% endif %}            <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>        {% endblock %}    </div>    {% endif %}    {% endblock %}    {% block nav-global %}{% endblock %}</div>

…and replace it with bootstrap Navbar. The following code is taken from Bootstrap documentation, we’ll modify it later.

<nav class=”navbar navbar-expand-lg navbar-light bg-light”>    <a class=”navbar-brand” href=”#”>Navbar</a>    <button class=”navbar-toggler” type=”button” data-toggle=”collapse” data-target=”#navbarSupportedContent” aria-controls=”navbarSupportedContent” aria-expanded=”false” aria-label=”Toggle navigation”>        <span class=”navbar-toggler-icon”></span>    </button>    <div class=”collapse navbar-collapse” id=”navbarSupportedContent”>        <ul class=”navbar-nav mr-auto”>            <li class=”nav-item active”><a class=”nav-link” href=”#”>Home <span class=”sr-only”>(current)</span></a></li>            <li class=”nav-item”><a class=”nav-link” href=”#”>Link</a></li>            <li class=”nav-item dropdown”>                <a class=”nav-link dropdown-toggle” href=”#” id=”navbarDropdown” role=”button” data-toggle=”dropdown” aria-haspopup=”true” aria-expanded=”false”> Dropdown</a>                <div class=”dropdown-menu” aria-labelledby=”navbarDropdown”>                    <a class=”dropdown-item” href=”#”>Action</a>                    <a class=”dropdown-item” href=”#”>Another action</a>                    <div class=”dropdown-divider”></div>                    <a class=”dropdown-item” href=”#”>Something else here</a>                </div>            </li>            <li class=”nav-item”><a class=”nav-link disabled” href=”#”>Disabled</a></li>        </ul>        <form class=”form-inline my-2 my-lg-0">            <input class=”form-control mr-sm-2" type=”search” placeholder=”Search” aria-label=”Search”>            <button class=”btn btn-outline-success my-2 my-sm-0" type=”submit”>Search</button>        </form>    </div></nav>

Also add the following snippet before closing </head> tag to avoid django CSS from overriding bootstrap’s CSS.

<style type=”text/css”>    .navbar ul li {        list-style-type: none;        padding: 0;    }    .navbar .dropdown-item {        width: auto;    }</style>

Our template is ready to show bootstrap Navbar.

Just one thing though. We need to add my_admin to INSTALLED_APPS in settings.py. Let’s do that quickly, and start the development server again.

Navigate to http://localhost:8000/admin/.

Voila! There’s the Navbar.

Let’s make it compatible with Django default Admin so that it plays nice with existing template files and makes modification easier.

Django Admin at this stage

                                                             .    .    .

Making the Code Compatible with Default Django Admin Template

First, replace <a class=”navbar-brand” href=”#”>Navbar</a> with {% block branding %}{% endblock %}. base_site.html specifies this part in Django.

Let’s quickly switch to base_site.html and replace the following block of code:

<h1 id=”site-name”><a href=”{% url ‘admin:index’ %}”>{{ site_header|default:_(‘Django administration’) }}</a></h1>

…with this:

<a href=”{% url ‘admin:index’ %}” class=”navbar-brand”>{{ site_header|default:_(‘Django administration’) }}</a>

Back to editing base.html. Replace Navbar menu items with the following code to dynamically populate menu with list of apps.

{% if app_list %}    <ul class="navbar-nav mr-auto">        {% for app in app_list %}            <li class="nav-item dropdown">                <a class="nav-link dropdown-toggle" href="{{ app.app_url }}" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ app.name }}</a>                <div class="dropdown-menu" aria-labelledby="navbarDropdown">                    {% for model in app.models %}                        {% if model.admin_url %}                            <a class="dropdown-item" href="{{ model.admin_url }}">{{ model.name }}</a>                        {% endif %}                    {% endfor %}                </div>            </li>        {% endfor %}    </ul>{% elif user.is_authenticated %}    <span class="navbar-text">You don't have permission to edit anything.</span>{% endif %}

Replace the bootstrap search form with the following code to display user action links.

{% block usertools %}    {% if has_permission %}        <ul class="navbar-nav my-2 my-lg-0">            <li class="nav-item dropdown dropleft text-right">                <a class="nav-link dropdown-toggle" href="#" id="nav-user-icon" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">                    {% block welcome-msg %}<strong>{% firstof user.get_short_name user.get_username %}</strong>{% endblock %}                </a>                <div class="dropdown-menu" aria-labelledby="nav-user-icon">                    {% block userlinks %}                        {% if site_url %}<a class="dropdown-item" href="{{ site_url }}">{% trans 'View site' %}</a>{% endif %}                        {% if user.is_active and user.is_staff %}                            {% url 'django-admindocs-docroot' as docsroot %}                            {% if docsroot %}<a class="dropdown-item" href="{{ docsroot }}">{% trans 'Documentation' %}</a>{% endif %}                        {% endif %}                        {% if user.has_usable_password %}<a class="dropdown-item" href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a>{% endif %}                        <a class="dropdown-item" href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>                    {% endblock %}                </div>            </li>        </ul>    {% endif %}{% endblock %}{% block nav-global %}{% endblock %}

Run the development server again, then navigate to Django admin. It works, right?

There’s a tiny issue though. If you navigate to any other page except the homepage, the navigation menu shows different items based on the current page. We did all this effort to show a menu with all items at all pages, didn’t we? Let’s fix that.


Showing All Apps In Navigation Menu in Django Admin

We’re using app_list tag provided by default in Django, but it does not return all apps’ list at all times. It takes current page as context, and modifies the result accordingly. The solution? We need a custom tag which can return all items at all times.

Create a python package named templatetags in my_admin app, and create a file utils.py in it. Paste the following code in this file.from django import template

from django.contrib import adminregister = template.Library()class CustomRequest:    def __init__(self, user):    self.user = user@register.simple_tag(takes_context=True)def get_app_list(context, **kwargs):    custom_request = CustomRequest(context['request'].user)    app_list = admin.site.get_app_list(custom_request)    return app_list

Next, in settings.py add the following to OPTIONS in TEMPLATES variable:

'libraries': {    'common_utils': 'my_admin.templatetags.common_utils',},

TEMPLATES might look something like this after adding it:

TEMPLATES = [    {        'BACKEND': 'django.template.backends.django.DjangoTemplates',        'DIRS': [],        'APP_DIRS': True,        'OPTIONS': {            'context_processors': [            'django.template.context_processors.debug',            'django.template.context_processors.request',            'django.contrib.auth.context_processors.auth',            'django.contrib.messages.context_processors.messages',            ],            'libraries': {                'common_utils': 'my_admin.templatetags.common_utils',            },        },    },]

Next, go back to base.html and make the following changes:

  1. Put {% load common_utils %} somewhere at the top of file to load our custom template tags.
  2. Put {% get_app_list as all_app_list %} above {% if app_list %}.
  3. Change app_list variable to all_app_list.
  4. Reload Django Admin in browser.

Your header in base.html might look something like this:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    {% block branding %}{% endblock %}
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
    {% load common_utils %}
    {% get_app_list as all_app_list %}
    {% if all_app_list %}
        <ul class="navbar-nav mr-auto">
            {% for app in all_app_list %}
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="{{ app.app_url }}" id="navbarDropdown"
                       role="button" data-toggle="dropdown" aria-haspopup="true"
                       aria-expanded="false">{{ app.name }}</a>
                    <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                        {% for model in app.models %}
                            {% if model.admin_url %}
                                <a class="dropdown-item"
                                   href="{{ model.admin_url }}">{{ model.name }}</a>
                            {% endif %}
                        {% endfor %}
                    </div>
                </li>
            {% endfor %}
        </ul>
    {% elif user.is_authenticated %}
        <span class="navbar-text">You don't have permission to edit anything.</span>
    {% endif %}
    {% block usertools %}
        {% if has_permission %}
            <ul class="navbar-nav my-2 my-lg-0">
                <li class="nav-item dropdown dropleft text-right">
                    <a class="nav-link dropdown-toggle" href="#" id="nav-user-icon" role="button"
                       data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        {% block welcome-msg %}
                            <strong>{% firstof user.get_short_name user.get_username %}</strong>{% endblock %}
                    </a>
                    <div class="dropdown-menu" aria-labelledby="nav-user-icon">
                        {% block userlinks %}
                            {% if site_url %}
                                <a class="dropdown-item" href="{{ site_url }}">{% trans 'View site' %}</a>
                            {% endif %}
                            {% if user.is_active and user.is_staff %}
                                {% url 'django-admindocs-docroot' as docsroot %}
                                {% if docsroot %}
                                    <a class="dropdown-item"
                                       href="{{ docsroot }}">{% trans 'Documentation' %}</a>
                                {% endif %}
                            {% endif %}
                            {% if user.has_usable_password %}
                                <a class="dropdown-item"
                                   href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a>
                            {% endif %}
                            <a class="dropdown-item"
                               href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
                        {% endblock %}
                    </div>
                </li>
            </ul>
        {% endif %}
    {% endblock %}
    {% block nav-global %}{% endblock %}
</div>
</nav>

We’re done! Everything should work as expected.

                                                              .    .    .

Some Tips and Complete Code

You might be tempted to edit the core files directly or not using existing Django template blocks, but the advantage of the above approach is that we get a modular standalone app. You can literally use this app with any standard Django Admin project and you’ll get a working navigation menu bar with no extra efforts.

We’ve used bootstrap in this tutorial to avoid spending time in creating UI. In your project you may use any other navigation menu component or create your own component from scratch.

What we are essentially doing is getting list of apps from our custom template tag and showing them in the menu as links. You can use the same approach and create material design drawer menu instead of Navbar! The best part is that you can override any part of Navbar as you would have done with default Django Admin template.

The project I created for this tutorial is available on github for reference: https://github.com/TheKalpit/Bootstrap-Navbar-in-Django-Admin

Thanks for reading!

Were you able to create Navbar easily using this tutorial? Do you have any other tips to improve this tutorial? Let me know your experience in comments below.

Thanks to William Wickey for help editing.