Doing more with the Django admin

Three ways to customize this powerful application to suit your needs

The Django admin

Django offers many features to prospective developers: a mature standard library, an active user community, and all the benefits of the Python language. While other Web frameworks can make similar claims, Django's unique asset is its built-in administration application — the admin.

The admin provides advanced Create-Read-Update-Delete (CRUD) functionality out of the box, eliminating hours of repetitive work. This is key for many Web applications in development, when programmers can quickly explore their database models, and in deployment, when nontechnical end users can use the admin to add and edit site content.

In the real world, there's always some customization to be performed. The Django documentation provides lots of guidelines for reskinning the basic look and feel of the admin, and Django itself includes some simple methods to override a subset of admin behavior. What if you need to do more? Where do you start? This article provides some guidelines for advanced admin customization.

A quick tour of the admin

Most Django developers are familiar with the default features of the admin. To quickly review, start with enabling the admin in Listing 1, by editing the top-level

Listing 1. Enabling the admin in
from django.conf.urls.defaults import *

# Uncomment the next two lines to enable the admin:
from django.contrib import admin

urlpatterns = patterns('',
    # Uncomment the next line to enable the admin:

You also need to add the django.contrib.admin application to settings.INSTALLED_APPS.

Before going further, anyone planning to extensively customize the admin is advised to become familiar with the source code. For operating systems that support shortcuts or symlinks, it can be useful to make one to the admin application.

The admin lives inside the Django package. Assuming it was installed with setuptools, the admin is in site-packages under django/contrib/admin. Here is an example of a symbolic link from a project to the Django admin source you can customize based on your operating system and the location of your Django installation for easy copying and reference:

$ ln -s /path/to/Python/install/site-packages/django/contrib/admin admin-source

The admin.autodiscover() method iterates through each application in settings.INSTALLED_APPS and looks for a file called This is usually placed in the top level of the application directory, at the same level as

The examples application needs a, provided in Listing 2. A corresponding is shown below.

Listing 2. A sample for this application
from django.db import models

class Document(models.Model):
    '''A Document is a blog post or wiki entry with some text content'''
    name = models.CharField(max_length=255)
    text = models.TextField()
    def __unicode__(self):

class Comment(models.Model):
    '''A Comment is some text about a given Document'''
    document = models.ForeignKey(Document, related_name='comments')
    text = models.TextField()

At this point, you can invoke the admin by running the Django development server:

python runserver

The admin is available at the default location of http://localhost:8000/admin/. After logging in, you see the basic admin screen shown below.

Figure 1. The basic Django admin screen
Basic Django admin screen
Basic Django admin screen

Note that your models are not available yet because you haven't created an Listing 3 demonstrates code that allows you to work with the models in the admin.

Listing 3. A sample
from django.contrib import admin
from more_with_admin.examples import models

class DocumentAdmin(admin.ModelAdmin):

class CommentAdmin(admin.ModelAdmin):
    pass, DocumentAdmin), CommentAdmin)

Now when you reload the main page in the admin, you see the new models available for use, as shown below.

Figure 2. The Django admin ready to support custom models
Django admin screen with custom models
Django admin screen with custom models

Customizing the admin model pages

The key to understanding how to customize the admin without hacking the Django source is to remember that the admin is an ordinary Django application like any other. Foremost, this means that the Django template inheritance system applies.

Django's template search order always prioritizes your own project's templates over any system ones. In addition, the admin attempts to search for hard-coded templates that match each model before resorting to the defaults. This provides an entry point for easy customization.

First, make sure Django is going to look in your template directories by editing the project's

Listing 4. Editing to look in your template directories

Then create the following directories in your project:

$ mkdir templates/admin/examples/document/
$ mkdir templates/admin/examples/comment/

The special behavior of the Django admin will check for a directory with your application's name (here, examples), then the name of the model (document and comment) before using the system templates to render that administration page.

Overriding a single model add/edit page

The name of the page that the admin uses for adding and editing a model instance is change_form.html. Start by creating a page called templates/admin/examples/document/change_form.html in the Document model directory and put a Django template inheritance line in it: {% extends "admin/change_form.html" %}.

Now you're ready to customize. Take some time to become familiar with the contents of the real admin/change_form.html. It's reasonably well organized into template blocks you can override, but some customizations may require wholesale copying of blocks. Nevertheless, using block-based template overrides is always preferable to copying the entire page.

What kinds of customizations might you want to do to the add/edit page? Maybe for each Document in the system you'd like to show a preview of the five most recent comments.

First, create some sample content.

Listing 5. Using the Django shell to create a sample Document with several comments
$ python shell
In [1]: from examples import models
In [2]: d = models.Document.objects.create(name='Test document', 
                                           text='This is a test document.')
In [3]: for c in range(0, 10):
   ...:     models.Comment.objects.create(text='Comment number %s' % c, document=d)

Now the admin list page shows one Document. Select that Document to bring up the default add/edit page shown below.

