commit 015c9b884376c82a1b53ccd11728449d1b119d26
Author: Honza Král <Honza.Kral@gmail.com>
Date:   Tue Oct 2 18:18:24 2007 +0200

    Added generic edit inline patch
    
    http://code.djangoproject.com/ticket/4667

diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index bab9580..d650a1e 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -1,7 +1,6 @@
 from django import oldforms, template
 from django import newforms as forms
 from django.newforms.formsets import all_valid
-from django.contrib.contenttypes.models import ContentType
 from django.contrib.admin import widgets
 from django.contrib.admin.util import get_deleted_objects
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
@@ -451,6 +450,7 @@ class ModelAdmin(BaseModelAdmin):
             return HttpResponseRedirect("../")
 
     def render_change_form(self, model, context, add=False, change=False, form_url=''):
+        from django.contrib.contenttypes.models import ContentType
         opts = model._meta
         app_label = opts.app_label
         ordered_objects = opts.get_ordered_objects()
@@ -721,6 +721,7 @@ class InlineModelAdmin(BaseModelAdmin):
     template = None
     verbose_name = None
     verbose_name_plural = None
+    formset = None
 
     def __init__(self, parent_model, admin_site):
         self.admin_site = admin_site
@@ -738,7 +739,8 @@ class InlineModelAdmin(BaseModelAdmin):
             fields = flatten_fieldsets(self.declared_fieldsets)
         else:
             fields = None
-        return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra)
+        return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields,
+                    formfield_callback=self.formfield_for_dbfield, extra=self.extra, formset=self.formset)
 
     def formset_change(self, request, obj):
         """Returns an InlineFormSet class for use in admin change views."""
@@ -746,7 +748,8 @@ class InlineModelAdmin(BaseModelAdmin):
             fields = flatten_fieldsets(self.declared_fieldsets)
         else:
             fields = None
-        return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields, formfield_callback=self.formfield_for_dbfield, extra=self.extra)
+        return forms.inline_formset(self.parent_model, self.model, fk_name=self.fk_name, fields=fields,
+                    formfield_callback=self.formfield_for_dbfield, extra=self.extra, formset=self.formset)
 
     def fieldsets_add(self, request):
         if self.declared_fieldsets:
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index b738a26..bdf9985 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, instances=self.get_inline_objects(), prefix=self.rel_name)
+
+    def get_inline_objects(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()
diff --git a/django/newforms/models.py b/django/newforms/models.py
index 5fdbc4d..a27a9e8 100644
--- a/django/newforms/models.py
+++ b/django/newforms/models.py
@@ -338,7 +338,7 @@ def get_foreign_key(parent_model, model, fk_name=None):
             raise Exception("%s has more than 1 ForeignKey to %s" % (model, parent_model))
     return fk
 
-def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield()):
+def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orderable=False, deletable=True, formfield_callback=lambda f: f.formfield(), formset=None):
     """
     Returns an ``InlineFormset`` for the given kwargs.
 
@@ -347,7 +347,7 @@ def inline_formset(parent_model, model, fk_name=None, fields=None, extra=3, orde
     """
     fk = get_foreign_key(parent_model, model, fk_name=fk_name)
     # let the formset handle object deletion by default
-    FormSet = formset_for_model(model, formset=InlineFormset, fields=fields,
+    FormSet = formset_for_model(model, formset=formset or InlineFormset, fields=fields,
                                 formfield_callback=formfield_callback,
                                 extra=extra, orderable=orderable,
                                 deletable=deletable)
