Commit 0623f4de authored by Kamil Braun's avatar Kamil Braun Committed by Tim Graham
Browse files

Fixed #23934 -- Fixed regression in admin views obj parameter.

parent 1da1fe8e
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -1425,7 +1425,7 @@ class ModelAdmin(BaseModelAdmin):
            else:
                form_validated = False
                new_object = form.instance
            formsets, inline_instances = self._create_formsets(request, new_object)
            formsets, inline_instances = self._create_formsets(request, new_object, change=not add)
            if all_valid(formsets) and form_validated:
                self.save_model(request, new_object, form, not add)
                self.save_related(request, form, formsets, not add)
@@ -1440,10 +1440,10 @@ class ModelAdmin(BaseModelAdmin):
            if add:
                initial = self.get_changeform_initial_data(request)
                form = ModelForm(initial=initial)
                formsets, inline_instances = self._create_formsets(request, self.model())
                formsets, inline_instances = self._create_formsets(request, self.model(), change=False)
            else:
                form = ModelForm(instance=obj)
                formsets, inline_instances = self._create_formsets(request, obj)
                formsets, inline_instances = self._create_formsets(request, obj, change=True)

        adminForm = helpers.AdminForm(
            form,
@@ -1729,13 +1729,13 @@ class ModelAdmin(BaseModelAdmin):
            "admin/object_history.html"
        ], context, current_app=self.admin_site.name)

    def _create_formsets(self, request, obj):
    def _create_formsets(self, request, obj, change):
        "Helper function to generate formsets for add/change_view."
        formsets = []
        inline_instances = []
        prefixes = {}
        get_formsets_args = [request]
        if obj.pk:
        if change:
            get_formsets_args.append(obj)
        for FormSet, inline in self.get_formsets_with_inlines(*get_formsets_args):
            prefix = FormSet.get_default_prefix()
+4 −0
Original line number Diff line number Diff line
@@ -94,3 +94,7 @@ Bugfixes

* Fixed a crash when ``RunSQL`` SQL content was collected by the schema editor,
  typically when using ``sqlmigrate`` (:ticket:`23909`).

* Fixed a regression in ``contrib.admin`` add/change views which caused some
  ``ModelAdmin`` methods to receive the incorrect ``obj`` value
  (:ticket:`23934`).
+26 −2
Original line number Diff line number Diff line
@@ -18,7 +18,8 @@ from django.contrib.admin import BooleanFieldListFilter
from django.utils.safestring import mark_safe
from django.utils.six import StringIO

from .models import (Article, Chapter, Child, Parent, Picture, Widget,
from .models import (
    Article, Chapter, Child, Parent, Picture, Widget,
    DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link,
    PrePopulatedPost, PrePopulatedSubPost, CustomArticle, Section,
    ModelWithStringPrimaryKey, Color, Thing, Actor, Inquisition, Sketch,
@@ -37,7 +38,9 @@ from .models import (Article, Chapter, Child, Parent, Picture, Widget,
    State, City, Restaurant, Worker, ParentWithDependentChildren,
    DependentChild, StumpJoke, FieldOverridePost, FunkyTag,
    ReferencedByParent, ChildOfReferer, ReferencedByInline,
    InlineReference, InlineReferer, Recipe, Ingredient, NotReferenced)
    InlineReference, InlineReferer, Recipe, Ingredient, NotReferenced,
    ExplicitlyProvidedPK, ImplicitlyGeneratedPK,
)


def callable_year(dt_value):
@@ -842,6 +845,25 @@ class InlineRefererAdmin(admin.ModelAdmin):
    inlines = [InlineReferenceInline]


class GetFormsetsArgumentCheckingAdmin(admin.ModelAdmin):
    fields = ['name']

    def add_view(self, request, *args, **kwargs):
        request.is_add_view = True
        return super(GetFormsetsArgumentCheckingAdmin, self).add_view(request, *args, **kwargs)

    def change_view(self, request, *args, **kwargs):
        request.is_add_view = False
        return super(GetFormsetsArgumentCheckingAdmin, self).change_view(request, *args, **kwargs)

    def get_formsets_with_inlines(self, request, obj=None):
        if request.is_add_view and obj is not None:
            raise Exception("'obj' passed to get_formsets_with_inlines wasn't None during add_view")
        if not request.is_add_view and obj is None:
            raise Exception("'obj' passed to get_formsets_with_inlines was None during change_view")
        return super(GetFormsetsArgumentCheckingAdmin, self).get_formsets_with_inlines(request, obj)


site = admin.AdminSite(name="admin")
site.site_url = '/my-site-url/'
site.register(Article, ArticleAdmin)
@@ -942,6 +964,8 @@ site.register(StumpJoke)
site.register(Recipe)
site.register(Ingredient)
site.register(NotReferenced)
site.register(ExplicitlyProvidedPK, GetFormsetsArgumentCheckingAdmin)
site.register(ImplicitlyGeneratedPK, GetFormsetsArgumentCheckingAdmin)

# Register core models we need in our tests
from django.contrib.auth.models import User, Group
+9 −0
Original line number Diff line number Diff line
@@ -874,3 +874,12 @@ class RecipeIngredient(models.Model):
class NotReferenced(models.Model):
    # Don't point any FK at this model.
    pass


# Models for #23934
class ExplicitlyProvidedPK(models.Model):
    name = models.IntegerField(primary_key=True)


class ImplicitlyGeneratedPK(models.Model):
    name = models.IntegerField(unique=True)
+48 −0
Original line number Diff line number Diff line
@@ -5207,3 +5207,51 @@ class TestEtagWithAdminView(TestCase):
            response = self.client.get('/test_admin/admin/')
            self.assertEqual(response.status_code, 302)
            self.assertTrue(response.has_header('ETag'))


@override_settings(
    PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
    ROOT_URLCONF="admin_views.urls",
)
class GetFormsetsWithInlinesArgumentTest(TestCase):
    """
    #23934 - When adding a new model instance in the admin, the 'obj' argument
    of get_formsets_with_inlines() should be None. When changing, it should be
    equal to the existing model instance.
    The GetFormsetsArgumentCheckingAdmin ModelAdmin throws an exception
    if obj is not None during add_view or obj is None during change_view.
    """
    fixtures = ['admin-views-users.xml']

    def setUp(self):
        self.client.login(username='super', password='secret')

    def test_explicitly_provided_pk(self):
        post_data = {'name': '1'}
        try:
            response = self.client.post('/test_admin/admin/admin_views/explicitlyprovidedpk/add/', post_data)
        except Exception as e:
            self.fail(e)
        self.assertEqual(response.status_code, 302)

        post_data = {'name': '2'}
        try:
            response = self.client.post('/test_admin/admin/admin_views/explicitlyprovidedpk/1/', post_data)
        except Exception as e:
            self.fail(e)
        self.assertEqual(response.status_code, 302)

    def test_implicitly_generated_pk(self):
        post_data = {'name': '1'}
        try:
            response = self.client.post('/test_admin/admin/admin_views/implicitlygeneratedpk/add/', post_data)
        except Exception as e:
            self.fail(e)
        self.assertEqual(response.status_code, 302)

        post_data = {'name': '2'}
        try:
            response = self.client.post('/test_admin/admin/admin_views/implicitlygeneratedpk/1/', post_data)
        except Exception as e:
            self.fail(e)
        self.assertEqual(response.status_code, 302)