﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
15907	Generic inlines ignoring the exclude information from a custom form.	leonelfreire	nobody	"Suppose we have the following models:


{{{
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class MainModel(models.Model):
    field1 = models.CharField(max_length=255)

class Inline1(models.Model):
    main_model     = models.ForeignKey(MainModel)
    inline1_field1 = models.CharField(max_length=255)
    inline1_field2 = models.CharField(max_length=255)

class Inline2(models.Model):
    content_type   = models.ForeignKey(ContentType)
    object_id      = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey()
    inline2_field1 = models.CharField(max_length=255)
    inline2_field2 = models.CharField(max_length=255)
}}}

    
and this admin configuration:


{{{
from django import forms
from django.contrib.contenttypes import generic
from django.contrib import admin

from bug.models import MainModel, Inline1, Inline2

class Inline1Form(forms.models.ModelForm):
    class Meta:
        model   = Inline1
        exclude = ('inline1_field2', )

class Inline2Form(forms.models.ModelForm):
    class Meta:
        model   = Inline2
        exclude = ('inline2_field2',)

class Inline1Inline(admin.StackedInline):
    model = Inline1
    form  = Inline1Form
    extra = 1

class Inline2Inline(generic.GenericStackedInline):
    model = Inline2
    form  = Inline2Form
    extra = 1

class MainModelAdmin(admin.ModelAdmin):
    inlines = (Inline1Inline, Inline2Inline)

admin.site.register(MainModel, MainModelAdmin)
}}}


Then, the exclude attribute from the inner Meta class from Inline2Form is not used in the final Meta class (look the comments):


{{{
# Django 1.3 (file: django/contrib/contenttypes/generic.py, line: 362)
def generic_inlineformset_factory(model, form=ModelForm,
                                  formset=BaseGenericInlineFormSet,
                                  ct_field=""content_type"", fk_field=""object_id"",
                                  fields=None, exclude=None,
                                  extra=3, can_order=False, can_delete=True,
                                  max_num=None,
                                  formfield_callback=lambda f: f.formfield()):
    """"""
    Returns an ``GenericInlineFormSet`` for the given kwargs.

    You must provide ``ct_field`` and ``object_id`` if they different from the
    defaults ``content_type`` and ``object_id`` respectively.
    """"""
    opts = model._meta
    # Avoid a circular import.
    from django.contrib.contenttypes.models import ContentType
    # if there is no field called `ct_field` let the exception propagate
    ct_field = opts.get_field(ct_field)
    if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType:
        raise Exception(""fk_name '%s' is not a ForeignKey to ContentType"" % ct_field)
    fk_field = opts.get_field(fk_field) # let the exception propagate
    if exclude is not None:
        exclude = list(exclude)
        exclude.extend([ct_field.name, fk_field.name])
    else:
    	##################################################################
	##################################################################
	# ct_field.name and fk_field.name are always in the exclude list #
	##################################################################
	##################################################################
        exclude = [ct_field.name, fk_field.name]
    FormSet = modelformset_factory(model, form=form,
                                   formfield_callback=formfield_callback,
                                   formset=formset,
                                   extra=extra, can_delete=can_delete, can_order=can_order,
                                   fields=fields, exclude=exclude, max_num=max_num)
    FormSet.ct_field = ct_field
    FormSet.ct_fk_field = fk_field
    return FormSet
}}}

    

{{{
# Django 1.3 (file: django/forms/models.py, line: 370)
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
                       formfield_callback=None):
    # Create the inner Meta class. FIXME: ideally, we should be able to
    # construct a ModelForm without creating and passing in a temporary
    # inner class.

    # Build up a list of attributes that the Meta object will have.
    attrs = {'model': model}
    if fields is not None:
        attrs['fields'] = fields
    if exclude is not None:
    	###########################################################################################
    	###########################################################################################
    	# For an generic inline, exclude is never None (at least ct_field.name and fk_field.name) #
    	###########################################################################################
    	###########################################################################################
        attrs['exclude'] = exclude

    # If parent form class already has an inner Meta, the Meta we're
    # creating needs to inherit from the parent's inner meta.
    #######################################################################################################
    #######################################################################################################
    # I think the problem is here: we are not ""mixing"" attrs['exclude'] with form.Meta.exclude.           #
    # attrs['exclude'] is overwriting the attribute form.Meta.exclude and the final Meta class is created #
    # without the exclude information from the Meta class of the form.                                    #
    # I think that, if the generic inline does not provide an exclude list by itself, but have a custom   #
    # form that do this, the exclude from the Meta of this form should be honored.                        #
    #######################################################################################################
    #######################################################################################################
    parent = (object,)
    if hasattr(form, 'Meta'):
        parent = (form.Meta, object)
    Meta = type('Meta', parent, attrs)

    # Give this new form class a reasonable name.
    class_name = model.__name__ + 'Form'

    # Class attributes for the new form class.
    form_class_attrs = {
        'Meta': Meta,
        'formfield_callback': formfield_callback
    }

    return ModelFormMetaclass(class_name, (form,), form_class_attrs)

}}}


I've attached the project that I made in order to test this (if this help)."	Bug	closed	Forms	1.3	Normal	fixed	form, exclude, generic, inline		Ready for checkin	1	0	0	0	0	0
