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.
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 urls.py.
Listing 1. Enabling the admin in urls.py
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Uncomment the next line to enable the admin:
(r'^admin/(.*)', admin.site.root),
)
|
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 admin.py. This is
usually placed in the top level of the application
directory, at the same level as models.py.
The examples application needs a models.py, provided in Listing 2. A corresponding admin.py is shown below.
Listing 2. A sample models.py 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):
return self.name
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 manage.py 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
Note that your models are not available yet because you haven't created an admin.py. Listing 3 demonstrates code that allows you to work with the models in the admin.
Listing 3. A sample admin.py
from django.contrib import admin
from more_with_admin.examples import models
class DocumentAdmin(admin.ModelAdmin):
pass
class CommentAdmin(admin.ModelAdmin):
pass
admin.site.register(models.Document, DocumentAdmin)
admin.site.register(models.Comment, 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
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 settings.py.
Listing 4. Editing settings.py to look in your template directories
TEMPLATE_DIRS = (
"/path/to/project/more_with_admin/templates",
"/path/to/project/more_with_admin/examples/templates",
) |
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 manage.py 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
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 admin.py 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
admin.site.register(models.Document, DocumentAdmin)
admin.site.register(models.Comment, 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
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
| Variable | Description |
|---|---|
object_id | This 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_id | If you are overriding multiple types of model pages, use
this to query the ContentTypes
framework to get the name of the model. See Resources 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 __init__.py file:
$ mkdir examples/templatetags/ $ touch examples/templatetags/__init__.py |
Create a new file called examples/templatetags/example_tags.py 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()
@register.inclusion_tag('comments.html')
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 admin.py 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
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.
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 admin.py. There are many, many public
methods you can override. Check the source in admin-source/options.py 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 models.py.
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 = 'no-reply@example.com'
recipient_list = ('admin@example.com',)
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 Resources, 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 models.py to include
an attribute recording who created the Document, as shown below.
Listing 12. Updating models.py 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 'http://example.com/preview/document/%d/' % self.id
def __unicode__(self):
return self.name
[...]
|
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 admin.py, 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
obj.save()
[...]
|
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.
Learn
-
Developers who are just getting started with Django or the
admin should begin with the Django
tutorial.
-
The main admin documentation and the admin
source code are the best complete references for
everything in the admin.
-
References for signals include the list
of signals provided by Django and their
documentation
on defining your own signals.
-
Learn about the two new admin features in Django V1.1: admin
actions and editable
lists.
-
The examples in this article demonstrated editing
model-specific pages. If you need to edit pages across
multiple models, become familiar with
Django's contenttypes framework.
-
The Django documentation includes
a
complete list of all database engines supported by
Django. Since Django V1.0, it has been possible to define
new external engines as well.
-
Learn more about the technique of
overriding redirection behavior in the admin.
-
Browse the
technology bookstore
for books on these and other technical topics.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Many Linux® distributions as well as Mac OS® X come with SQLite,
but if your system does not you can download SQLite from the
project site.
-
Since V2.5, Python has come bundled
with support for SQLite and requires no other driver.
For earlier Python versions, you must download
pysqlite
directly.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Participate in developerWorks blogs and get involved in the developerWorks community.

Liza Daly is a software engineer who specializes in applications for the publishing industry. She has been the lead developer on major online products for Oxford University Press, O'Reilly Media, and other publishers. Currently she is an independent consultant and the founder of Threepress, an open source project developing ebook applications.
Comments (Undergoing maintenance)





