The Power of Django Admin (Even For Non-Django Projects)

Author: Steven C. Wilcox
Version: 1.0 [for PyCon 2008 (2008-03-15)]
Copyright: This work is licensed under the Creative Commons Attribution 3.0 United States License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/us/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.

The Admin interface for Django is powerful and can be useful for building stand-alone applications or as the official web-admin interface for non-Django projects. This will be a quick tour of the capabilities and limitations of the Django Admin interface as well as the database introspection utility that can be used to quickly get a Django application setup.

Contents

Introduction

Author Background

For more information about Steven Wilcox, visit http://devpicayune.com.

Purpose

This paper (and corresponding talk) is designed to shed light on some of the very robust features that are quickly gained by utilizing the supplied Admin Interface in the Django Web Framework. Because the Admin portion of the framework is so powerful and thorough, there are actually quite a few applications that could be built solely on the Admin screens. Also, due to the speed a project can be setup with the Admin Interface and the additional database introspection tool provided with Django, it's possible to quickly create web-based administration screens for an existing web or non-web project that's been originally created in any language so long as the database is supported by Django.

When properly utilized, the Admin Interface then becomes both an incredibly useful tool for standalone applications, administration screens for existing or legacy projects, and also an invaluable weapon for Python advocacy within an organization.

Assumptions

  • You know a little something about databases and you know at least a beginners level knowledge of Python
  • You know how to use the internet to search for anything not covered
  • Some of the information presented here could be incredibly wrong

Big Notice

ATTENTION: All code samples in this paper are presented using the Django NewForms-Admin which is currently just an active branch of the Django project with plans to merge into the trunk prior to version 1.0. While I hesitated to show information that was based on a version of Django that might not be suitable for production use at this moment, it's likely that within a matter of weeks, this will become the standard syntax and model for Admin use.

Check the NewForms-Admin branch for more information.

Also note that I've decided to just keep all my code in the models.py file. That's probably not good form, but for demonstration and teaching purposes, I believe it's more concise to keep it all in one file.

What's Not Covered

  • Setting up or installing Django
  • Django as a whole
  • A complete review of everything that can be with Django's ORM
  • A complete analysis of the entire Admin Interface supplied by Django
  • The Answer to Life, the Universe and Everything... oh wait, that's 42.

The Quick Admin Interface Tour

The Django Admin Interface provides two high level functions:

  1. a security structure for restricting access rights at both the users and group level
  2. ability to add/edit/delete data in tables in the database

Security

The security model in Django is built around the concept of users and groups. This is obviously, a pretty common and easily understood paradigm. Of note is the fact that a user can belong to multiple groups rather than just one.

The security model in Django (with respect to the Admin interface) also makes a couple of assumptions. As stated in The Django Book[1] regarding the Admin interface:

This is a Web-based interface, limited to trusted site administrators, that enables the adding, editing and deletion of site content.

So the biggest security assumption made is that the users are "trusted". But one of the reasons this paper/talk exists is that for a certain but fairly large set of applications, all the users of a particular application may be "trusted" to one extent or another. It's for this reason, that we can consider building an entire application using just the Admin interface (or at least relying on it for 90-99% of it).

django_admin_files/django_login.png

With regard to security and the Admin site, one important point to remember is that users must have the "Staff Status" checked for them to be able to utilize the Admin interface. The other shortcut around the group membership is administrator status which basically enables all rights for a user.

In what may also be considered part of the Django Admin interface, there is a log kept as a basic form of audit trail that logs users actions on the data that includes the user, object, and which fields may have been changed. This log contains action messages like "changed description" (for a field change) or "added date 'dad birthday'" (for the addition of a related child record).

Alternate Authentication

As soon the topic of users and authentication comes up, the more enterprise-oriented folks will begin asking about alternate ways to authenticate users. While the out-of-box system doesn't immediately grant the ability to authenticate against another system, several very easy to apply changes can be found to enable, for example, LDAP-based authentication. For most of these basic alternate authentication mechanisms, the Django tables that are used to manage users and groups and rights are all still present and the alternate authentication is used purely for just validation of the user id and password. [1]

[1]Check here and possibly here

Data Manipulation

