Kaydet (Commit) 504c89e8 authored tarafından Maxime Turcotte's avatar Maxime Turcotte Kaydeden (comit) Tim Graham

Fixed #6327 -- Added has_module_permission method to BaseModelAdmin

Thanks chrj for the suggestion.
üst bf743a4d
...@@ -473,6 +473,19 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): ...@@ -473,6 +473,19 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
codename = get_permission_codename('delete', opts) codename = get_permission_codename('delete', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename)) 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 @python_2_unicode_compatible
class ModelAdmin(BaseModelAdmin): class ModelAdmin(BaseModelAdmin):
......
...@@ -367,10 +367,9 @@ class AdminSite(object): ...@@ -367,10 +367,9 @@ class AdminSite(object):
apps that have been registered in this site. apps that have been registered in this site.
""" """
app_dict = {} app_dict = {}
user = request.user
for model, model_admin in self._registry.items(): for model, model_admin in self._registry.items():
app_label = model._meta.app_label 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: if has_module_perms:
perms = model_admin.get_model_perms(request) perms = model_admin.get_model_perms(request)
...@@ -424,14 +423,14 @@ class AdminSite(object): ...@@ -424,14 +423,14 @@ class AdminSite(object):
current_app=self.name) current_app=self.name)
def app_index(self, request, app_label, extra_context=None): def app_index(self, request, app_label, extra_context=None):
user = request.user
app_name = apps.get_app_config(app_label).verbose_name 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 = {} app_dict = {}
for model, model_admin in self._registry.items(): for model, model_admin in self._registry.items():
if app_label == model._meta.app_label: 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) perms = model_admin.get_model_perms(request)
# Check whether user has any perm for this module. # Check whether user has any perm for this module.
......
...@@ -1631,6 +1631,19 @@ templates used by the :class:`ModelAdmin` views: ...@@ -1631,6 +1631,19 @@ templates used by the :class:`ModelAdmin` views:
be interpreted as meaning that the current user is not permitted to delete be interpreted as meaning that the current user is not permitted to delete
any object of this type). 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) .. method:: ModelAdmin.get_queryset(request)
The ``get_queryset`` method on a ``ModelAdmin`` returns a 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 ...@@ -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_add_permission`
- :meth:`~ModelAdmin.has_change_permission` - :meth:`~ModelAdmin.has_change_permission`
- :meth:`~ModelAdmin.has_delete_permission` - :meth:`~ModelAdmin.has_delete_permission`
- :meth:`~ModelAdmin.has_module_permission`
The ``InlineModelAdmin`` class adds: The ``InlineModelAdmin`` class adds:
......
...@@ -31,7 +31,9 @@ Minor features ...@@ -31,7 +31,9 @@ Minor features
:mod:`django.contrib.admin` :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` :mod:`django.contrib.auth`
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
......
...@@ -17,6 +17,9 @@ class MockSuperUser(object): ...@@ -17,6 +17,9 @@ class MockSuperUser(object):
def has_perm(self, perm): def has_perm(self, perm):
return True return True
def has_module_perms(self, module):
return True
request = MockRequest() request = MockRequest()
request.user = MockSuperUser() request.user = MockSuperUser()
......
...@@ -124,6 +124,12 @@ class ArticleAdmin(admin.ModelAdmin): ...@@ -124,6 +124,12 @@ class ArticleAdmin(admin.ModelAdmin):
return super(ArticleAdmin, self).save_model(request, obj, form, change) 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): class RowLevelChangePermissionModelAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj=None): def has_change_permission(self, request, obj=None):
""" Only allow changing objects with even id number """ """ Only allow changing objects with even id number """
...@@ -923,3 +929,5 @@ site.register(Group, GroupAdmin) ...@@ -923,3 +929,5 @@ site.register(Group, GroupAdmin)
site2 = admin.AdminSite(name="namespaced_admin") site2 = admin.AdminSite(name="namespaced_admin")
site2.register(User, UserAdmin) site2.register(User, UserAdmin)
site2.register(Group, GroupAdmin) site2.register(Group, GroupAdmin)
site7 = admin.AdminSite(name="admin7")
site7.register(Article, ArticleAdmin2)
...@@ -1493,6 +1493,70 @@ class AdminViewPermissionsTest(TestCase): ...@@ -1493,6 +1493,70 @@ class AdminViewPermissionsTest(TestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, 'http://example.com/dummy/foo/') 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',), @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',),
ROOT_URLCONF="admin_views.urls") ROOT_URLCONF="admin_views.urls")
......
...@@ -11,4 +11,5 @@ urlpatterns = [ ...@@ -11,4 +11,5 @@ urlpatterns = [
url(r'^test_admin/admin3/', include(admin.site.urls), dict(form_url='pony')), 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/admin4/', include(customadmin.simple_site.urls)),
url(r'^test_admin/admin5/', include(admin.site2.urls)), url(r'^test_admin/admin5/', include(admin.site2.urls)),
url(r'^test_admin/admin7/', include(admin.site7.urls)),
] ]
...@@ -1542,3 +1542,93 @@ class ListDisplayEditableTests(CheckTestCase): ...@@ -1542,3 +1542,93 @@ class ListDisplayEditableTests(CheckTestCase):
list_editable = ['name', 'slug'] list_editable = ['name', 'slug']
list_display_links = ['pub_date'] list_display_links = ['pub_date']
self.assertIsValid(ProductAdmin, ValidationTestModel) 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))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment