Explorar o código

Fixed #20702 -- Deprecated get_formsets in favor of get_formsets_with_inlines.

Thanks stanislas.guerra at gmail.com for the report.
tschilling %!s(int64=11) %!d(string=hai) anos
pai
achega
0d1ba84d13

+ 48 - 11
django/contrib/admin/options.py

@@ -602,10 +602,48 @@ class ModelAdmin(BaseModelAdmin):
             self.get_changelist_form(request), extra=0,
             fields=self.list_editable, **defaults)
 
-    def get_formsets(self, request, obj=None):
+    def _get_formsets(self, request, obj):
+        """
+        Helper function that exists to allow the deprecation warning to be
+        executed while this function continues to return a generator.
+        """
         for inline in self.get_inline_instances(request, obj):
             yield inline.get_formset(request, obj)
 
+    def get_formsets(self, request, obj=None):
+        warnings.warn(
+            "ModelAdmin.get_formsets() is deprecated and will be removed in "
+            "Django 1.9. Use ModelAdmin.get_formsets_with_inlines() instead.",
+            PendingDeprecationWarning, stacklevel=2
+        )
+        return self._get_formsets(request, obj)
+
+    def get_formsets_with_inlines(self, request, obj=None):
+        """
+        Yields formsets and the corresponding inlines.
+        """
+        # We call get_formsets() [deprecated] and check if it triggers a
+        # warning. If it does, then it's ours and we can safely ignore it, but
+        # if it doesn't then it has been overridden so we must warn about the
+        # deprecation.
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            formsets = self.get_formsets(request, obj)
+
+        if len(w) != 1 or not issubclass(w[0].category, PendingDeprecationWarning):
+            warnings.warn(
+                "ModelAdmin.get_formsets() is deprecated and will be removed in "
+                "Django 1.9. Use ModelAdmin.get_formsets_with_inlines() instead.",
+                PendingDeprecationWarning
+            )
+            if formsets:
+                zipped = zip(formsets, self.get_inline_instances(request, None))
+                for formset, inline in zipped:
+                    yield formset, inline
+        else:
+            for inline in self.get_inline_instances(request, obj):
+                yield inline.get_formset(request, obj), inline
+
     def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
         return self.paginator(queryset, per_page, orphans, allow_empty_first_page)
 
@@ -1185,8 +1223,6 @@ class ModelAdmin(BaseModelAdmin):
             raise PermissionDenied
 
         ModelForm = self.get_form(request)
-        formsets = []
-        inline_instances = self.get_inline_instances(request, None)
         if request.method == 'POST':
             form = ModelForm(request.POST, request.FILES)
             if form.is_valid():
@@ -1195,7 +1231,7 @@ class ModelAdmin(BaseModelAdmin):
             else:
                 form_validated = False
                 new_object = self.model()
-            formsets = self._create_formsets(request, new_object, inline_instances)
+            formsets, inline_instances = self._create_formsets(request, new_object)
             if all_valid(formsets) and form_validated:
                 self.save_model(request, new_object, form, False)
                 self.save_related(request, form, formsets, False)
@@ -1213,7 +1249,7 @@ class ModelAdmin(BaseModelAdmin):
                 if isinstance(f, models.ManyToManyField):
                     initial[k] = initial[k].split(",")
             form = ModelForm(initial=initial)
-            formsets = self._create_formsets(request, self.model(), inline_instances)
+            formsets, inline_instances = self._create_formsets(request, self.model())
 
         adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
             self.get_prepopulated_fields(request),
@@ -1266,7 +1302,6 @@ class ModelAdmin(BaseModelAdmin):
                                     current_app=self.admin_site.name))
 
         ModelForm = self.get_form(request, obj)
-        inline_instances = self.get_inline_instances(request, obj)
         if request.method == 'POST':
             form = ModelForm(request.POST, request.FILES, instance=obj)
             if form.is_valid():
@@ -1275,7 +1310,7 @@ class ModelAdmin(BaseModelAdmin):
             else:
                 form_validated = False
                 new_object = obj
-            formsets = self._create_formsets(request, new_object, inline_instances)
+            formsets, inline_instances = self._create_formsets(request, new_object)
             if all_valid(formsets) and form_validated:
                 self.save_model(request, new_object, form, True)
                 self.save_related(request, form, formsets, True)
@@ -1285,7 +1320,7 @@ class ModelAdmin(BaseModelAdmin):
 
         else:
             form = ModelForm(instance=obj)
-            formsets = self._create_formsets(request, obj, inline_instances)
+            formsets, inline_instances = self._create_formsets(request, obj)
 
         adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
             self.get_prepopulated_fields(request, obj),
@@ -1566,14 +1601,15 @@ class ModelAdmin(BaseModelAdmin):
             "admin/object_history.html"
         ], context, current_app=self.admin_site.name)
 
-    def _create_formsets(self, request, obj, inline_instances):
+    def _create_formsets(self, request, obj):
         "Helper function to generate formsets for add/change_view."
         formsets = []
+        inline_instances = []
         prefixes = {}
         get_formsets_args = [request]
         if obj.pk:
             get_formsets_args.append(obj)
-        for FormSet, inline in zip(self.get_formsets(*get_formsets_args), inline_instances):
+        for FormSet, inline in self.get_formsets_with_inlines(*get_formsets_args):
             prefix = FormSet.get_default_prefix()
             prefixes[prefix] = prefixes.get(prefix, 0) + 1
             if prefixes[prefix] != 1 or not prefix:
@@ -1590,7 +1626,8 @@ class ModelAdmin(BaseModelAdmin):
                     'save_as_new': '_saveasnew' in request.POST
                 })
             formsets.append(FormSet(**formset_params))
-        return formsets
+            inline_instances.append(inline)
+        return formsets, inline_instances
 
 
 class InlineModelAdmin(BaseModelAdmin):

+ 2 - 0
docs/internals/deprecation.txt

@@ -453,6 +453,8 @@ these changes.
   * ``django.db.backends.util``
   * ``django.forms.util``
 
+* ``ModelAdmin.get_formsets`` will be removed.
+
 2.0
 ---
 

+ 23 - 2
docs/ref/contrib/admin/index.txt

@@ -454,7 +454,7 @@ subclass::
 .. attribute:: ModelAdmin.inlines
 
     See :class:`InlineModelAdmin` objects below as well as
-    :meth:`ModelAdmin.get_formsets`.
+    :meth:`ModelAdmin.get_formsets_with_inlines`.
 
 .. attribute:: ModelAdmin.list_display
 
@@ -1365,7 +1365,10 @@ templates used by the :class:`ModelAdmin` views:
 
 .. method:: ModelAdmin.get_formsets(self, request, obj=None)
 
-    Yields :class:`InlineModelAdmin`\s for use in admin add and change views.
+    .. deprecated:: 1.7
+        Use :meth:`get_formsets_with_inlines()` instead.
+
+    Yields  :class:`InlineModelAdmin`\s for use in admin add and change views.
 
     For example if you wanted to display a particular inline only in the change
     view, you could override ``get_formsets`` as follows::
@@ -1380,6 +1383,24 @@ templates used by the :class:`ModelAdmin` views:
                         continue
                     yield inline.get_formset(request, obj)
 
+.. method:: ModelAdmin.get_formsets_with_inlines(self, request, obj=None)
+
+    Yields (``FormSet``, :class:`InlineModelAdmin`) pairs for use in admin add
+    and change views.
+
+    For example if you wanted to display a particular inline only in the change
+    view, you could override ``get_formsets_with_inlines`` as follows::
+
+        class MyModelAdmin(admin.ModelAdmin):
+            inlines = [MyInline, SomeOtherInline]
+
+            def get_formsets_with_inlines(self, request, obj=None):
+                for inline in self.get_inline_instances(request, obj):
+                    # hide MyInline in the add view
+                    if isinstance(inline, MyInline) and obj is None:
+                        continue
+                    yield inline.get_formset(request, obj), inline
+
 .. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)
 
     The ``formfield_for_foreignkey`` method on a ``ModelAdmin`` allows you to

+ 13 - 6
docs/releases/1.7.txt

