Fixed #6327 -- Added has_module_permission method to BaseModelAdmin

Thanks chrj for the suggestion.
This commit is contained in:
Maxime Turcotte 2014-05-28 12:07:27 -04:00 committed by Tim Graham
parent bf743a4d57
commit 504c89e800
9 changed files with 201 additions and 7 deletions

View File

@ -473,6 +473,19 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
codename = get_permission_codename('delete', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_module_permission(self, request):
"""
Returns True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does not restrict access to the add, change or delete views. Use
`ModelAdmin.has_(add|change|delete)_permission` for that.
"""
return request.user.has_module_perms(self.opts.app_label)
@python_2_unicode_compatible
class ModelAdmin(BaseModelAdmin):

View File

@ -367,10 +367,9 @@ class AdminSite(object):
apps that have been registered in this site.
"""
app_dict = {}
user = request.user
for model, model_admin in self._registry.items():
app_label = model._meta.app_label
has_module_perms = user.has_module_perms(app_label)
has_module_perms = model_admin.has_module_permission(request)
if has_module_perms:
perms = model_admin.get_model_perms(request)
@ -424,14 +423,14 @@ class AdminSite(object):
current_app=self.name)
def app_index(self, request, app_label, extra_context=None):
user = request.user
app_name = apps.get_app_config(app_label).verbose_name
has_module_perms = user.has_module_perms(app_label)
if not has_module_perms:
raise PermissionDenied
app_dict = {}
for model, model_admin in self._registry.items():
if app_label == model._meta.app_label:
has_module_perms = model_admin.has_module_permission(request)
if not has_module_perms:
raise PermissionDenied
perms = model_admin.get_model_perms(request)
# Check whether user has any perm for this module.

View File

@ -1631,6 +1631,19 @@ templates used by the :class:`ModelAdmin` views:
be interpreted as meaning that the current user is not permitted to delete
any object of this type).
.. method:: ModelAdmin.has_module_permission(request)
.. versionadded:: 1.8
Should return ``True`` if displaying the module on the admin index page and
accessing the module's index page is permitted, ``False`` otherwise.
Uses :meth:`User.has_module_perms()
<django.contrib.auth.models.User.has_module_perms>` by default. Overriding
it does not restrict access to the add, change or delete views,
:meth:`~ModelAdmin.has_add_permission`,
:meth:`~ModelAdmin.has_change_permission`, and
:meth:`~ModelAdmin.has_delete_permission` should be used for that.
.. method:: ModelAdmin.get_queryset(request)
The ``get_queryset`` method on a ``ModelAdmin`` returns a
@ -1909,6 +1922,7 @@ adds some of its own (the shared features are actually defined in the
- :meth:`~ModelAdmin.has_add_permission`
- :meth:`~ModelAdmin.has_change_permission`
- :meth:`~ModelAdmin.has_delete_permission`
- :meth:`~ModelAdmin.has_module_permission`
The ``InlineModelAdmin`` class adds:

View File

@ -31,7 +31,9 @@ Minor features
:mod:`django.contrib.admin`
^^^^^^^^^^^^^^^^^^^^^^^^^^^
* ...
* :class:`~django.contrib.admin.ModelAdmin` now has a
:meth:`~django.contrib.admin.ModelAdmin.has_module_permission`
method to allow limiting access to the module on the admin index page.
:mod:`django.contrib.auth`
^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -17,6 +17,9 @@ class MockSuperUser(object):
def has_perm(self, perm):
return True
def has_module_perms(self, module):
return True
request = MockRequest()
request.user = MockSuperUser()

View File

@ -124,6 +124,12 @@ class ArticleAdmin(admin.ModelAdmin):
return super(ArticleAdmin, self).save_model(request, obj, form, change)
class ArticleAdmin2(admin.ModelAdmin):
def has_module_permission(self, request):
return False
class RowLevelChangePermissionModelAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None):
""" Only allow changing objects with even id number """
@ -923,3 +929,5 @@ site.register(Group, GroupAdmin)
site2 = admin.AdminSite(name="namespaced_admin")
site2.register(User, UserAdmin)
site2.register(Group, GroupAdmin)
site7 = admin.AdminSite(name="admin7")
site7.register(Article, ArticleAdmin2)

View File

@ -1493,6 +1493,70 @@ class AdminViewPermissionsTest(TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, 'http://example.com/dummy/foo/')
def test_has_module_permission(self):
"""
Ensure that has_module_permission() returns True for all users who
have any permission for that module (add, change, or delete), so that
the module is displayed on the admin index page.
"""
login_url = reverse('admin:login') + '?next=/test_admin/admin/'
self.client.post(login_url, self.super_login)
response = self.client.get('/test_admin/admin/')
self.assertContains(response, 'admin_views')
self.assertContains(response, 'Articles')
self.client.get('/test_admin/admin/logout/')
self.client.post(login_url, self.adduser_login)
response = self.client.get('/test_admin/admin/')
self.assertContains(response, 'admin_views')
self.assertContains(response, 'Articles')
self.client.get('/test_admin/admin/logout/')
self.client.post(login_url, self.changeuser_login)
response = self.client.get('/test_admin/admin/')
self.assertContains(response, 'admin_views')
self.assertContains(response, 'Articles')
self.client.get('/test_admin/admin/logout/')
self.client.post(login_url, self.deleteuser_login)
response = self.client.get('/test_admin/admin/')
self.assertContains(response, 'admin_views')
self.assertContains(response, 'Articles')
self.client.get('/test_admin/admin/logout/')
def test_overriding_has_module_permission(self):
"""
Ensure that overriding has_module_permission() has the desired effect.
In this case, it always returns False, so the module should not be
displayed on the admin index page for any users.
"""
login_url = reverse('admin:login') + '?next=/test_admin/admin7/'
self.client.post(login_url, self.super_login)
response = self.client.get('/test_admin/admin7/')
self.assertNotContains(response, 'admin_views')
self.assertNotContains(response, 'Articles')
self.client.get('/test_admin/admin7/logout/')
self.client.post(login_url, self.adduser_login)
response = self.client.get('/test_admin/admin7/')
self.assertNotContains(response, 'admin_views')
self.assertNotContains(response, 'Articles')
self.client.get('/test_admin/admin7/logout/')
self.client.post(login_url, self.changeuser_login)
response = self.client.get('/test_admin/admin7/')
self.assertNotContains(response, 'admin_views')
self.assertNotContains(response, 'Articles')
self.client.get('/test_admin/admin7/logout/')
self.client.post(login_url, self.deleteuser_login)
response = self.client.get('/test_admin/admin7/')
self.assertNotContains(response, 'admin_views')
self.assertNotContains(response, 'Articles')
self.client.get('/test_admin/admin7/logout/')
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
ROOT_URLCONF="admin_views.urls")

View File

@ -11,4 +11,5 @@ urlpatterns = [
url(r'^test_admin/admin3/', include(admin.site.urls), dict(form_url='pony')),
url(r'^test_admin/admin4/', include(customadmin.simple_site.urls)),
url(r'^test_admin/admin5/', include(admin.site2.urls)),
url(r'^test_admin/admin7/', include(admin.site7.urls)),
]

View File

@ -1542,3 +1542,93 @@ class ListDisplayEditableTests(CheckTestCase):
list_editable = ['name', 'slug']
list_display_links = ['pub_date']
self.assertIsValid(ProductAdmin, ValidationTestModel)
class ModelAdminPermissionTests(TestCase):
class MockUser(object):
def has_module_perms(self, app_label):
if app_label == "modeladmin":
return True
return False
class MockAddUser(MockUser):
def has_perm(self, perm):
if perm == "modeladmin.add_band":
return True
return False
class MockChangeUser(MockUser):
def has_perm(self, perm):
if perm == "modeladmin.change_band":
return True
return False
class MockDeleteUser(MockUser):
def has_perm(self, perm):
if perm == "modeladmin.delete_band":
return True
return False
def test_has_add_permission(self):
"""
Ensure that has_add_permission returns True for users who can add
objects and False for users who can't.
"""
ma = ModelAdmin(Band, AdminSite())
request = MockRequest()
request.user = self.MockAddUser()
self.assertTrue(ma.has_add_permission(request))
request.user = self.MockChangeUser()
self.assertFalse(ma.has_add_permission(request))
request.user = self.MockDeleteUser()
self.assertFalse(ma.has_add_permission(request))
def test_has_change_permission(self):
"""
Ensure that has_change_permission returns True for users who can edit
objects and False for users who can't.
"""
ma = ModelAdmin(Band, AdminSite())
request = MockRequest()
request.user = self.MockAddUser()
self.assertFalse(ma.has_change_permission(request))
request.user = self.MockChangeUser()
self.assertTrue(ma.has_change_permission(request))
request.user = self.MockDeleteUser()
self.assertFalse(ma.has_change_permission(request))
def test_has_delete_permission(self):
"""
Ensure that has_delete_permission returns True for users who can delete
objects and False for users who can't.
"""
ma = ModelAdmin(Band, AdminSite())
request = MockRequest()
request.user = self.MockAddUser()
self.assertFalse(ma.has_delete_permission(request))
request.user = self.MockChangeUser()
self.assertFalse(ma.has_delete_permission(request))
request.user = self.MockDeleteUser()
self.assertTrue(ma.has_delete_permission(request))
def test_has_module_permission(self):
"""
Ensure that has_module_permission returns True for users who have any
permission for the module and False for users who don't.
"""
ma = ModelAdmin(Band, AdminSite())
request = MockRequest()
request.user = self.MockAddUser()
self.assertTrue(ma.has_module_permission(request))
request.user = self.MockChangeUser()
self.assertTrue(ma.has_module_permission(request))
request.user = self.MockDeleteUser()
self.assertTrue(ma.has_module_permission(request))
ma.opts.app_label = "anotherapp"
request.user = self.MockAddUser()
self.assertFalse(ma.has_module_permission(request))
request.user = self.MockChangeUser()
self.assertFalse(ma.has_module_permission(request))
request.user = self.MockDeleteUser()
self.assertFalse(ma.has_module_permission(request))