Fixed #27991 -- Added obj arg to InlineModelAdmin.has_add_permission().

Thanks Vladimir Ivanov for the initial patch.
This commit is contained in:
Jon Dufresne 2018-02-21 19:11:50 -08:00 committed by Tim Graham
parent 4f88143649
commit be6ca89396
7 changed files with 128 additions and 6 deletions

View File

@ -1,3 +1,4 @@
import warnings
from itertools import chain
from django.apps import apps
@ -13,6 +14,8 @@ from django.forms.models import (
BaseModelForm, BaseModelFormSet, _get_foreign_key,
)
from django.template.engine import Engine
from django.utils.deprecation import RemovedInDjango30Warning
from django.utils.inspect import get_func_args
def check_admin_app(app_configs, **kwargs):
@ -884,6 +887,7 @@ 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),
@ -968,6 +972,20 @@ 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 [

View File

@ -44,6 +44,7 @@ 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
@ -559,12 +560,18 @@ class ModelAdmin(BaseModelAdmin):
inline_instances = []
for inline_class in self.inlines:
inline = inline_class(self.model, self.admin_site)
# RemovedInDjango30Warning: obj will be a required argument.
args = get_func_args(inline.has_add_permission)
if 'obj' in args:
inline_has_add_permission = inline.has_add_permission(request, obj)
else:
inline_has_add_permission = inline.has_add_permission(request)
if request:
if not (inline.has_add_permission(request) or
if not (inline_has_add_permission or
inline.has_change_permission(request, obj) or
inline.has_delete_permission(request, obj)):
continue
if not inline.has_add_permission(request):
if not inline_has_add_permission:
inline.max_num = 0
inline_instances.append(inline)
@ -2007,13 +2014,13 @@ class InlineModelAdmin(BaseModelAdmin):
queryset = queryset.none()
return queryset
def has_add_permission(self, request):
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
# to have the change permission for the related model in order to
# be able to do anything with the intermediate model.
return self.has_change_permission(request)
return self.has_change_permission(request, obj)
return super().has_add_permission(request)
def has_change_permission(self, request, obj=None):

View File

@ -41,6 +41,9 @@ details on these changes.
* ``django.contrib.staticfiles.templatetags.static()`` will be removed.
* The shim to allow ``InlineModelAdmin.has_add_permission()`` to be defined
without an ``obj`` argument will be removed.
.. _deprecation-removed-in-2.1:
2.1

View File

@ -2395,10 +2395,15 @@ The ``InlineModelAdmin`` class adds or customizes:
inline forms. For example, this may be based on the model instance
(passed as the keyword argument ``obj``).
.. method:: InlineModelAdmin.has_add_permission(request)
.. method:: InlineModelAdmin.has_add_permission(request, obj)
Should return ``True`` if adding an inline object is permitted, ``False``
otherwise.
otherwise. ``obj`` is the parent object being edited or ``None`` when
adding a new parent.
.. versionchanged:: 2.1
The ``obj`` argument was added.
.. method:: InlineModelAdmin.has_change_permission(request, obj=None)

View File

@ -64,6 +64,9 @@ Minor features
with ``change_list_object_tools.html`` and
``change_form_object_tools.html`` templates.
* :meth:`.InlineModelAdmin.has_add_permission` is now passed the parent object
as the second positional argument, ``obj``.
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -398,6 +401,10 @@ Miscellaneous
* ``django.contrib.staticfiles.templatetags.static()`` is deprecated in favor
of ``django.templatetags.static.static()``.
* Support for :meth:`.InlineModelAdmin.has_add_permission` methods that don't
accept ``obj`` as the second positional argument will be removed in Django
3.0.
.. _removed-features-2.1:
Features removed in 2.1

View File

@ -0,0 +1,63 @@
import warnings
from django.contrib.admin.options import ModelAdmin, TabularInline
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]
with warnings.catch_warnings(record=True) as recorded:
warnings.simplefilter('always')
self.assertIsValid(BandAdmin, Band)
self.assertEqual(len(recorded), 1)
self.assertIs(recorded[0].category, RemovedInDjango30Warning)
self.assertEqual(str(recorded[0].message), (
"Update SongInlineAdmin.has_add_permission() to accept a "
"positional `obj` argument."
))

View File

@ -709,6 +709,25 @@ class ModelAdminPermissionTests(SimpleTestCase):
request.user = self.MockDeleteUser()
self.assertFalse(ma.has_add_permission(request))
def test_inline_has_add_permission_uses_obj(self):
class ConcertInline(TabularInline):
model = Concert
def has_add_permission(self, request, obj):
return bool(obj)
class BandAdmin(ModelAdmin):
inlines = [ConcertInline]
ma = BandAdmin(Band, AdminSite())
request = MockRequest()
request.user = self.MockAddUser()
self.assertEqual(ma.get_inline_instances(request), [])
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