diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index ddaf130584..bcf2a191fe 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1959,7 +1959,7 @@ class ModelAdmin(BaseModelAdmin): # Bypass validation of each view-only inline form (since the form's # data won't be in request.POST), unless the form was deleted. - if not inline.has_change_permission(request, obj): + if not inline.has_change_permission(request, obj if change else None): for index, form in enumerate(formset.initial_forms): if user_deleted_form(request, obj, formset, index): continue diff --git a/docs/releases/2.1.5.txt b/docs/releases/2.1.5.txt index 73850e01b5..27ffbc7510 100644 --- a/docs/releases/2.1.5.txt +++ b/docs/releases/2.1.5.txt @@ -22,3 +22,7 @@ Bugfixes * Fixed a regression in Django 2.1.4 (which enabled keep-alive connections) where request body data isn't properly consumed for such connections (:ticket:`30015`). + +* Fixed a regression in Django 2.1.4 where + ``InlineModelAdmin.has_change_permission()`` is incorrectly called with a + non-``None`` ``obj`` argument during an object add (:ticket:`30050`). diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index a94bbbccfc..7ef07ff036 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -2,6 +2,7 @@ import datetime import os import re import unittest +from unittest import mock from urllib.parse import parse_qsl, urljoin, urlparse import pytz @@ -1740,6 +1741,24 @@ class AdminViewPermissionsTest(TestCase): # make sure the view removes test cookie self.assertIs(self.client.session.test_cookie_worked(), False) + @mock.patch('django.contrib.admin.options.InlineModelAdmin.has_change_permission') + def test_add_view_with_view_only_inlines(self, has_change_permission): + """User with add permission to a section but view-only for inlines.""" + self.viewuser.user_permissions.add(get_perm(Section, get_permission_codename('add', Section._meta))) + self.client.force_login(self.viewuser) + # Valid POST creates a new section. + data = { + 'name': 'New obj', + 'article_set-TOTAL_FORMS': 0, + 'article_set-INITIAL_FORMS': 0, + } + response = self.client.post(reverse('admin:admin_views_section_add'), data) + self.assertRedirects(response, reverse('admin:index')) + self.assertEqual(Section.objects.latest('id').name, data['name']) + # InlineModelAdmin.has_change_permission()'s obj argument is always + # None during object add. + self.assertEqual([obj for (request, obj), _ in has_change_permission.call_args_list], [None, None]) + def test_change_view(self): """Change view should restrict access and allow users to edit items.""" change_dict = {'title': 'Ikke fordømt',