The bulk of what people think of as the Admin interface is the actual ability to deal with application data as part of web-interface. The Django Admin interface provides the ability to add/edit/delete records in tables that are defined in a Django application (using Django ORM).

There are a ton of configurable features and options that can be defined for these screens that affect how the browse lists function and look as well as the restrictions, validations, etc... made on records being added or changed.

It's this full-featured data manipulation interface for non-technical users that we so desperately need for so many applications. And Django's Admin is obviously very complete and out-of-the-box beautiful versus so many other frameworks. It's just a logical choice when you need to have a good looking and full featured Admin interface built in an extremely short amount of time.

Getting Started

The purpose of this document is not to teach you to use Django, so you can refer to any of the more competant resources [2] to really get a proper feel for getting started on a new application and some of the options available as you setup Django projects and applications.

[2]The Django Book is always a great place to start!

Regardless of whether you're writing a brand new application or whether you're wanting to build an admin interface for existing project, you still have to start with a new project and application in Django. As a reminder, Django has a term of "project" for the container of the overall system which can contain one or more applications within that project. If done correctly, it's very possible to have applications which can then be portable and re-used within other projects.

Let's assume as our starting point, that you've followed the normal steps to create a new project (mine will be called 'mydemo') and a new application called 'addtracker'. We'll also assume that you've setup the settings.py file for the project to point to a database and configured the user ID and password in the database to match the settings. This is something you must do anyway for all django projects (again, there is plenty of documentation on this). In my example, I'll be using MySQL and I have a database ('schema' in MySQL) called demo (with a user: demo and password: demo).

So the database section of my settings.py file looks like this:

DATABASE_ENGINE = 'mysql'
DATABASE_NAME = 'demo'
DATABASE_USER = 'demo'
DATABASE_PASSWORD = 'demo
DATABASE_HOST = ''          #just using my machine as the database server
DATABASE_PORT = ''

ORM'd and Dangerous

The key to working with database-based applications in Django and utilizing the Admin interface is Django's ORM (Object Relational Mapping). Once the database tables, fields and relationships are correctly setup and configured using Django ORM, the Admin site and any other Django activity is pretty straight forward.

The ORM is basically all configured in your application in the models.py file within the application directory. This is where the models are setup.

From Scratch

If the database tables you are going to be using for your application don't exist yet, you can follow the standard instructions for building a new Django application. Part of those instructions have you writing out definitions for the data in the models.py for the application you are building.

For our example application, I setup something like this as a starting point in the models.py:

from django.db import models


class Address(models.Model):
    short_name = models.CharField(max_length=20)
    long_name = models.CharField(max_length=80)
    addr_line_1 = models.CharField(max_length=80,blank=True)
    addr_line_2 = models.CharField(max_length=80,blank=True)
    city = models.CharField(max_length=50)
    state = models.USStateField()
    zip = models.CharField(max_length=10,blank=True)


class PhoneType(models.Model):
    description = models.CharField(max_length=20)


class Phone(models.Model):
    description = models.CharField(max_length=80)
    number = models.PhoneNumberField()
    number_type = models.ForeignKey(PhoneType)
    address = models.ForeignKey(Address)


class Date(models.Model):
    description = models.CharField(max_length=80)
    start_date = models.DateField()
    annual_event = models.BooleanField()
    address = models.ForeignKey(Address)


class Group(models.Model):
    description = models.CharField(max_length=80)
    addresses = models.ManyToManyField(Address)


class Note(models.Model):
    note_text = models.TextField()
    address = models.ForeignKey(Address)

The only semi-custom thing I've done is indicated that some of the fields are not required by including the blank=True property in the definition of some of the fields. At this point, Admin is not enabled, we're just defining our ORM models for our database.

If you run syncdb command at this point, django will setup the basic framework support tables in the database, but you'll notice it doesn't even include our new models:

$ ./manage.py syncdb
Creating table auth_message
Creating table auth_group
Creating table auth_user
Creating table auth_permission
Creating table django_content_type
Creating table django_session
Creating table django_site

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'steven'):
E-mail address: demo@site.demo
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Message model
Installing index for auth.Permission model
Loading 'initial_data' fixtures...
No fixtures found.

