Categories
Python

Django Friday Tips: Custom Admin Pages

One of the great builtin features of Django is the admin app. It lets you, among other things, execute the usual CRUD operations on your data, search, filter and execute bulk actions on many records.

However the interface is a bit rigid, by default you have the “dashboard” with the list of models, the page listing your records for each model and the details page for each individual item.

What if you want to display other information to the admins? Such an overview of the system, aggregate data, some statistics, etc.

In this post I will address the steps required to add custom pages to the admin and include them in the main menu of the administration.

The starting point will be the website I used in a previous tip/post:

Regular Django admin dashboard, without any custom page on the menu.
Admin without any custom pages

1. Add a custom Admin Site

The first step is to create a new custom admin website, so we can easily modify its contents and functionality.

from django.contrib import admin

class YourCustomAdminSite(admin.AdminSite):
    pass

Now we will have to use this admin site across your project, which means changing your current admin settings to use this “site” (such as your ModelAdmins and your urls.py.

If the above is too much trouble and requires to many changes, this small “hack” before including the admin URLs will also work:

# urls.py

admin.site.__class__ = YourCustomAdminSite

2. Add a new view

In our new admin site we can now create views as methods, to handle different requests. Like this:

from django.contrib import admin
from django.template.response import TemplateResponse

class YourCustomAdminSite(admin.AdminSite):
    def custom_page(self, request):
        context = {"text": "Hello Admin", 
                   "page_name": "Custom Page"}
        return TemplateResponse(request,
                                "admin/custom_page.html", 
                                context)

3. Add a new template

As you can see in the above python snippet, we are using a template that doesn’t exist yet, so lets create it:

{# templates/admin/custom_page.html #}

{% extends 'admin/change_list.html' %}

{% block pagination %}{% endblock %}
{% block filters %}{% endblock filters %}
{% block object-tools %}{% endblock object-tools %}
{% block search %}{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
  <a href="{% url 'admin:index' %}">Home</a>
  {% if page_name %} &rsaquo; {{ page_name }}{% endif %}
</div>
{% endblock %}

{% block result_list %}
{{text}}
{% endblock result_list %}

(I chose to extend admin/change_list.html, but the content of this template is up to you, no restrictions here. If you decide to not extend any existing admin template, the last steps of 5. will not apply to your case)

4. Extend the admin URLs

To make this new endpoint accessible, we now have to edit the method that returns all existing URLs of the administration and append our new page.

from django.urls import path
...

class YourCustomAdminSite(admin.AdminSite):
    ...

    def get_urls(
        self,
    ):
        return [
            path(
                "custom_page/",
                self.admin_view(self.custom_page),
                name="custom_page",
            ),
        ] + super().get_urls()

At this point the custom page should be available at /admin/custom_page/, but users will not be able to find it, since no other link or button points to it.

5. Extend the menu items

The final step is to include a link to the new page in the existing menu, in order for it to be easily accessible. First we need to replace the current index_template:

...

class YourCustomAdminSite(admin.AdminSite):
    index_template = "admin/custom_index.html"
    ...

And add the following content to your new template:

{% extends "admin/index.html" %}

{% block content %}
<div id="content-main">
  {% include "admin/custom_app_list.html" with app_list=app_list show_changelinks=True %}
</div>
{% endblock %}

The included custom_app_list.html should look like this:

<div id="extra_links_wrapper" class="module">
    <table>
        <caption>
            <a class="section" title="Custom Pages">Custom Pages</a>
        </caption>
        <tr>
            <th scope="row">
                <a href="{% url 'admin:custom_page' %}">
                    Custom Page
                </a>
            </th>
            <td></td>
        </tr>
    </table>
</div>

{% include 'admin/app_list.html' %}

Basically we added a new section to the list containing a link to our new page.

Screenshot of the index page, with links to the new custom page
Links were added to the index page
Screenshot of the custom page that was created.
The custom page we just created

Looking good, the page is there with our content and is accessible through the index page. However many traditional elements on the page are missing (side menu, logout button, etc). To add them we just need some small changes to our view:

...

class YourCustomAdminSite(admin.AdminSite):
    ...

    def custom_page(self, request):
        context = {
            "text": "Hello Admin",
            "page_name": "Custom Page",
            "app_list": self.get_app_list(request),
            **self.each_context(request),
        }
        return TemplateResponse(request, "admin/custom_page.html", context)

Now it looks a lot better:

Screenshot of the custom page now with the other standard elements included.

But something is still missing. Where are the links to our custom pages?

It seems the standard app_list.html is being used. Lets replace it with our custom one by overriding nav_sidebar.html:

{# templates/admin/nav_sidebar.html #}

{% load i18n %}
<button class="sticky toggle-nav-sidebar" id="toggle-nav-sidebar"
  aria-label="{% translate 'Toggle navigation' %}"></button>
<nav class="sticky" id="nav-sidebar">
  {% include 'admin/custom_app_list.html' with app_list=available_apps show_changelinks=False %}
</nav>

Note: The app where you put the above template must be placed before the “admin” app in your “INSTALLED_APPS” list, otherwise the default template will be used anyway.

Screenshot of the custom page, with all elements and the correct links displayed on the sidebar
Custom admin page with all the elements

And for today, this is it. With the above changes you can add as many custom pages to your admin as you need and have full controls over their functionality.