Code

Ticket #14496: 14496.custom-admin-form-exclude.diff

File 14496.custom-admin-form-exclude.diff, 6.8 KB (added by julien, 3 years ago)
Line 
1diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
2index c603210..af0c146 100644
3--- a/django/contrib/admin/options.py
4+++ b/django/contrib/admin/options.py
5@@ -428,6 +428,9 @@ class ModelAdmin(BaseModelAdmin):
6             exclude = list(self.exclude)
7         exclude.extend(kwargs.get("exclude", []))
8         exclude.extend(self.get_readonly_fields(request, obj))
9+        if hasattr(self.form, '_meta') and self.form._meta.exclude:
10+            # Take into account the custom ModelForm's Meta.exclude.
11+            exclude.extend(self.form.Meta.exclude)
12         # if exclude is an empty list we pass None to be consistant with the
13         # default on modelform_factory
14         exclude = exclude or None
15@@ -1330,6 +1333,9 @@ class InlineModelAdmin(BaseModelAdmin):
16             exclude = list(self.exclude)
17         exclude.extend(kwargs.get("exclude", []))
18         exclude.extend(self.get_readonly_fields(request, obj))
19+        if hasattr(self.form, '_meta') and self.form._meta.exclude:
20+            # Take into account the custom ModelForm's Meta.exclude.
21+            exclude.extend(self.form.Meta.exclude)
22         # if exclude is an empty list we use None, since that's the actual
23         # default
24         exclude = exclude or None
25diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
26index fb48ba8..ba1593d 100644
27--- a/docs/ref/contrib/admin/index.txt
28+++ b/docs/ref/contrib/admin/index.txt
29@@ -293,6 +293,27 @@ subclass::
30 
31     For an example see the section `Adding custom validation to the admin`_.
32 
33+    .. admonition:: Note
34+   
35+        .. versionchanged:: 1.4
36+   
37+        If your own ``ModelForm`` defines an ``exclude`` list of fields in its
38+        ``Meta`` options, then that list of fields will automatically
39+        be combined to that of the ``ModelAdmin``'s own ``exclude`` list::
40+       
41+            class PersonForm(forms.ModelForm):
42+               
43+                class Meta:
44+                    model = Person
45+                    exclude = ['name']
46+       
47+            class PersonAdmin(admin.ModelAdmin):
48+                exclude = ['age']
49+                form = PersonForm
50+               
51+        In the above example, the two fields "name" and "age" will be excluded from the
52+        generated form.
53+
54 .. attribute:: ModelAdmin.formfield_overrides
55 
56     This provides a quick-and-dirty way to override some of the
57diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
58index 6a126ed..fef786a 100644
59--- a/docs/releases/1.4.txt
60+++ b/docs/releases/1.4.txt
61@@ -60,6 +60,27 @@ A new helper function,
62 ``template.Library`` to ease the creation of template tags that store some
63 data in a specified context variable.
64 
65+``exclude`` in ``ModelAdmin`` and custom admin forms
66+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67+
68+If you define a custom ``ModelForm`` in a ``ModelAdmin`` and that form
69+defines an ``exclude`` list of fields in its ``Meta`` options, then that list
70+of fields will automatically be combined to that of the ``ModelAdmin``'s own
71+``exclude`` list::
72+
73+    class PersonForm(forms.ModelForm):
74+       
75+        class Meta:
76+            model = Person
77+            exclude = ['name']
78+
79+    class PersonAdmin(admin.ModelAdmin):
80+        exclude = ['age']
81+        form = PersonForm
82+       
83+In the above example, the two fields "name" and "age" will be excluded from the
84+generated form.
85+
86 .. _backwards-incompatible-changes-1.4:
87 
88 Backwards incompatible changes in 1.4
89diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py
90index 530b476..70b3e3e 100644
91--- a/tests/regressiontests/modeladmin/tests.py
92+++ b/tests/regressiontests/modeladmin/tests.py
93@@ -119,7 +119,98 @@ class ModelAdminTests(TestCase):
94         ma = BandAdmin(Band, self.site)
95         self.assertEqual(ma.get_form(request).base_fields.keys(),
96             ['name'])
97+       
98+    def test_custom_form_meta_exclude_with_readonly(self):
99+        """
100+        Ensure that the custom ModelForm's `Meta.exclude` is respected when
101+        used in conjunction with readonly fields.
102+        Refs #14496.
103+        """
104+        # First, with `ModelAdmin` -----------------------
105+       
106+        class AdminBandForm(forms.ModelForm):
107+
108+            class Meta:
109+                model = Band
110+                exclude = ['bio']
111 
112+        class BandAdmin(ModelAdmin):
113+            readonly_fields = ['name']
114+            form = AdminBandForm
115+       
116+        ma = BandAdmin(Band, self.site)
117+        self.assertEqual(ma.get_form(request).base_fields.keys(),
118+            ['sign_date',])
119+   
120+        # Then, with `InlineModelAdmin`  -----------------
121+       
122+        class AdminConcertForm(forms.ModelForm):
123+
124+            class Meta:
125+                model = Concert
126+                exclude = ['day']
127+       
128+        class ConcertInline(TabularInline):
129+            readonly_fields = ['transport']
130+            form = AdminConcertForm
131+            fk_name = 'main_band'
132+            model = Concert
133+           
134+        class BandAdmin(ModelAdmin):
135+            inlines = [
136+                ConcertInline
137+            ]
138+
139+        ma = BandAdmin(Band, self.site)
140+        self.assertEqual(
141+            list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
142+            ['main_band', 'opening_band', 'id', 'DELETE',])
143+       
144+    def test_custom_form_meta_exclude(self):
145+        """
146+        Ensure that the custom ModelForm's `Meta.exclude` is respected.
147+        Refs #14496.
148+        """
149+        # First, with `ModelAdmin` -----------------------
150+       
151+        class AdminBandForm(forms.ModelForm):
152+
153+            class Meta:
154+                model = Band
155+                exclude = ['bio']
156+
157+        class BandAdmin(ModelAdmin):
158+            exclude = ['name']
159+            form = AdminBandForm
160+       
161+        ma = BandAdmin(Band, self.site)
162+        self.assertEqual(ma.get_form(request).base_fields.keys(),
163+            ['sign_date',])
164+       
165+        # Then, with `InlineModelAdmin`  -----------------
166+       
167+        class AdminConcertForm(forms.ModelForm):
168+
169+            class Meta:
170+                model = Concert
171+                exclude = ['day']
172+       
173+        class ConcertInline(TabularInline):
174+            exclude = ['transport']
175+            form = AdminConcertForm
176+            fk_name = 'main_band'
177+            model = Concert
178+           
179+        class BandAdmin(ModelAdmin):
180+            inlines = [
181+                ConcertInline
182+            ]
183+
184+        ma = BandAdmin(Band, self.site)
185+        self.assertEqual(
186+            list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
187+            ['main_band', 'opening_band', 'id', 'DELETE',])
188+       
189     def test_custom_form_validation(self):
190         # If we specify a form, it should use it allowing custom validation to work
191         # properly. This won't, however, break any of the admin widgets or media.