Pay particular attention to the superuser setting because you'll need that later to login to the Admin interface. Set that carefully.

Edit the settings.py and add the addtracker application as an installed application so it should now look like this:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'addtracker',
)

Then rerun the syncdb application:

$ ./manage.py syncdb
Creating table addtracker_group
Creating table addtracker_note
Creating table addtracker_phone
Creating table addtracker_phonetype
Creating table addtracker_address
Creating table addtracker_date
Installing index for addtracker.Note model
Installing index for addtracker.Phone model
Installing index for addtracker.Date model
Loading 'initial_data' fixtures...
No fixtures found.

Now we actually have tables in our database that map with our models.

Importing (or Rather "Inspecting")

The other possible way of dealing with getting the database layout correctly setup in Django's ORM is the "inspectdb" command available as part of the manage.py application that is part of each django project.

For this example, I've created a new project called mydemo2 and an application called legacyapp to demonstrate setting up django based on existing database tables. The settings.py will be configured to point to a database schema called demo2 (again MySQL). The tables have a layout like this:

authors
name varchar(20)
nickname varchar(20)
description text
entries
id integer
title varchar(200)
postDate timestamp
modifyDate timestamp
contents text
author varchar(20)

After setting up the settings.py similar to before (although this time my data is in the demo2 schema), we can run the inspectdb utility to give us a leg up on our models.py:

$ ./manage.py inspectdb
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#     * Rearrange models' order
#     * Make sure each model has one field with primary_key=True
# Feel free to rename the models, but don't rename db_table values or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
# into your database.

from django.db import models

class Authors(models.Model):
    name = models.CharField(max_length=60, primary_key=True)
    nickname = models.CharField(max_length=60)
    description = models.TextField()
    class Meta:
        db_table = u'authors'

class Entries(models.Model):
    id = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=600)
    postdate = models.DateTimeField()
    modifydate = models.DateTimeField()
    contents = models.TextField()
    author = models.CharField(max_length=60)
    class Meta:
        db_table = u'entries'

This is pretty close to right. For some reason, in my particular version, you can see it was multiplying the max_length by 3, so that's got to be tweaked (issue 5725). The other thing here is that while it correctly identifies the primary fields, inspectdb is unable to detect that the id field is auto numbered. Also, because I'm using a MyISAM storage engine for MySQL, there is no true database support for foreign keys, so inspectdb can't detect those either.

So making a couple of changes to the above version of models.py we now have:

from django.db import models

class Authors(models.Model):
    name = models.CharField(max_length=20, primary_key=True)
    nickname = models.CharField(max_length=20)
    description = models.TextField()
    class Meta:
        db_table = u'authors'

class Entries(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=200)
    postdate = models.DateTimeField()
    modifydate = models.DateTimeField()
    contents = models.TextField()
    author = models.ForeignKey(Authors,db_column='author')
    class Meta:
        db_table = u'entries'

First, I fixed the id field in Entries to be an AutoField since it is auto-numbered by the database. We wouldn't want the admin interface demand that we enter a number there.

Second, I changed the author field to be a ForeignKey to Authors. If the author field in Entries actually linked to a non-primary key, Django even offers an option called to_field so that a specific field can be specified to link to. I also had to specify the the option db_column to specify that the name of the field in the database really was just 'author' because under the covers, Django is used naming those types of fields with an '_id' appended to the name of the field.

Depending on how the postdate and modifydate fields are used, you may have to decide to deal with those. If the legacy application normally fills those, and now your application is being requested to fill those, you'll have to accomodate that. So let's say that the postdate field is filled when the record is originally created by the user, and the modifydate field is always updated anytime the record is added or edited. And we're obviously stating that the database isn't taking care of that for us in this case, that the application is expected to do that.

Without wandering out of the models.py and without changing the table, we can handle that also:

from django.db import models
import datetime

class Authors(models.Model):
    name = models.CharField(max_length=20, primary_key=True)
    nickname = models.CharField(max_length=20)
    description = models.TextField()
    class Meta:
        db_table = u'authors'

    def __str__(self):
        return self.name

