From 3d027b72ebaf3fa41c9f6c17873fe3cee08d9007 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 12 Aug 2011 14:14:49 +0000 Subject: [PATCH] Fixed #14496 -- Fixed conflict between ModelForm exclude and ModelAdmin readonly values. Thanks, Julien Phalip. git-svn-id: http://code.djangoproject.com/svn/django/trunk@16602 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/options.py | 8 ++ docs/ref/contrib/admin/index.txt | 18 +++++ tests/regressiontests/modeladmin/tests.py | 93 +++++++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 922205f8cd..cf7ea83c11 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -437,6 +437,10 @@ class ModelAdmin(BaseModelAdmin): else: exclude = list(self.exclude) exclude.extend(self.get_readonly_fields(request, obj)) + if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude: + # Take the custom ModelForm's Meta.exclude into account only if the + # ModelAdmin doesn't define its own. + exclude.extend(self.form._meta.exclude) # if exclude is an empty list we pass None to be consistant with the # default on modelform_factory exclude = exclude or None @@ -1343,6 +1347,10 @@ class InlineModelAdmin(BaseModelAdmin): else: exclude = list(self.exclude) exclude.extend(self.get_readonly_fields(request, obj)) + if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude: + # Take the custom ModelForm's Meta.exclude into account only if the + # InlineModelAdmin doesn't define its own. + exclude.extend(self.form._meta.exclude) # if exclude is an empty list we use None, since that's the actual # default exclude = exclude or None diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 43057a31b0..3cd8279234 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -313,6 +313,24 @@ subclass:: For an example see the section `Adding custom validation to the admin`_. + .. admonition:: Note + + If your ``ModelForm`` and ``ModelAdmin`` both define an ``exclude`` + option then ``ModelAdmin`` takes precedence:: + + class PersonForm(forms.ModelForm): + + class Meta: + model = Person + exclude = ['name'] + + class PersonAdmin(admin.ModelAdmin): + exclude = ['age'] + form = PersonForm + + In the above example, the "age" field will be excluded but the "name" + field will be included in the generated form. + .. attribute:: ModelAdmin.formfield_overrides This provides a quick-and-dirty way to override some of the diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py index 1bce158dd8..ad86f854a7 100644 --- a/tests/regressiontests/modeladmin/tests.py +++ b/tests/regressiontests/modeladmin/tests.py @@ -120,6 +120,99 @@ class ModelAdminTests(TestCase): self.assertEqual(ma.get_form(request).base_fields.keys(), ['name']) + def test_custom_form_meta_exclude_with_readonly(self): + """ + Ensure that the custom ModelForm's `Meta.exclude` is respected when + used in conjunction with `ModelAdmin.readonly_fields` and when no + `ModelAdmin.exclude` is defined. + Refs #14496. + """ + # First, with `ModelAdmin` ----------------------- + + class AdminBandForm(forms.ModelForm): + + class Meta: + model = Band + exclude = ['bio'] + + class BandAdmin(ModelAdmin): + readonly_fields = ['name'] + form = AdminBandForm + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['sign_date',]) + + # Then, with `InlineModelAdmin` ----------------- + + class AdminConcertForm(forms.ModelForm): + + class Meta: + model = Concert + exclude = ['day'] + + class ConcertInline(TabularInline): + readonly_fields = ['transport'] + form = AdminConcertForm + fk_name = 'main_band' + model = Concert + + class BandAdmin(ModelAdmin): + inlines = [ + ConcertInline + ] + + ma = BandAdmin(Band, self.site) + self.assertEqual( + list(ma.get_formsets(request))[0]().forms[0].fields.keys(), + ['main_band', 'opening_band', 'id', 'DELETE',]) + + def test_custom_form_meta_exclude(self): + """ + Ensure that the custom ModelForm's `Meta.exclude` is overridden if + `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined. + Refs #14496. + """ + # First, with `ModelAdmin` ----------------------- + + class AdminBandForm(forms.ModelForm): + + class Meta: + model = Band + exclude = ['bio'] + + class BandAdmin(ModelAdmin): + exclude = ['name'] + form = AdminBandForm + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['bio', 'sign_date',]) + + # Then, with `InlineModelAdmin` ----------------- + + class AdminConcertForm(forms.ModelForm): + + class Meta: + model = Concert + exclude = ['day'] + + class ConcertInline(TabularInline): + exclude = ['transport'] + form = AdminConcertForm + fk_name = 'main_band' + model = Concert + + class BandAdmin(ModelAdmin): + inlines = [ + ConcertInline + ] + + ma = BandAdmin(Band, self.site) + self.assertEqual( + list(ma.get_formsets(request))[0]().forms[0].fields.keys(), + ['main_band', 'opening_band', 'day', 'id', 'DELETE',]) + def test_custom_form_validation(self): # If we specify a form, it should use it allowing custom validation to work # properly. This won't, however, break any of the admin widgets or media.