Figure 3. Default add/edit page featuring a Document
Default add/edit page
Default add/edit page

Note that the related comments aren't shown in any way. The standard way to show related models in the admin is to use the powerful Inline classes. Inline classes allow admin users to edit or add multiple related models on a single page. To see inlines in action, edit the application to match Listing 6.

Listing 6. Adding the related model comment to the Document admin
from django.contrib import admin
from more_with_admin.examples import models

class CommentInline(admin.TabularInline):
    model = models.Comment

class DocumentAdmin(admin.ModelAdmin):
    inlines = [CommentInline,]

class CommentAdmin(admin.ModelAdmin):
    pass, DocumentAdmin), CommentAdmin)

Figure 4 shows the new add/edit page after adding the TabularInline control.

Figure 4. Document add/edit page after the comment model is added as an Inline
Document add/edit page after the comment model is added as Inline
Document add/edit page after the comment model is added as Inline

This is certainly powerful, but it may be overkill if you just want to see a quick preview of the comments.

You can take two approaches here. One is to edit the HTML widgets associated with the inline using the Django admin widget interface; the Django documentation describes widgets in detail. The other approach is to modify the add/edit template directly. This approach is most useful when you don't want to use any admin-specific features at all.

If you don't want to allow any editing of the comments (perhaps because the users don't have sufficient permissions), but you do want them to be able to see the comments, you can modify change_form.html.

Variables provided by the Django admin

To add functionality to a model instance page, you need to know what data is already available from the admin. The two key variables are described below.

Table 1. Variables needed for customizing an admin template
object_idThis is the primary key for the object being edited. If you are customizing a specific instance page (such as Document), this is all you should need.
content_type_idIf you are overriding multiple types of model pages, use this to query the ContentTypes framework to get the name of the model. See Related topics for more information about content types.

Creating a template tag for inclusion in the admin page

Listing the related comments requires code that can't be entered directly into a Django template. The best solution for this is to use a template tag. First, create the template tag directory and file:

$ mkdir examples/templatetags/
$ touch examples/templatetags/

Create a new file called examples/templatetags/ and add the code shown below.

Listing 7. Template tag for retrieving the comments for a given Document ID
from django import template
from examples import models

register = template.Library()

def display_comments(document_id):
    document = models.Document.objects.get(id__exact=document_id)
    comments = models.Comment.objects.filter(document=document)[0:5]
    return { 'comments': comments }

Because this is an inclusion tag, you need to create the corresponding template file: comments.html. Edit the examples/templates/comments.html file and enter the code from Listing 8.

Listing 8. Template for displaying a set of comment previews
{% for comment in comments %}
<blockquote>{{ comment.text }}</blockquote>
{% endfor %}

Now it's time to add this into the admin page. Comment out the references to CommentInline in and make the changes shown in Listing 9 to your local version of change_form.html.

Listing 9. Including the template tag in the add/edit page
{% extends "admin/change_form.html" %}

{% load example_tags %}

{% block after_field_sets %}
  {% if object_id %}{% display_comments object_id %}{% endif %}
  {% endblock %}

It's important to check for the existence of object_id before attempting to use it because change_form.html is also used for creating new instances, in which case object_id is not yet available. The after_field_sets block is just one of many that are provided as extension points in the admin. Consult the change_form.html source page for others.

Figure 5 shows the updated form.

Figure 5. Document add/edit page after the custom template tag is included
Document add/edit page after the custom template tag is included
Document add/edit page after the custom template tag is included

Modifying admin behavior

Template overrides can only do so much. What if you want to change the actual flow and behavior of the admin? Hacking the source is one possibility, but that locks you in to the specific version of Django that you're using at the time of the update.

Overriding AdminModel methods

By default, clicking Save in the admin returns the user to the list page. Usually, that's fine, but what if you want to go directly to a preview page for an object that is outside of the admin? This is a common use case when developing a content management system (CMS).

Most of the functionality in the admin application is attached to the admin.ModelAdmin class. This is the class that objects inherit from in There are many, many public methods you can override. Check the source in admin-source/ for the class definition.

There are two ways to change the behavior of the Save button: You can override admin.ModelAdmin.response_add, which is responsible for the actual redirection after a save, or you can override admin.ModelAdmin.change_view. The latter is somewhat simpler.

Listing 10. Overriding the page users are directed to after a save event
class DocumentAdmin(admin.ModelAdmin):

    def change_view(self, request, object_id, extra_context=None):

        result = super(DocumentAdmin, self).change_view(request, object_id, extra_context)

        document = models.Document.objects.get(id__exact=object_id)
        if not request.POST.has_key('_addanother') and 
              not request.POST.has_key('_continue'):
            result['Location'] = document.get_absolute_url()
        return result

Now when users click Save, they are redirected to the preview page, rather than to the list page showing all Documents.

Adding features to the admin with signals

Signals are an under-used feature in Django that improve the modularity of your code. Signals define events, such as saving a model or loading a template, that function anywhere the Django project can listen for and respond to. This means you can easily augment the behavior of applications without having to modify them directly.