class Entries(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=200)
    postdate = models.DateTimeField(editable=False)
    modifydate = models.DateTimeField(editable=False)
    contents = models.TextField()
    author = models.ForeignKey(Authors,db_column='author')
    class Meta:
        db_table = u'entries'

    def save(self):
        if not self.id:
            self.postdate = datetime.date.today()
        self.modifydate = datetime.datetime.today()
        super(Entries, self).save()

In this version, we're sub-classing the save method of the model. Prior to doing the normal saving, we set the postdate to today's date and the modifydate to the current date and time (more of a true timestamp). But also note that in the field definitions, I added the editable=False option so that those fields wouldn't be manually entered on the Admin form. [3]

[3]thanks to James Bennett's tip

Caution

Depending on the rights granted to the user account that your Django project will be using, you could very easily end up seriously messing up some data. If you're working with existing data and tables, you'll want avoid commands like sqlreset that can overwrite the definitions of tables and clear tables.

Limitations of inspectdb and the ORM

Relationship Detection

inspectdb can detect foreign keys in databases that support those like MySQL with the InnoDB engine or Oracle or PostgreSQL. For MySQL with the MyISAM storage engine or SQLite, no foreign keys are detected. In fact with SQLite, inspectdb can't detect a primary key, so that has to be set manually as well. Even with our example, though, you can see that most standard foreign key references can be dealt with given the number of optional parameters that Django provides for models in the ORM.

Primary Key Must be a Single Field

The primary key for each table must be a single field (and of course each table must have a primary key). Currently, there is no way in the Django ORM to specify or support a multi-field primary key. One obvious workaround to this, if you have the ability to do so is use your own special version of a table that combines the values of the fields into one field so that you can later push those records back to the original table.

Single Database Limitation

Django currently expects to only deal with one database or one database schema at a time. There has been talk and work on supporting multiple schemas in MySQL, for instance, and some have managed to do it with small tweaks to the Django code, but it's currently not still got true support.

With Oracle, there is support for tablespaces, specifically for supporting indexes that are stored in separate tablespaces.

Work Arounds

I don't have a magic pill for each of these issues or probably others that I haven't covered. Instances where there are incompatibilities, it really is time to kick in some creative problem solving.

My suggestion is asking yourself several questions:

  • What tables really need to be accessed by the Django Admin Interface?
  • How often is the data changed or needs to be changed?
  • How current does the data from the rest of the system need to be?

Chances are if the current system already involves regularly scheduled imports or transfers of data to/from other databases or csv's or spreadsheets (yuck), then you're probably in a good position to still utilize some form of Django application to enter/maintain data. Even if there is an import/export process still involved, having a true web app on a real database is bound to be vastly superior to folks passing around a spreadsheet document.

By relying heavily on batching and separation, you can allow your Django application to live a more separate environment which certainly makes the Risk Police in your organization happier while still getting you the functionality you need.

Enabling Admin

For the remainder, I'll focus on our first project and application but obviously, these techniques apply, regardless of how you got the ORM setup initially.

In settings.py you should add django.contrib.admin as an installed application:

INSTALLED_APPS = (
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.sites',
  'django.contrib.admin',
  'addtracker',
)

Then one more syncdb and we'll have all the database tables we need:

$ ./manage.py syncdb
Creating table django_admin_log
Installing index for admin.LogEntry model
Loading 'initial_data' fixtures...
No fixtures found.

To be able to actually navigate in our browser to the admin site, we've got to enable a pattern to get to the admin site:

from django.conf.urls.defaults import *
from django.contrib import admin

urlpatterns = patterns('',
    ('^admin/(.*)', admin.site.root),
    )

To enable Admin for a model (the correct terminology here is apparently register), we need to add the following lines:

from django.contrib import admin

admin.site.register(Address)
admin.site.register(Date)
admin.site.register(PhoneType)
admin.site.register(Phone)
admin.site.register(Group)
admin.site.register(Note)

This enables each of models for use in the Admin interface. So if we run the server and login we get:

django_admin_files/django_enabled_admin.png

And if we add an address record it's really not a bad looking form:

django_admin_files/django_add_form_entry.png

The field names are a bit turse and we can't easily get to any of the child records directly from here, but it is usable.

django_admin_files/django_browse_list_bad.png

