commit 8ecda951e8ded8fadf9e54dbeb1b7c444d88e3d2
Author: Honza Král <Honza.Kral@gmail.com>
Date:   Sun Nov 11 21:32:22 2007 +0100

    Support for edit_inline for generic relations
    
    http://code.djangoproject.com/ticket/4667

diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index b738a26..ba45d4d 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -260,3 +260,104 @@ class GenericRel(ManyToManyRel):
         self.multiple = True
         assert not (self.raw_id_admin and self.filter_interface), \
             "Generic relations may not use both raw_id_admin and filter_interface"
+
+from django.newforms.models import BaseModelFormSet, formset_for_model, save_instance
+from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
+
+def get_foreign_key(model, ct_field_name, id_field_name):
+    opts = model._meta
+    # avoid circular import
+    from django.db.models import ForeignKey
+    from django.contrib.contenttypes.models import ContentType
+    opts = model._meta
+
+    # if there is no field called `ct_field_name` let the exception propagate
+    ct = opts.get_field(ct_field_name)
+    if not isinstance(ct, ForeignKey) or ct.rel.to != ContentType:
+        raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % (ct_field_name))
+
+    # if there is no field called `id_field_name` let the exception propagate
+    obj_id = opts.get_field(id_field_name)
+
+    return ct, obj_id
+
+def inline_formset(model, ct_field_name, id_field_name, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield(), formset=None):
+    """
+    Returns an ``InlineFormset`` for the given kwargs.
+
+    You must provide ``fk_name`` if ``model`` has more than one ``ForeignKey``
+    to ``parent_model``.
+    """
+    ct, obj_id = get_foreign_key(model, ct_field_name, id_field_name)
+    # let the formset handle object deletion by default
+    FormSet = formset_for_model(model, formset=formset or GenericInlineFormset, fields=fields,
+                                formfield_callback=formfield_callback,
+                                extra=extra, orderable=orderable,
+                                deletable=deletable)
+    # HACK: remove the ForeignKey to the parent from every form
+    # This should be done a line above before we pass 'fields' to formset_for_model
+    # an 'omit' argument would be very handy here
+    try:
+        del FormSet.form_class.base_fields[ct.name]
+        del FormSet.form_class.base_fields[obj_id.name]
+    except KeyError:
+        pass
+    FormSet.ct = ct
+    FormSet.obj_id = obj_id
+    return FormSet
+
+class GenericInlineFormset(BaseModelFormSet):
+    """A formset for child objects related to a parent."""
+    def __init__(self, instance=None, data=None, files=None):
+        self.instance = instance
+        # is there a better way to get the object descriptor?
+        self.rel_name = self.ct.name + '_' + self.obj_id.name
+        super(GenericInlineFormset, self).__init__(data, files, prefix=self.rel_name)
+
+    def get_queryset(self):
+        # This import is done here to avoid circular import importing this module
+        from django.contrib.contenttypes.models import ContentType
+        if self.instance is None:
+            return []
+        return self.model._default_manager.filter( **{ 
+                self.ct.name : ContentType.objects.get_for_model(self.instance),
+                self.obj_id.name : self.instance._get_pk_val() 
+            })
+
+    def save_new(self, form, commit=True):
+        # This import is done here to avoid circular import importing this module
+        from django.contrib.contenttypes.models import ContentType
+        kwargs = {
+            self.ct.get_attname(): ContentType.objects.get_for_model(self.instance).id,
+            self.obj_id.get_attname(): self.instance._get_pk_val(),
+        }
+        new_obj = self.model(**kwargs)
+        return save_instance(form, new_obj, commit=commit)
+
+class GenericInlineModelAdmin(InlineModelAdmin):
+    ct_field_name = None
+    id_field_name = None
+    def formset_add(self, request):
+        """Returns an GenericInlineFormSet class for use in admin add views."""
+        if self.declared_fieldsets:
+            fields = flatten_fieldsets(self.declared_fieldsets)
+        else:
+            fields = None
+        return inline_formset(self.model,  self.ct_field_name, self.id_field_name, fields=fields,
+                    formfield_callback=self.formfield_for_dbfield, extra=self.extra, formset=self.formset)
+
+    def formset_change(self, request, obj):
+        """Returns an GenericInlineFormSet class for use in admin change views."""
+        if self.declared_fieldsets:
+            fields = flatten_fieldsets(self.declared_fieldsets)
+        else:
+            fields = None
+        return inline_formset(self.model,  self.ct_field_name, self.id_field_name, fields=fields,
+                    formfield_callback=self.formfield_for_dbfield, extra=self.extra, formset=self.formset)
+
+class GenericStackedInline(GenericInlineModelAdmin):
+    template = 'admin/edit_inline/stacked.html'
+
+class GenericTabularInline(GenericInlineModelAdmin):
+    template = 'admin/edit_inline/tabular.html'
+
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 4d0d295..636ae72 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -5,7 +5,6 @@ from django.db.models import signals, loading
 from django.dispatch import dispatcher
 from django.utils.datastructures import SortedDict
 from django.utils.encoding import smart_unicode
-from django.contrib.contenttypes import generic
 import datetime
 import operator
 import re
@@ -1119,6 +1118,7 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
 
 def delete_objects(seen_objs):
     "Iterate through a list of seen classes, and remove any instances that are referred to"
+    from django.contrib.contenttypes import generic
     qn = connection.ops.quote_name
     ordered_classes = seen_objs.keys()
     ordered_classes.reverse()