The admin provides one feature that application developers often want to modify: managing users via the django.contrib.auth.models.User class. Often, the admin is the only place in which Django users are added or modified, making it difficult to customize this useful class.

Imagine you want the administrator of the site to receive an e-mail every time a new User object is created. Because the User model isn't directly available in the project, it might seem like the only way to accomplish this is to subclass User or use an indirect method such as creating a dummy profile object to modify.

Listing 11 demonstrates how easy it is to add a function that runs when a User instance is saved. Signals are usually added to

Listing 11. Using Django signals to notify when a new user is added
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
from django.core.mail import send_mail

class Document(models.Model):

class Comment(models.Model):

def notify_admin(sender, instance, created, **kwargs):
    '''Notify the administrator that a new user has been added.'''
    if created:
       subject = 'New user created'
       message = 'User %s was added' % instance.username
       from_addr = ''
       recipient_list = ('',)
       send_mail(subject, message, from_addr, recipient_list)        

signals.post_save.connect(notify_admin, sender=User)

The post_save signal is provided by Django and fires any time a model is saved or created. The connect() method here is taking two arguments: a callback (notify_admin) and the sender argument, which specifies that this callback is only interested in save events from the User model.

Inside the callback, the post_save signal passes the sender (the model class), the instance of that model, and a boolean (created) that indicates whether the instance was just created. In this example, the method sends an e-mail if the User is being created; otherwise it does nothing.

A list of other Django-provided signals is provided in Related topics, as well as documentation on how to write your own signals.

Deeper modifications: Adding row-level permissions

A commonly requested feature of the Django admin is that its permission system be extended to include row-level permissions. By default, the admin allows for fine-grained control of roles and rights, but those roles apply only at the class level: a user can either modify all Documents or none.

Often, it is desirable to let users modify only specific objects. These are often called row-level permissions because they reflect the ability to modify only particular rows of a database table rather than blanket permission to modify any record in the table. A use case in the examples application might be that you want users to be able to see only Documents that they created.

First, update to include an attribute recording who created the Document, as shown below.

Listing 12. Updating to record the user who created each Document
from django.db import models
from django.db.models import signals
from django.contrib.auth.models import User
from django.core.mail import send_mail
class Document(models.Model):
    name = models.CharField(max_length=255)
    text = models.TextField()
    added_by = models.ForeignKey(User, 
      null=True, blank=True)

    def get_absolute_url(self):
        return '' %

    def __unicode__(self):

Next, you need to add code to automatically record which user created the Document. Signals don't work for this because the signal doesn't have access to the user object. However, the ModelAdmin class does provide a method that includes the request and, therefore, the current user as a parameter.

Modify the save_model() method in, as shown below.

Listing 13. Overriding a method in DocumentAdmin to save the current user to the database when created
from django.contrib import admin

class DocumentAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if getattr(obj, 'added_by', None) is None:
            obj.added_by = request.user
        obj.last_modified_by = request.user


If the value of added_by is None, this is a new record that hasn't been saved. (You could also check if change is false, which indicates that the record is being added, but checking for whether added_by is empty means that this also populates records that have been added outside of the admin.)

The next piece of row-level permissions is to restrict the list of documents to only those users who created them. The ModelAdmin class provides a hook for this via a method called queryset(), which determines the default query set returned by any list page.

As shown in Listing 14, override queryset() to restrict the listing to only those Documents created by the current user. Superusers can see all documents.

Listing 14. Overriding the query set returned by the list pages
from django.contrib import admin
from more_with_admin.examples import models

class DocumentAdmin(admin.ModelAdmin):
    def queryset(self, request):
        qs = super(DocumentAdmin, self).queryset(request)

        # If super-user, show all comments
        if request.user.is_superuser:
            return qs
        return qs.filter(added_by=request.user)

Now any requests for the Document list page in the admin show only those created by the current user (unless the current user is a superuser, in which case all documents are shown).

Of course, nothing currently prevents a determined user from accessing an edit page for an unauthorized document by knowing its ID. Truly secure row-level permissions require more method overriding. Because admin users are generally trusted to some degree anyway, sometimes basic permissions are enough to provide a streamlined workflow.


Customizing the Django admin does require some knowledge of the admin source code but little hacking. The admin is structured to be extensible using normal Python inheritance and some Django-only features such as signals.

The advantages of customizing the admin over creating a totally new administration interface are many:

  • Your application benefits from advances in Django as active development continues.
  • The admin already supports most common use cases.
  • External applications added to your project are automatically administrable side by side with your own code.

Looking ahead to Django V1.1, the admin provides two new features that are often requested: the ability to edit fields inline on the list pages and admin actions, which allow for bulk updates to many objects at once. Both additions will remove the need to write these common features from scratch while adding extension points for additional customization.

Downloadable resources

Related topics

Zone=Open source, Web development
ArticleTitle=Doing more with the Django admin