summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach Borboa <zachborboa@gmail.com>2016-09-29 02:55:10 -0700
committerTim Graham <timograham@gmail.com>2016-10-07 18:49:28 -0400
commitbf91be83d56d70181de807a5e8d22679cf63a52f (patch)
treec12c401aa3cbe6b4bd9fd58d97af4bdc20f3bf94
parentc60feb6999f83bfd1fdabff01f0dd4a26d72e158 (diff)
Fixed #24941 -- Added ModelAdmin.get_exclude().
Thanks Ola Sitarska for the initial patch.
-rw-r--r--django/contrib/admin/options.py22
-rw-r--r--docs/ref/contrib/admin/index.txt8
-rw-r--r--docs/releases/1.11.txt4
-rw-r--r--tests/modeladmin/tests.py81
4 files changed, 105 insertions, 10 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 0804697594..499d27d02e 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -282,6 +282,12 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
except AttributeError:
return mark_safe(self.admin_site.empty_value_display)
+ def get_exclude(self, request, obj=None):
+ """
+ Hook for specifying exclude.
+ """
+ return self.exclude
+
def get_fields(self, request, obj=None):
"""
Hook for specifying fields.
@@ -605,13 +611,11 @@ class ModelAdmin(BaseModelAdmin):
fields = kwargs.pop('fields')
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
- if self.exclude is None:
- exclude = []
- else:
- exclude = list(self.exclude)
+ excluded = self.get_exclude(request, obj)
+ exclude = [] if excluded is None else list(excluded)
readonly_fields = self.get_readonly_fields(request, obj)
exclude.extend(readonly_fields)
- if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
+ if excluded 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)
@@ -1851,12 +1855,10 @@ class InlineModelAdmin(BaseModelAdmin):
fields = kwargs.pop('fields')
else:
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
- if self.exclude is None:
- exclude = []
- else:
- exclude = list(self.exclude)
+ excluded = self.get_exclude(request, obj)
+ exclude = [] if excluded is None else list(excluded)
exclude.extend(self.get_readonly_fields(request, obj))
- if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
+ if excluded 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)
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 845f467b77..4d3ab6dc1f 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -1459,6 +1459,14 @@ templates used by the :class:`ModelAdmin` views:
names on the changelist that will be linked to the change view, as described
in the :attr:`ModelAdmin.list_display_links` section.
+.. method:: ModelAdmin.get_exclude(request, obj=None)
+
+ .. versionadded:: 1.11
+
+ The ``get_exclude`` method is given the ``HttpRequest`` and the ``obj``
+ being edited (or ``None`` on an add form) and is expected to return a list
+ of fields, as described in :attr:`ModelAdmin.exclude`.
+
.. method:: ModelAdmin.get_fields(request, obj=None)
The ``get_fields`` method is given the ``HttpRequest`` and the ``obj``
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index f5faedd0a9..ff77ebeabb 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -68,6 +68,10 @@ Minor features
* :attr:`.ModelAdmin.date_hierarchy` can now reference fields across relations.
+* The new :meth:`ModelAdmin.get_exclude()
+ <django.contrib.admin.ModelAdmin.get_exclude>` hook allows specifying the
+ exclude fields based on the request or model instance.
+
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py
index c2250b923b..e2f565ad9d 100644
--- a/tests/modeladmin/tests.py
+++ b/tests/modeladmin/tests.py
@@ -52,6 +52,7 @@ class ModelAdminTests(TestCase):
self.assertEqual(list(ma.get_form(request).base_fields), ['name', 'bio', 'sign_date'])
self.assertEqual(list(ma.get_fields(request)), ['name', 'bio', 'sign_date'])
self.assertEqual(list(ma.get_fields(request, self.band)), ['name', 'bio', 'sign_date'])
+ self.assertIsNone(ma.get_exclude(request, self.band))
def test_default_fieldsets(self):
# fieldsets_add and fieldsets_change should return a special data structure that
@@ -279,6 +280,40 @@ class ModelAdminTests(TestCase):
['main_band', 'opening_band', 'day', 'id', 'DELETE']
)
+ def test_overriding_get_exclude(self):
+ class BandAdmin(ModelAdmin):
+ def get_exclude(self, request, obj=None):
+ return ['name']
+
+ self.assertEqual(
+ list(BandAdmin(Band, self.site).get_form(request).base_fields),
+ ['bio', 'sign_date']
+ )
+
+ def test_get_exclude_overrides_exclude(self):
+ class BandAdmin(ModelAdmin):
+ exclude = ['bio']
+
+ def get_exclude(self, request, obj=None):
+ return ['name']
+
+ self.assertEqual(
+ list(BandAdmin(Band, self.site).get_form(request).base_fields),
+ ['bio', 'sign_date']
+ )
+
+ def test_get_exclude_takes_obj(self):
+ class BandAdmin(ModelAdmin):
+ def get_exclude(self, request, obj=None):
+ if obj:
+ return ['sign_date']
+ return ['name']
+
+ self.assertEqual(
+ list(BandAdmin(Band, self.site).get_form(request, self.band).base_fields),
+ ['name', 'bio']
+ )
+
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.
@@ -346,6 +381,52 @@ class ModelAdminTests(TestCase):
list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
['main_band', 'day', 'transport', 'id', 'DELETE'])
+ def test_formset_overriding_get_exclude_with_form_fields(self):
+ class AdminConcertForm(forms.ModelForm):
+ class Meta:
+ model = Concert
+ fields = ['main_band', 'opening_band', 'day', 'transport']
+
+ class ConcertInline(TabularInline):
+ form = AdminConcertForm
+ fk_name = 'main_band'
+ model = Concert
+
+ def get_exclude(self, request, obj=None):
+ return ['opening_band']
+
+ class BandAdmin(ModelAdmin):
+ inlines = [ConcertInline]
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(
+ list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
+ ['main_band', 'day', 'transport', 'id', 'DELETE']
+ )
+
+ def test_formset_overriding_get_exclude_with_form_exclude(self):
+ class AdminConcertForm(forms.ModelForm):
+ class Meta:
+ model = Concert
+ exclude = ['day']
+
+ class ConcertInline(TabularInline):
+ form = AdminConcertForm
+ fk_name = 'main_band'
+ model = Concert
+
+ def get_exclude(self, request, obj=None):
+ return ['opening_band']
+
+ class BandAdmin(ModelAdmin):
+ inlines = [ConcertInline]
+
+ ma = BandAdmin(Band, self.site)
+ self.assertEqual(
+ list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
+ ['main_band', 'day', 'transport', 'id', 'DELETE']
+ )
+
def test_queryset_override(self):
# If we need to override the queryset of a ModelChoiceField in our custom form
# make sure that RelatedFieldWidgetWrapper doesn't mess that up.