The other day I was asked to add a new page on the admin site of django that was like a list view for a model but with different actions, different columns to display and with a filter applied to the list. After hours of searching on the docs I ended up reading the source code of the contrib.admin package to understand how to do it.

Feel free to skip all this text and inspect the code directly on github

Source Code

The goal

Let’s use the user model to create a new view on the admin. In this new view we want to display only the list of inactive users that are not admins of the site. And in this view we want to display the emails, date joined and last login. BUT we also want to keep our complete users page.

We’ll need une view /users/ to display the complete list and /users/inactive/ to display only the inactive users

The code

Admin.py

This is where we customize the admin pages. It’s a single file


  # -*- coding: utf-8 -*-
'''
Created on 2015-03-24 19:17
@summary: custom admin page for users
@author: pablo
'''
from django.contrib import admin
from django.contrib.auth.models import User
from django.conf.urls import patterns, url
from functools import update_wrapper
from .admin_views import InactiveUsersView


class UserAdmin(admin.ModelAdmin):

    list_display = ('username', 'email', 'is_active', 'is_staff', 'is_superuser')

    def get_urls(self):
        # this is just a copy paste from the admin code
        def wrap(view):
            def wrapper(*args, **kwargs):
                return self.admin_site.admin_view(view)(*args, **kwargs)
            return update_wrapper(wrapper, view)
        # get the default urls
        urls = super(UserAdmin, self).get_urls()
        # define my own urls
        my_urls = patterns(
            '',
            url(r'^inactive/$',
                wrap(self.changelist_view),
                name="inactive_users")
        )
        # return the complete list of urls
        return my_urls + urls

    def get_changelist(self, request):
        """
        This method must return the view to be used for listing the model
        """
        # for inactive users use the InactiveUsersView
        if request.resolver_match.url_name == "inactive_users":
            return InactiveUsersView
        return super(UserAdmin, self).get_changelist(request)

# we must unregister the default user model
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

The custom views

I wanted to separate the code and have the admin views in a different file but you could put it in the admin.py file too


from django.contrib.admin.views.main import ChangeList


class InactiveUsersView(ChangeList):
    """
    This view displays the list of inactive users
    """
    def __init__(self, *args, **kwargs):
        super(InactiveUsersView, self).__init__(*args, **kwargs)
        self.list_display = ('username', 'email', 'date_joined', 'last_login')

    def get_queryset(self, request):
        qs = super(InactiveUsersView, self).get_queryset(request)
        # filter inactive and admin users
        return qs.filter(is_staff=False, is_active=False, is_superuser=False)  

The result

The default users list view


Default user list view on django

The custom users list view


Custom list view of users

Final Notes

I tried and tried and searched and read the docs, and I wasn’t able to find a way to add a custom link on the index page of the admin (without installing another package that manages the admin). If you are using django-suit you could add a custom link on the sidebar

When you are customizing your admin site, you end up writing a lot of code inside the admin.py file and it might get dirty. It’s better to separate your code in multiple files. I also tried to create an admin submodule that works fine on production, but not in the unit tests (because of the admin registry)

Hope this helps