Yuck! Our new address record is listed as Address object. There are two ways of dealing with this problem and really both of them ought be done. The first way to deal with this, is to define a string (think __str__ ) representation of the object. This is extremely easy and really ought to be done for all objects since in parent child scenarios Django used the string representation for selecting a record. But to really make this browse look nice, we're going to start using true Admin options. This is again, one of the ways the NewForms-Admin differs from the old style. Many of the options have the same names, so looking at the Django Book (reference) or other code examples, won't totally be a waste of time because it shows you what can be done and a hint as to what is used to make it happen.

To define Admin options for a particular model, you need to define a new class (not a nested class) in the models.py (or whereever you decide you're going to define Admin related options since it doesn't have to be in models.py):

class Address(models.Model):
    short_name = models.CharField(max_length=20,
                                  verbose_name="last name",
                                  help_text="name used for sorting purposes")
    long_name = models.CharField(max_length=80,
                                 help_text="the text that would appear on an envelope")
    addr_line_1 = models.CharField(max_length=80,blank=True,verbose_name="address line 1")
    addr_line_2 = models.CharField(max_length=80,blank=True,verbose_name="address line 2")
    city = models.CharField(max_length=50)
    state = models.USStateField()
    zip = models.CharField(max_length=10,blank=True)

    def __str__(self):
        return self.short_name

    class Meta:
        verbose_name_plural = 'addresses'


    class AddressOptions(admin.ModelAdmin):
        search_fields = ['short_name','long_name','city']
        list_display = ('short_name','city','state')
        ordering = ('short_name',)
        list_filter = ('state',)
        fieldsets = (('Name',
                   {'fields':('short_name',
                              'long_name')}),
                  ('Address',
                   {'fields':('addr_line_1',
                              'addr_line_2',
                              'city',
                              'state',
                              'zip')}),
            )

    admin.site.register(Address,AddressOptions)

In the Address model, I've specified a verbose_name for some of the fields to have it make more sense to the user. I've also added help_text attributes to a couple of the fields to further explain their use. This information gets displayed in the Admin interface.

Then, a method definition for __str__ was added. For now, all that's returned is the short_name field but that's obviously better than 'Address object' for everything.

A child class called Meta was added and verbose_name_plural was specified as 'Addresses' so that it would stop showing up as 'Addresss'.

I've added a new class called AddressOptions (which inherits from admin.ModelAdmin) and I've specified several options. Also, the admin.site.register call has the added AddressOptions added as an additional parameter.

  • search_fields : specifies which fields can be searched on from the list view
  • list_display : specifies which fields to use in the browse list
  • fieldsets : specifies which fields should be displayed in which order and how they should be grouped in the Admin form. Optionally, fields can be used instead with just a tuple of field names to specify which fields and which order
  • ordering : specifies the default display order. The cool feature is that many of the other list_display fields can be sorted on
  • list_filter : specifies which fields should be available for use as a filter

Looking at the web-pages now, it's obvious the difference the settings make:

django_admin_files/django_refined_browse_list.png django_admin_files/django_better_form.png

We now have a pretty decent looking browse and form for the Addresses. But for so many of the related tables, it seems really silly that we should have to go to a separate browse and form to add those child records.

Inline Child Records

The NewForms-Admin makes inline child records very logical and easy to implement. We'll look at Phone child. We want to be able to add and edit related phone records when we're on the address record form. For this particular purpose, the admin.TabularInline class is perfect. We just define a class that inherits from admin.TabularInline and then specify the options we want:

class PhoneInline(admin.TabularInline):
    model = Phone
    extra = 3

class AddressOptions(admin.ModelAdmin):
    search_fields = ['short_name','long_name','city']
    list_display = ('short_name','city','state')
    ordering = ('short_name',)
    list_filter = ('state',)
    fieldsets = (('Name',
               {'fields':('short_name',
                          'long_name')}),
              ('Address',
               {'fields':('addr_line_1',
                          'addr_line_2',
                          'city',
                          'state',
                          'zip')}),
        )
    inlines = [PhoneInline]

Then, our AddressOptions class we specify a new option called inlines which is really just a list of all inline child record classes. So far, we're just working on Phone records so we add the new PhoneInline object in our list of inlines.

