Edit Inline Support for Generic Relations
Introduction
I've recently (past week or so) been digging into the newforms-admin branch of django. I am really looking forward to this code line getting merged into trunk as there are tons of great work in this branch. I especially like the separation of the admin code from the models.
Well, I'll get to my point.
The Problem
I needed to be able to edit child records in the admin that were children through the use of GenericRelations. I was told about a solution on Google Code called django-genericadmin, but it seemed dead and I could not get it to work.
The thing that seemed the closest to working was a patch on ticket 4667 by Honza Kral and a variation on this patch in the Django Snippet 765. Neither of these worked for r7771 of branches/newforms-admin.
The Solution
I put the 765 snippet in a file called generic.py in the root of the django project I was working on and fiddled with it until I got it working. I ended up needing to change an import, fix some variable names, and change the argument list order in one of the init methods. I also added the can_order and can_delete options to the GenericInlineModelAdmin class.
After I got it working isolated in generic.py, I then added the code to the django/contrib/admin/options.py file and created a new patch (after testing locally of course). This patch has since been added to ticket 4667. I also posted the full version that I had running in generic.py in snippet 832 in case someone wants to use it without patching their newforms-admin branch.
Example of How to Use
In order to get a sense for how to make use of this functionality, imagine you have a songs app that you use to model and record details about songs, you have built this generically so you can reuse it wisely in a number of different projects. One project in particular has a need to to reference song data.
You are building a home media inventory system and decide you want to catalog both your albums as well as your DVD collection, however, you want the ability to record what songs exist on both your albums as well as your DVDs as you are a fanatic about soundtracks.
So you have the following models (abbreviated for clarity):
# Media Collection App
class Album(models.Model):
...
class DVD(models.Model):
...
# Song App
class Song(models.Model):
...
Since you want to enable your Song model to relate to two other models generically, you decide to use Generic Relations like so:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class Song(models.Model):
...
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
and then on the models that you want to have 0 to Many child records that are songs:
from myproject.song.models import Song
from django.contrib.contenttypes import generic
class Album(models.Model):
...
songs = generic.GenericRelation(Song)
class DVD(models.Model):
...
songs = generic.GenericRelation(Song)
Now that's all you need to do if you didn't care about editing the data in the admin, however, since that is not the point of the article, here is what you need to do in your admin.py in the app that contains Album and DVD:
...
from myproject.song.models import Song
from myproject.generic import GenericTabularInline
### OR ###
### from django.contrib.admin.options import GenericTabularInline
class SongInline(GenericTabularInline):
model = Song
extra = 2
ct_field_name = 'content_type'
id_field_name = 'object_id'
class AlbumOptions(admin.ModelAdmin):
model = Album
inlines = (SongInline,)
class DVDOptions(admin.ModelAdmin):
model = DVD
inlines = (SongInline,)
admin.site.register(Album, AlbumOptions)
admin.site.register(DVD, DVDOptions)
Now you should be able to add/remove songs when editing an Album or DVD in your admin.
Update: Fixed typo in code sample above in setting the GenericForeignKey() -- thanks FunkyBob!
Commentary
Thank you very much for useful article! Waiting for next article on newforms-admin
Thanks for the work. I had it on my ToDo list to rewrite the thing to be stand-alone but never got around to do it. The first patch couldn't work outside of Django, but with Brian's and Joseph's work it became very simple to do so.
btw. you could have written me a mail, I would have sent you a working patch from out git repository ;)
Honza: Why didn't you just include the patch in the ticket? :) What git repo you talking about? The one on github?
I haven't found any understandable examples of "Generic Relations" where the use is opposite from your example. I mean for example: a page which have sections of different types, like picture-, text or "html"-section.
Is this right direction or is I headed into a one end street?
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic
class BaseInfo(models.Model): name = models.CharField(maxlength=50) headline = models.CharField(maxlength=100) linktext = models.CharField(maxlength=100) updated = models.DateTimeField(autonow=True) created = models.DateTimeField(autonowadd=True)
class Section(models.Model): tag = models.SlugField()
class TextItem(BaseInfo): text = models.CharField(max_length=100)
class PictureItem(BaseInfo): picture = models.ImageField(uploadto="images") alttext = models.CharField(max_length=50) textItems = models.ForeignKey(TextItem) item = generic.GenericRelation(Section)
class HtmlItem(BaseInfo): html = models.CharField(maxlength=250) html.allowtags = True
class Page(BaseInfo): metatext = models.CharField(max_length=100) sections = models.ManyToManyField(Section) published = models.DateTimeField()
I am using the generic inline in a project and would like to supply the inlines with some initial data? Is this possible?