Version 30 (modified by Matthew Flanagan <mattimustang@…>, 17 years ago) ( diff )

added missing comma from tuple

The newforms-admin branch

This branch aims to integrate Django's admin site with the newforms library. Along the way, we're taking the opportunity to add extra customization hooks to the admin site.

How to get the branch

svn co http://code.djangoproject.com/svn/django/branches/newforms-admin/

See our branch policy for full information on how to use a branch.

Goals

The main goals of this branch are:

  • Change the admin site to use newforms instead of automatic manipulators.
  • Enable developers to declare custom widgets for particular fields in a model.
  • Enable developers to declare custom admin-only validation for a model (i.e., validation logic that is applied only in the admin site, nowhere else).
  • Give developers extra hooks into the admin-site functionality. (Yes, this is a broad goal. More examples are forthcoming.)
  • Remove the admin declarations (class Admin) from within models, thereby helping decouple the admin system from models.
  • Integrate some ideas from #2248: Remove core=True, specify inline models in the model itself rather than in the related model, specify which fields should be displayed inline.

The django.contrib.formtools.preview.FormPreview application (not yet documented, but fully implemented) elegantly allows for fine-grained customization of the application by subclassing. We're taking a similar approach for the new version of the Django admin site: Write a class that subclasses a base class called ModelAdmin and specify whichever customizations you need to make -- from the current basic admin options such as list_display and search_fields to full-on Python hooks, such as defining arbitrary Python code to run before or after a model object is saved via the admin.

Status

A lot has changed in this branch. Let's start the syntax for URLconfs:

# OLD:
from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^admin/', include('django.contrib.admin.urls')),
)

# NEW:
from django.conf.urls.defaults import *
from django.contrib import admin

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

Note that, in this above URLconf example, we're dealing with the object django.contrib.admin.site. This is an instance of django.contrib.admin.AdminSite, which is a class that lets you specify admin-site functionality. The object django.contrib.admin.site is a default AdminSite instance that is created for you automatically, but you can also create other instances as you see fit.

Previously, there was one "global" version of the admin site, which used all models that contained a class Admin. This new scheme allows for much more fine-grained control over your admin sites, allowing you to have multiple admin sites in the same Django instance.

In this example, we create two AdminSite instances, registering different models with both. Assume Book, Author, Musician and Instrument are Django model classes (not instances).

from django.contrib import admin

site1 = admin.AdminSite()
site1.register(Book)
site1.register(Author)

site2 = admin.AdminSite()
site2.register(Musician)
site2.register(Instrument)

# URLconf

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    ('^book_admin/(.*)', site1.root),
    ('^music_admin/(.*)', site2.root),
)

With this example, if you go to /book_admin/, you'll get a Django admin site for the Book and Author models. If you go to /music_admin/, you'll get a Django admin site for the Musician and Instrument models.

Admin options -- the inner class Admin -- have changed, too. Models no longer use an inner class to declare their admin site options. In fact, all admin functionality has been decoupled from the model syntax! How, then, do we declare admin options? Like this:

# a sample models.py file
from django.db import models

class Author(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)

    def __str__(self):
        return '%s %s' % (self.first_name, self.last_name)

class Book(models.Model):
    title = models.CharField(maxlength=100)
    author = models.ForeignKey(Author)

from django.contrib import admin

class BookOptions(admin.ModelAdmin):
    list_display = ('title', 'author')
    ordering = ('title',)

# Make sure the following are executed exactly once (i.e., watch your imports), or you'll see AlreadyRegistered exceptions.
admin.site.register(Author)
admin.site.register(Book, BookOptions)

In this example, we register both Author and Book with the AdminSite instance django.contrib.admin.site. Author doesn't need any custom admin options, so we just call admin.site.register(Author). Book, on the other hand, has some custom admin options, so we define a BookOptions class and pass that class as a second argument to admin.site.register().

In this example, the admin options still live in the models.py file. But there's nothing that requires them to do so. The only requirement is that the register() calls are executed at some point, and putting them in the models.py is an easy way to ensure that. We'll likely come up with a nice convention for this.
A proposal convention: Specifying all admin options in a file called admin.py, and import it in the __init.py file of your application module to do the registering during the initialization.

