Sunday, July 20, 2008

newforms-admin Migration and Screencast

with host Brian Rosner

Bookmark and Share

The newforms-admin branch has landed! It has been in development for quite some time and is now in trunk. I am extremely excited about it. However, I am a special case since I have been using the branch for quite some and even a committer on the branch. Migration for me has been easy, but I wanted to share that knowledge with everyone else. I have been seeing a lot of people getting stuck on where to begin. The admin documentation is quite pointless for those who have exisiting Django code. That is done on purpose. The newforms-admin branch wiki page has the information you are looking for.

I am going to go in-depth about how to perform this migration. I have done so in the screencast and will also provide a textual version here as well.

Download screencast (61.9 MB, 12:23, 800x600, Apple Animation)

Migrating Your Code to newforms-admin

Let us begin by starting with old-style admin code. Here are two models where we have a model that is edited inline:

class Post(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField(prepopulate_from=("title",))
    pub_date = models.DateField()

    class Admin:
        list_display = ("title", "pub_date")

class Link(models.Model):
    post = models.ForeignKey(Post, edit_inline=models.TABULAR)
    title = models.CharField(max_length=100, core=True)
    url = models.URLField(core=True)

newforms-admin has completely decoupled the admin syntax and options from the models. This is to provide a cleaner and more controllable expierence. It may have been more convenient before, but ultimately it isn't a ton more verbose, but gives you plenty of areas for customization.

The first thing you will need to do is create an admin.py file in your app. This is where your new ModelAdmin subclasses will live. Here is what the above Post model will turn into:

from django.contrib import admin

class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "pub_date")
    prepopulated_fields = {"slug": ("title",)}

The prepopulate_from keyword argument has been translated to prepopulated_fields. Most of the old inner Admin class options have stayed the same, but be sure to make sure.

The last bit that needs to be moved out from the models is the edit_inline and core keyword arguments. You can safely just remove any and all core arguments. They are no longer used. newforms-admin now provides a nice delete checkbox for exisiting instances in inlines. Your admin.py will now look like after changing the edit_inline bit:

from django.contrib import admin

from myproject.myapp.models import Link

class LinkInline(admin.TabularInline):
    model = Link

class PostAdmin(admin.ModelAdmin):
    list_display = ("title", "pub_date")
    prepopulated_fields = {"slug": ("title",)}
    inlines = [
        LinkInline,
    ]

Hooking everything up

Now that you have done a migration of the code, we now need to tie everything together. Another change in newforms-admin is how the URL is routed. Before it worked with an include:

from django.conf.urls.defaults import *

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

That no longer works since django.contrib.admin.urls no longer exists. The new way introduces the notion of individual sites. Those are defined with the AdminSite class. Here is how you would quickly migrate this part of the code:

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

admin.autodiscover()

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

Ok, notice the addition of admin.autodiscover(). This is a simple function that iterates over INSTALLED_APPS and imports the admin.py module of each. The reason for this is because you should always register your models to their ModelAdmin classes for the default AdminSite instance. At the bottom of your admin.py file add:

admin.site.register(Post, PostAdmin)

Based on the example code above, you will also need to import the Post model into scope.

Custom AdminSite instances

There are cases where you may want more than one admin interface that are registered to different models. Or perhaps you would like to override behavior or options of any app that provides ModelAdmin classes. This is very simple. Simply create a project-level admin.py module. Inside it create your own AdminSite class:

from django.contrib import admin

class AdminSite(admin.AdminSite):
    pass

site = AdminSite()

Now, use this instead of the default instance in your urls.py:

from django.conf.urls.defaults import *

from myproject.admin import site

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

Notice how I have removed admin.autodiscover(). This is no longer needed since we don't really need to be automatically import the app's admin.py modules. It is done in our project-level admin.py, but really doesn't matter since we are not using the default instance. We could if we wanted, but in this case we don't.

Now do all registration for our new AdminSite instance in the project-level admin.py:

from django.contrib import admin

from myproject.myapp.admin import PostAdmin
from myproject.myapp.models import Post

class AdminSite(admin.AdminSite):
    pass

site = AdminSite()

site.register(Post, PostAdmin)

We could even customize the contrib app's ModelAdmin subclasses and provide our own behavior or option overrides:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

from myproject.myapp.admin import PostAdmin
from myproject.myapp.models import Post

class AdminSite(admin.AdminSite):
    pass

site = AdminSite()

class MyUserAdmin(UserAdmin):
    list_display = ("username",)

site.register(User, MyUserAdmin)
site.register(Post, PostAdmin)

That is all I have for now. I will be doing more posts in the future about the additional hooks you can find with AdminSite and ModelAdmin classes. Let me know what you think!

Comments - 1 person has already said something. Join the discussion.

  • Cruze said

    All information about mens health, womens health, sexual health and more product online.