diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index 3f8abdd476..a5bcc3f6df 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -1,4 +1,3 @@ -import warnings from itertools import chain from django.apps import apps @@ -16,8 +15,6 @@ from django.forms.models import ( ) from django.template import engines from django.template.backends.django import DjangoTemplates -from django.utils.deprecation import RemovedInDjango30Warning -from django.utils.inspect import get_func_args def _issubclass(cls, classinfo): @@ -981,7 +978,6 @@ class ModelAdminChecks(BaseModelAdminChecks): class InlineModelAdminChecks(BaseModelAdminChecks): def check(self, inline_obj, **kwargs): - self._check_has_add_permission(inline_obj) parent_model = inline_obj.parent_model return [ *super().check(inline_obj), @@ -1066,20 +1062,6 @@ class InlineModelAdminChecks(BaseModelAdminChecks): else: return [] - def _check_has_add_permission(self, obj): - cls = obj.__class__ - try: - func = cls.has_add_permission - except AttributeError: - pass - else: - args = get_func_args(func) - if 'obj' not in args: - warnings.warn( - "Update %s.has_add_permission() to accept a positional " - "`obj` argument." % cls.__name__, RemovedInDjango30Warning - ) - def must_be(type, option, obj, id): return [ diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index eb2c8cd094..ab4aa6d93b 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -45,7 +45,6 @@ from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.html import format_html from django.utils.http import urlencode -from django.utils.inspect import get_func_args from django.utils.safestring import mark_safe from django.utils.text import capfirst, format_lazy, get_text_list from django.utils.translation import gettext as _, ngettext @@ -587,12 +586,11 @@ class ModelAdmin(BaseModelAdmin): for inline_class in self.inlines: inline = inline_class(self.model, self.admin_site) if request: - inline_has_add_permission = inline._has_add_permission(request, obj) if not (inline.has_view_or_change_permission(request, obj) or - inline_has_add_permission or + inline.has_add_permission(request, obj) or inline.has_delete_permission(request, obj)): continue - if not inline_has_add_permission: + if not inline.has_add_permission(request, obj): inline.max_num = 0 inline_instances.append(inline) @@ -1475,7 +1473,7 @@ class ModelAdmin(BaseModelAdmin): for inline, formset in zip(inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request, obj)) readonly = list(inline.get_readonly_fields(request, obj)) - has_add_permission = inline._has_add_permission(request, obj) + has_add_permission = inline.has_add_permission(request, obj) has_change_permission = inline.has_change_permission(request, obj) has_delete_permission = inline.has_delete_permission(request, obj) has_view_permission = inline.has_view_permission(request, obj) @@ -2011,11 +2009,6 @@ class InlineModelAdmin(BaseModelAdmin): js.append('collapse%s.js' % extra) return forms.Media(js=['admin/js/%s' % url for url in js]) - def _has_add_permission(self, request, obj): - # RemovedInDjango30Warning: obj will be a required argument. - args = get_func_args(self.has_add_permission) - return self.has_add_permission(request, obj) if 'obj' in args else self.has_add_permission(request) - def get_extra(self, request, obj=None, **kwargs): """Hook for customizing the number of extra inline forms.""" return self.extra @@ -2061,7 +2054,7 @@ class InlineModelAdmin(BaseModelAdmin): base_model_form = defaults['form'] can_change = self.has_change_permission(request, obj) if request else True - can_add = self._has_add_permission(request, obj) if request else True + can_add = self.has_add_permission(request, obj) if request else True class DeleteProtectedModelForm(base_model_form): @@ -2126,8 +2119,7 @@ class InlineModelAdmin(BaseModelAdmin): queryset = queryset.none() return queryset - def has_add_permission(self, request, obj=None): - # RemovedInDjango31Warning: obj becomes a mandatory argument. + def has_add_permission(self, request, obj): if self.opts.auto_created: # We're checking the rights to an auto-created intermediate model, # which doesn't have its own individual permissions. The user needs diff --git a/tests/modeladmin/test_has_add_permission_obj_deprecation.py b/tests/modeladmin/test_has_add_permission_obj_deprecation.py deleted file mode 100644 index 8e932ec7e8..0000000000 --- a/tests/modeladmin/test_has_add_permission_obj_deprecation.py +++ /dev/null @@ -1,111 +0,0 @@ -from datetime import date - -from django.contrib.admin.options import ModelAdmin, TabularInline -from django.contrib.admin.sites import AdminSite -from django.test import TestCase -from django.utils.deprecation import RemovedInDjango30Warning - -from .models import Band, Song -from .test_checks import CheckTestCase - - -class HasAddPermissionObjTests(CheckTestCase): - def test_model_admin_inherited_valid(self): - class BandAdmin(ModelAdmin): - pass - - self.assertIsValid(BandAdmin, Band) - - def test_model_admin_valid(self): - class BandAdmin(ModelAdmin): - def has_add_permission(self, request): - return super().has_add_permission(request) - - self.assertIsValid(BandAdmin, Band) - - def test_inline_admin_inherited_valid(self): - class SongInlineAdmin(TabularInline): - model = Song - - class BandAdmin(ModelAdmin): - inlines = [SongInlineAdmin] - - self.assertIsValid(BandAdmin, Band) - - def test_inline_admin_valid(self): - class SongInlineAdmin(TabularInline): - model = Song - - def has_add_permission(self, request, obj): - return super().has_add_permission(request, obj) - - class BandAdmin(ModelAdmin): - inlines = [SongInlineAdmin] - - self.assertIsValid(BandAdmin, Band) - - def test_inline_admin_warning(self): - class SongInlineAdmin(TabularInline): - model = Song - - def has_add_permission(self, request): - return super().has_add_permission(request) - - class BandAdmin(ModelAdmin): - inlines = [SongInlineAdmin] - - msg = ( - "Update SongInlineAdmin.has_add_permission() to accept a " - "positional `obj` argument." - ) - with self.assertWarnsMessage(RemovedInDjango30Warning, msg): - self.assertIsValid(BandAdmin, Band) - - -class MockRequest: - method = 'POST' - FILES = {} - POST = {} - - -class SongInline(TabularInline): - model = Song - - def has_add_permission(self, request): - return True - - -class BandAdmin(ModelAdmin): - inlines = [SongInline] - - -class ModelAdminTests(TestCase): - - @classmethod - def setUpTestData(cls): - cls.band = Band.objects.create(name='The Doors', bio='', sign_date=date(1965, 1, 1)) - cls.song = Song.objects.create(name='test', band=cls.band) - - def setUp(self): - self.site = AdminSite() - self.request = MockRequest() - self.request.POST = { - 'song_set-TOTAL_FORMS': 4, - 'song_set-INITIAL_FORMS': 1, - } - self.request.user = self.MockAddUser() - self.ma = BandAdmin(Band, self.site) - - class MockAddUser: - def has_perm(self, perm): - return perm == 'modeladmin.add_band' - - def test_get_inline_instances(self): - self.assertEqual(len(self.ma.get_inline_instances(self.request)), 1) - - def test_get_inline_formsets(self): - formsets, inline_instances = self.ma._create_formsets(self.request, self.band, change=True) - self.assertEqual(len(self.ma.get_inline_formsets(self.request, formsets, inline_instances)), 1) - - def test_get_formsets_with_inlines(self): - self.assertEqual(len(list(self.ma. get_formsets_with_inlines(self.request, self.band))), 1) diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 0d78dd9c21..6934bf2b3c 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -734,10 +734,6 @@ class ModelAdminPermissionTests(SimpleTestCase): def has_perm(self, perm): return perm == 'modeladmin.add_band' - class MockAddUserWithInline(MockUser): - def has_perm(self, perm): - return perm == 'modeladmin.add_concert' - class MockChangeUser(MockUser): def has_perm(self, perm): return perm == 'modeladmin.change_band' @@ -797,26 +793,6 @@ class ModelAdminPermissionTests(SimpleTestCase): self.assertEqual(len(inline_instances), 1) self.assertIsInstance(inline_instances[0], ConcertInline) - def test_inline_has_add_permission_without_obj(self): - # This test will be removed in Django 3.1 when `obj` becomes a required - # argument of has_add_permission() (#27991). - class ConcertInline(TabularInline): - model = Concert - - def has_add_permission(self, request): - return super().has_add_permission(request) - - class BandAdmin(ModelAdmin): - inlines = [ConcertInline] - - ma = BandAdmin(Band, AdminSite()) - request = MockRequest() - request.user = self.MockAddUserWithInline() - band = Band(name='The Doors', bio='', sign_date=date(1965, 1, 1)) - inline_instances = ma.get_inline_instances(request, band) - self.assertEqual(len(inline_instances), 1) - self.assertIsInstance(inline_instances[0], ConcertInline) - def test_has_change_permission(self): """ has_change_permission returns True for users who can edit objects and