django_admin_files/django_phone_inline.png

Now we've got the ability to add/edit/delete phone records as needed from within the parent record. By the way, the TabularInline versus the StackedInline is really about the field representation. The TabularInline has a grid sort of field (as you can see in the screen shot) where fields are spread across the screen as if they were in a table of a data (hence the term 'tabular' I guess). 'Stacked' is like having the admin screen for that child record right there embedded as the field and value pairs go down rather than across.

So in our example application, I'll just add the notes and dates children on there. The final bit of customization needed to have a useable application is a tweak to the Group form. The default group form has a small box where you Ctrl-Click on the members you want or don't want in the group. It works but likely won't pass the spouse-friendly user interface test. So, we'll tweak that by specifying in its Admin options class that we want a horizontal selection inteferface so that it looks like this:

django_admin_files/django_groups.png

After some final tweaks, this is the final version of the models.py:

from django.db import models
from django.contrib import admin


class Address(models.Model):
    short_name = models.CharField(max_length=20,
                                  verbose_name="last name",
                                  help_text="name used for sorting purposes")
    long_name = models.CharField(max_length=80,
                                 help_text="the text that would appear on an envelope")
    addr_line_1 = models.CharField(max_length=80,blank=True,verbose_name="address line 1")
    addr_line_2 = models.CharField(max_length=80,blank=True,verbose_name="address line 2")
    city = models.CharField(max_length=50)
    state = models.USStateField()
    zip = models.CharField(max_length=10,blank=True)

    def __str__(self):
        return self.short_name + ' (' + self.city + ', ' + self.state + ')'

    class Meta:
        verbose_name_plural = 'addresses'


class PhoneType(models.Model):
    description = models.CharField(max_length=20)

    def __str__(self):
        return self.description


class Phone(models.Model):
    description = models.CharField(max_length=80)
    number = models.PhoneNumberField()
    number_type = models.ForeignKey(PhoneType)
    address = models.ForeignKey(Address)

    def __str__(self):
        return str(self.number)


class Date(models.Model):
    description = models.CharField(max_length=80)
    start_date = models.DateField()
    annual_event = models.BooleanField()
    address = models.ForeignKey(Address)


class Group(models.Model):
    description = models.CharField(max_length=80)
    addresses = models.ManyToManyField(Address)

    def __str__(self):
        return self.description


class Note(models.Model):
    note_text = models.TextField()
    address = models.ForeignKey(Address)


class DateInline(admin.TabularInline):
    model = Date
    fields = ('start_date','description','annual_event')
    extra = 2

class NoteInline(admin.StackedInline):
    model = Note
    extra = 1

class PhoneInline(admin.TabularInline):
    model = Phone
    extra = 3

class AddressOptions(admin.ModelAdmin):
    search_fields = ['short_name','long_name','city']
    list_display = ('short_name','city','state')
    ordering = ('short_name',)
    list_filter = ('state',)
    fieldsets = (('Name',
               {'fields':('short_name',
                          'long_name')}),
              ('Address',
               {'fields':('addr_line_1',
                          'addr_line_2',
                          'city',
                          'state',
                          'zip')}),
        )
    inlines = [PhoneInline,DateInline,NoteInline]

class GroupOptions(admin.ModelAdmin):
    filter_horizontal = ('addresses',)


admin.site.register(Address,AddressOptions)
admin.site.register(PhoneType)
admin.site.register(Group,GroupOptions)

It isn't perfect but in what ended up being an hour or less of coding and playing with settings, we have a useable and decent looking application.

If you want to customize the "Django administration" text throughout the system to match you can find that information on-line, of course.

There are also some guides to changing and customizing the CSS used to style the Admin interface so that you can really make it feel like your own application.

And finally, you can take the existing Admin templates and use them as a starting point for additional views and forms as reports or special input screens.

Summary

Following the standard "getting started" guides for Django, possibly using the services of the inspectdb command, and modifying just 3 files (settings.py, urls.py, and models.py), you can have a very feature rich web-based admin interface for either an existing database or a new set of data that you need to modify. Whether you use this new found knowledge to impress your spouse or your bewildered co-workers, or just tuck away for a rainy day, the power of the Django Admin interface can now be yours.