1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

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 itertools import chain
from django.apps import apps from django.apps import apps
@ -13,6 +14,8 @@ from django.forms.models import (
BaseModelForm, BaseModelFormSet, _get_foreign_key, BaseModelForm, BaseModelFormSet, _get_foreign_key,
) )
from django.template.engine import Engine 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): def check_admin_app(app_configs, **kwargs):
@ -884,6 +887,7 @@ class ModelAdminChecks(BaseModelAdminChecks):
class InlineModelAdminChecks(BaseModelAdminChecks): class InlineModelAdminChecks(BaseModelAdminChecks):
def check(self, inline_obj, **kwargs): def check(self, inline_obj, **kwargs):
self._check_has_add_permission(inline_obj)
parent_model = inline_obj.parent_model parent_model = inline_obj.parent_model
return [ return [
*super().check(inline_obj), *super().check(inline_obj),
@ -968,6 +972,20 @@ class InlineModelAdminChecks(BaseModelAdminChecks):
else: else:
return [] 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): def must_be(type, option, obj, id):
return [ return [

View File

@ -44,6 +44,7 @@ from django.urls import reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.http import urlencode from django.utils.http import urlencode
from django.utils.inspect import get_func_args
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import capfirst, format_lazy, get_text_list from django.utils.text import capfirst, format_lazy, get_text_list
from django.utils.translation import gettext as _, ngettext from django.utils.translation import gettext as _, ngettext
@ -559,12 +560,18 @@ class ModelAdmin(BaseModelAdmin):
inline_instances = [] inline_instances = []
for inline_class in self.inlines: for inline_class in self.inlines:
inline = inline_class(self.model, self.admin_site) 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 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_change_permission(request, obj) or
inline.has_delete_permission(request, obj)): inline.has_delete_permission(request, obj)):
continue continue
if not inline.has_add_permission(request): if not inline_has_add_permission:
inline.max_num = 0 inline.max_num = 0
inline_instances.append(inline) inline_instances.append(inline)
@ -2007,13 +2014,13 @@ class InlineModelAdmin(BaseModelAdmin):
queryset = queryset.none() queryset = queryset.none()
return queryset return queryset
def has_add_permission(self, request): def has_add_permission(self, request, obj):
if self.opts.auto_created: if self.opts.auto_created:
# We're checking the rights to an auto-created intermediate model, # We're checking the rights to an auto-created intermediate model,
# which doesn't have its own individual permissions. The user needs # which doesn't have its own individual permissions. The user needs
# to have the change permission for the related model in order to # to have the change permission for the related model in order to
# be able to do anything with the intermediate model. # 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) return super().has_add_permission(request)
def has_change_permission(self, request, obj=None): 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. * ``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: .. _deprecation-removed-in-2.1:
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 inline forms. For example, this may be based on the model instance
(passed as the keyword argument ``obj``). (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`` 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) .. method:: InlineModelAdmin.has_change_permission(request, obj=None)

View File

@ -64,6 +64,9 @@ Minor features
with ``change_list_object_tools.html`` and with ``change_list_object_tools.html`` and
``change_form_object_tools.html`` templates. ``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` :mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -398,6 +401,10 @@ Miscellaneous
* ``django.contrib.staticfiles.templatetags.static()`` is deprecated in favor * ``django.contrib.staticfiles.templatetags.static()`` is deprecated in favor
of ``django.templatetags.static.static()``. 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: .. _removed-features-2.1:
Features removed in 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() request.user = self.MockDeleteUser()
self.assertFalse(ma.has_add_permission(request)) 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): def test_has_change_permission(self):
""" """
has_change_permission returns True for users who can edit objects and has_change_permission returns True for users who can edit objects and