@@ -466,13 +466,13 @@ than simply ``myapp/models.py``, Django would look for :ref:`initial SQL data
 will search ``myapp/sql/`` as documented. The old location will continue to
 work until Django 1.9.
 
-``declared_fieldsets`` attribute on ``ModelAdmin.``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``declared_fieldsets`` attribute on ``ModelAdmin``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-``ModelAdmin.declared_fieldsets`` was deprecated. Despite being a private API,
-it will go through a regular deprecation path. This attribute was mostly used
-by methods that bypassed ``ModelAdmin.get_fieldsets()`` but this was considered
-a bug and has been addressed.
+``ModelAdmin.declared_fieldsets`` has been deprecated. Despite being a private
+API, it will go through a regular deprecation path. This attribute was mostly
+used by methods that bypassed ``ModelAdmin.get_fieldsets()`` but this was
+considered a bug and has been addressed.
 
 ``syncdb``
 ~~~~~~~~~~
@@ -491,3 +491,10 @@ to ``utils.py`` in an effort to unify all util and utils references:
 * ``django.contrib.gis.db.backends.util``
 * ``django.db.backends.util``
 * ``django.forms.util``
+
+``get_formsets`` method on ``ModelAdmin``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``ModelAdmin.get_formsets`` has been deprecated in favor of the new
+:meth:`~django.contrib.admin.ModelAdmin.get_formsets_with_inlines`, in order to
+better handle the case of selecting showing inlines on a ``ModelAdmin``.

+ 90 - 3
tests/generic_inline_admin/tests.py

@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 from __future__ import unicode_literals
+import warnings
 
 from django.conf import settings
 from django.contrib import admin
@@ -277,7 +278,7 @@ class GenericInlineModelAdminTest(TestCase):
 
         ma = EpisodeAdmin(Episode, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['keywords', 'id', 'DELETE'])
 
     def test_custom_form_meta_exclude(self):
@@ -307,7 +308,7 @@ class GenericInlineModelAdminTest(TestCase):
 
         ma = EpisodeAdmin(Episode, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['url', 'keywords', 'id', 'DELETE'])
 
         # Then, only with `ModelForm`  -----------------
@@ -323,7 +324,7 @@ class GenericInlineModelAdminTest(TestCase):
 
         ma = EpisodeAdmin(Episode, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['description', 'keywords', 'id', 'DELETE'])
 
     def test_get_fieldsets(self):
@@ -345,3 +346,89 @@ class GenericInlineModelAdminTest(TestCase):
         ma = MediaInline(Media, self.site)
         form = ma.get_formset(None).form
         self.assertEqual(form._meta.fields, ['url', 'description'])
+
+    def test_get_formsets_with_inlines(self):
+        """
+        get_formsets() triggers a deprecation warning when get_formsets is
+        overridden.
+        """
+        class MediaForm(ModelForm):
+            class Meta:
+                model = Media
+                exclude = ['url']
+
+        class MediaInline(GenericTabularInline):
+            exclude = ['description']
+            form = MediaForm
+            model = Media
+
+        class EpisodeAdmin(admin.ModelAdmin):
+            inlines = [
+                MediaInline
+            ]
+
+            def get_formsets(self, request, obj=None):
+                return []
+
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            ma = EpisodeAdmin(Episode, self.site)
+            list(ma.get_formsets_with_inlines(request))
+            # Verify that the deprecation warning was triggered when get_formsets was called
+            # This verifies that we called that method.
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[0].category, PendingDeprecationWarning))
+
+        class EpisodeAdmin(admin.ModelAdmin):
+            inlines = [
+                MediaInline
+            ]
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            ma = EpisodeAdmin(Episode, self.site)
+            list(ma.get_formsets_with_inlines(request))
+            self.assertEqual(len(w), 0)
+
+    def test_get_formsets_with_inlines_returns_tuples(self):
+        """
+        Ensure that get_formsets_with_inlines() returns the correct tuples.
+        """
+        class MediaForm(ModelForm):
+            class Meta:
+                model = Media
+                exclude = ['url']
+
+        class MediaInline(GenericTabularInline):
+            form = MediaForm
+            model = Media
+
+        class AlternateInline(GenericTabularInline):
+            form = MediaForm
+            model = Media
+
+        class EpisodeAdmin(admin.ModelAdmin):
+            inlines = [
+                AlternateInline, MediaInline
+            ]
+        ma = EpisodeAdmin(Episode, self.site)
+        inlines = ma.get_inline_instances(request)
+        for (formset, inline), other_inline in zip(ma.get_formsets_with_inlines(request), inlines):
+            self.assertIsInstance(formset, other_inline.get_formset(request).__class__)
+
+        class EpisodeAdmin(admin.ModelAdmin):
+            inlines = [
+                AlternateInline, MediaInline
+            ]
+
+            def get_formsets(self, request, obj=None):
+                # Catch the deprecation warning to force the usage of get_formsets
+                with warnings.catch_warnings(record=True) as w:
+                    warnings.simplefilter("always")
+                    return super(EpisodeAdmin, self).get_formsets(request, obj)
+
+        ma = EpisodeAdmin(Episode, self.site)
+        inlines = ma.get_inline_instances(request)
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            for (formset, inline), other_inline in zip(ma.get_formsets_with_inlines(request), inlines):
+                self.assertIsInstance(formset, other_inline.get_formset(request).__class__)

+ 4 - 4
tests/modeladmin/tests.py

@@ -207,7 +207,7 @@ class ModelAdminTests(TestCase):
 
         ma = BandAdmin(Band, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['main_band', 'opening_band', 'id', 'DELETE'])
 
     def test_custom_form_meta_exclude(self):
@@ -253,7 +253,7 @@ class ModelAdminTests(TestCase):
 
         ma = BandAdmin(Band, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['main_band', 'opening_band', 'day', 'id', 'DELETE'])
 
     def test_custom_form_validation(self):
@@ -327,7 +327,7 @@ class ModelAdminTests(TestCase):
 
         ma = BandAdmin(Band, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['main_band', 'day', 'transport', 'id', 'DELETE'])
 
     def test_queryset_override(self):
@@ -521,7 +521,7 @@ class ModelAdminTests(TestCase):
 
         ma = BandAdmin(Band, self.site)
         self.assertEqual(
-            list(list(ma.get_formsets(request))[0]().forms[0].fields),
+            list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
             ['extra', 'transport', 'id', 'DELETE', 'main_band'])