You'll notice the BookOptions class looks a lot like the old-style class Admin. Almost all of the old class Admin options work exactly the same, with one or two exceptions. (For the options that have changed, we've made them much more powerful.) In addition to the classic options such as list_display and ordering, the ModelAdmin class introduces a wealth of extra hooks you can use to customize the admin site for that particular model. For example:

class BookOptions(admin.ModelAdmin):
    list_display = ('title', 'author')
    ordering = ('title',)

    def has_change_permission(self, request, obj):
        """
        John can only edit books by Roald Dahl.
        """
        if request.user.username == 'john':
            return obj.author.last_name == 'Dahl'
        return super(BookOptions, self).has_change_permission(request, obj)

Look at the class ModelAdmin in the file [source:/django/branches/newforms-admin/django/contrib/admin/options.py django/contrib/admin/options.py] to see all of the methods you can override. This is exciting stuff.

To-do list

(Updated April 6, 2007)

  • Implement "edit inline" functionality.
  • site.register does not handle kwargs for Model Admin.

Backwards-incompatible changes

This is a (currently incomplete) list of backwards-incompatible changes made in this branch.

Changed Admin.manager option to more flexible hook

As of [4342], the manager option to class Admin no longer exists. This option was undocumented, but we're mentioning the change here in case you used it. In favor of this option, class Admin may now define one of these methods:

  • queryset()
  • queryset_add()
  • queryset_change()

These give you much more flexibility.

(We initially called the new method change_list_queryset, but this was changed in [4584] to be more flexible.)

Changed prepopulate_from to be defined in the Admin class, not database field classes

As of [4446], the prepopulate_from option to database fields no longer exists. It's been discontinued in favor of the new prepopulated_fields option on class Admin. The new prepopulated_fields option, if given, should be a dictionary mapping field names to lists/tuples of field names. This change was made in an effort to remove admin-specific options from the model itself. Here's an example comparing old syntax and new syntax:

# OLD:
class MyModel(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name'))

    class Admin:
        pass

# NEW:
class MyModel(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    slug = models.CharField(maxlength=60)

from django.contrib import admin

class MyModelOptions(admin.ModelAdmin):
    prepopulated_fields = {'slug': ('first_name', 'last_name')}

admin.site.register(MyModel, MyModelOptions)

Moved admin doc views into django.contrib.admindocs

As of [4585], the documentation views for the Django admin site were moved into a new package, django.contrib.admindocs.

The admin docs, which aren't documented very well, were located at docs/ in the admin site. They're also linked-to by the "Documentation" link in the upper right of default admin templates.

Because we've moved the doc views, you now have to activate admin docs explicitly. Do this by adding the following line to your URLconf:

(r'^admin/doc/', include('django.contrib.admindocs.urls')),

Renamed 'fields' to 'fieldsets'

'Fields' is used to order and group fields in the change form layout.
It is still available in the new admin, but it accepts only a list of fields.
In case one uses fieldsets to organize the fields, one needs to use 'fieldsets' instead.

An example:

# OLD:
class MyModelA(models.Model):
   class Admin:
     fields = ('field1','field2','field3','field4')

class MyModelB(models.Model):
   class Admin:
     fields = (
       ('group1', {'fields': ('field1','field2'),),
       ('group2', {'fields': ('field3','field4'),),
       )

# NEW:
class MyModelA_Options(admin.ModelAdmin):
    fields = ('field1','field2','field3','field4')  # Renaming is optional 

class MyModelB_Options(admin.ModelAdmin):
     fieldsets = (
       ('group1', {'fields': ('field1','field2'),),
       ('group2', {'fields': ('field3','field4'),),
       )

Inline editing

The syntax is now different and much, much better. Here is an example: An example:

from django.contrib import admin 
class Child_Inline(admin.TabularInline): 
    model = Child 
    extra = 3 

class Parent_admin(admin.ModelAdmin): 
    model = Parent
    inlines = [Child_Inline] 

Moved raw_id_admin from the model definition

The syntax now seperates from the definition of your models.

An example:

# OLD:
class MyModel(models.Model): 
    field1 = models.ForeignKey(AnotherModel, raw_id_admin=True)
    class Admin:
        pass

# NEW:
class MyModel_Options(admin.ModelAdmin): 
    model = MyModel
    raw_id_fields = ('field1',) 
Note: See TracWiki for help on using the wiki.
Back to Top