Code

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

File 14496.custom-admin-form-exclude.alternative.diff, 6.0 KB (added by julien, 3 years ago)
Line 
1diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
2index c603210..73e1e30 100644
3--- a/django/contrib/admin/options.py
4+++ b/django/contrib/admin/options.py
5@@ -428,6 +428,10 @@ 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 self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
10+            # Take the custom ModelForm's Meta.exclude into account only if the
11+            # ModelAdmin doesn't define its own.
12+            exclude.extend(self.form.Meta.exclude)
13         # if exclude is an empty list we pass None to be consistant with the
14         # default on modelform_factory
15         exclude = exclude or None
16@@ -1330,6 +1334,10 @@ class InlineModelAdmin(BaseModelAdmin):
17             exclude = list(self.exclude)
18         exclude.extend(kwargs.get("exclude", []))
19         exclude.extend(self.get_readonly_fields(request, obj))
20+        if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
21+            # Take the custom ModelForm's Meta.exclude into account only if the
22+            # InlineModelAdmin doesn't define its own.
23+            exclude.extend(self.form.Meta.exclude)
24         # if exclude is an empty list we use None, since that's the actual
25         # default
26         exclude = exclude or None
27diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
28index fb48ba8..ba890ec 100644
29--- a/docs/ref/contrib/admin/index.txt
30+++ b/docs/ref/contrib/admin/index.txt
31@@ -293,6 +293,24 @@ subclass::
32 
33     For an example see the section `Adding custom validation to the admin`_.
34 
35+    .. admonition:: Note
36+   
37+        If your ``ModelForm`` and ``ModelAdmin`` both define an ``exclude``
38+        option then ``ModelAdmin`` takes precedence::
39+       
40+            class PersonForm(forms.ModelForm):
41+               
42+                class Meta:
43+                    model = Person
44+                    exclude = ['name']
45+       
46+            class PersonAdmin(admin.ModelAdmin):
47+                exclude = ['age']
48+                form = PersonForm
49+               
50+        In the above example, the "age" field will be excluded but the "name"
51+        field will be included in the generated form.
52+
53 .. attribute:: ModelAdmin.formfield_overrides
54 
55     This provides a quick-and-dirty way to override some of the
56diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py
57index 530b476..af0ae33 100644
58--- a/tests/regressiontests/modeladmin/tests.py
59+++ b/tests/regressiontests/modeladmin/tests.py
60@@ -119,7 +119,100 @@ class ModelAdminTests(TestCase):
61         ma = BandAdmin(Band, self.site)
62         self.assertEqual(ma.get_form(request).base_fields.keys(),
63             ['name'])
64+       
65+    def test_custom_form_meta_exclude_with_readonly(self):
66+        """
67+        Ensure that the custom ModelForm's `Meta.exclude` is respected when
68+        used in conjunction with `ModelAdmin.readonly_fields` and when no
69+        `ModelAdmin.exclude` is defined.
70+        Refs #14496.
71+        """
72+        # First, with `ModelAdmin` -----------------------
73+       
74+        class AdminBandForm(forms.ModelForm):
75+
76+            class Meta:
77+                model = Band
78+                exclude = ['bio']
79 
80+        class BandAdmin(ModelAdmin):
81+            readonly_fields = ['name']
82+            form = AdminBandForm
83+       
84+        ma = BandAdmin(Band, self.site)
85+        self.assertEqual(ma.get_form(request).base_fields.keys(),
86+            ['sign_date',])
87+   
88+        # Then, with `InlineModelAdmin`  -----------------
89+       
90+        class AdminConcertForm(forms.ModelForm):
91+
92+            class Meta:
93+                model = Concert
94+                exclude = ['day']
95+       
96+        class ConcertInline(TabularInline):
97+            readonly_fields = ['transport']
98+            form = AdminConcertForm
99+            fk_name = 'main_band'
100+            model = Concert
101+           
102+        class BandAdmin(ModelAdmin):
103+            inlines = [
104+                ConcertInline
105+            ]
106+
107+        ma = BandAdmin(Band, self.site)
108+        self.assertEqual(
109+            list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
110+            ['main_band', 'opening_band', 'id', 'DELETE',])
111+       
112+    def test_custom_form_meta_exclude(self):
113+        """
114+        Ensure that the custom ModelForm's `Meta.exclude` is overridden if
115+        `ModelAdmin.exclude` or `InlineModelAdmin.exclude` are defined.
116+        Refs #14496.
117+        """
118+        # First, with `ModelAdmin` -----------------------
119+       
120+        class AdminBandForm(forms.ModelForm):
121+
122+            class Meta:
123+                model = Band
124+                exclude = ['bio']
125+
126+        class BandAdmin(ModelAdmin):
127+            exclude = ['name']
128+            form = AdminBandForm
129+       
130+        ma = BandAdmin(Band, self.site)
131+        self.assertEqual(ma.get_form(request).base_fields.keys(),
132+            ['bio', 'sign_date',])
133+       
134+        # Then, with `InlineModelAdmin`  -----------------
135+       
136+        class AdminConcertForm(forms.ModelForm):
137+
138+            class Meta:
139+                model = Concert
140+                exclude = ['day']
141+       
142+        class ConcertInline(TabularInline):
143+            exclude = ['transport']
144+            form = AdminConcertForm
145+            fk_name = 'main_band'
146+            model = Concert
147+           
148+        class BandAdmin(ModelAdmin):
149+            inlines = [
150+                ConcertInline
151+            ]
152+
153+        ma = BandAdmin(Band, self.site)
154+        self.assertEqual(
155+            list(ma.get_formsets(request))[0]().forms[0].fields.keys(),
156+            ['main_band', 'opening_band', 'day', 'id', 'DELETE',])
157+       
158     def test_custom_form_validation(self):
159         # If we specify a form, it should use it allowing custom validation to work
160         # properly. This won't, however, break any of the admin widgets or media.