mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-05-17 11:41:12 +00:00
Adding possibility to swap model admin that is checked for permissions in the template.
This commit is contained in:
parent
c6a11efca8
commit
2aca9df7e2
4 changed files with 160 additions and 70 deletions
|
|
@ -161,44 +161,36 @@ class ModelDeletePermission(BasePermission):
|
||||||
permissions = (model_permission('{app_label}.delete_{model_name}'),)
|
permissions = (model_permission('{app_label}.delete_{model_name}'),)
|
||||||
|
|
||||||
|
|
||||||
class TemplatePermission(object):
|
|
||||||
'''
|
|
||||||
A small wrapper around the permission check of a specific view. This is
|
|
||||||
used in the template since we don't know if the permission will be used
|
|
||||||
as a boolean expression or if the user will append a ``for_object`` filter
|
|
||||||
to test for object level permission.
|
|
||||||
'''
|
|
||||||
do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def __init__(self, view):
|
|
||||||
self._view = view
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
return self._view.has_permission()
|
|
||||||
|
|
||||||
def __call__(self, obj=None):
|
|
||||||
return self._view.has_permission(obj)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return unicode(bool(self))
|
|
||||||
|
|
||||||
|
|
||||||
class TemplatePermissionChecker(object):
|
class TemplatePermissionChecker(object):
|
||||||
'''
|
'''
|
||||||
Can be used in the template like::
|
Can be used in the template like:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
{{ permissions.has_view_permission }}
|
{{ permissions.has_view_permission }}
|
||||||
{{ permissions.has_add_permission }}
|
{{ permissions.has_add_permission }}
|
||||||
{{ permissions.has_change_permission }}
|
{{ permissions.has_change_permission }}
|
||||||
{{ permissions.has_delete_permission|for_object:object }}
|
{{ permissions.has_delete_permission }}
|
||||||
{{ permissions.blog_post.has_view_permission }}
|
{{ permissions.blog_post.has_view_permission }}
|
||||||
{{ permissions.blog_comment.has_add_permission }}
|
{{ permissions.blog_comment.has_add_permission }}
|
||||||
|
|
||||||
So in general::
|
So in general:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
{{ permissions.has_<view_name>_permission }}
|
{{ permissions.has_<view_name>_permission }}
|
||||||
{{ permissions.<object admin name>.has_<view name>_permission }}
|
{{ permissions.<object admin name>.has_<view name>_permission }}
|
||||||
|
|
||||||
|
And using object-level permissions:
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
{% load admin2_tags %}
|
||||||
|
{{ permissions.has_delete_permission|for_object:object }}
|
||||||
|
{% with permissions|for_object:object as object_permissions %}
|
||||||
|
{{ object_permissions.has_delete_permission }}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
The attribute access of ``has_create_permission`` will be done via a
|
The attribute access of ``has_create_permission`` will be done via a
|
||||||
dictionary lookup (implemented in ``__getitem__``). This will return a
|
dictionary lookup (implemented in ``__getitem__``). This will return a
|
||||||
callable (instance of ``TemplatePermission``, that can take an object to
|
callable (instance of ``TemplatePermission``, that can take an object to
|
||||||
|
|
@ -210,7 +202,7 @@ class TemplatePermissionChecker(object):
|
||||||
needs an interface beeing implemented like suggested in:
|
needs an interface beeing implemented like suggested in:
|
||||||
https://github.com/twoscoops/django-admin2/issues/142
|
https://github.com/twoscoops/django-admin2/issues/142
|
||||||
'''
|
'''
|
||||||
has_named_permission_regex = re.compile('^has_(?P<name>\w+)_permission$')
|
_has_named_permission_regex = re.compile('^has_(?P<name>\w+)_permission$')
|
||||||
|
|
||||||
view_name_mapping = {
|
view_name_mapping = {
|
||||||
'view': 'detail_view',
|
'view': 'detail_view',
|
||||||
|
|
@ -219,38 +211,79 @@ class TemplatePermissionChecker(object):
|
||||||
'delete': 'delete_view',
|
'delete': 'delete_view',
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, request, view, model_admin=None):
|
def __init__(self, request, model_admin, view=None, obj=None):
|
||||||
self.request = request
|
self._request = request
|
||||||
self.view = view
|
self._model_admin = model_admin
|
||||||
self.model_admin = model_admin
|
self._view = view
|
||||||
|
self._obj = obj
|
||||||
|
|
||||||
def get_template_permission_object(self, view_name):
|
def clone(self):
|
||||||
if self.model_admin is None:
|
return self.__class__(
|
||||||
model_admin = self.view.model_admin
|
request=self._request,
|
||||||
else:
|
model_admin=self._model_admin,
|
||||||
model_admin = self.model_admin
|
view=self._view,
|
||||||
|
obj=self._obj)
|
||||||
|
|
||||||
|
def bind_admin(self, admin):
|
||||||
|
'''
|
||||||
|
Return a clone of the permission wrapper with a new model_admin bind
|
||||||
|
to it.
|
||||||
|
'''
|
||||||
|
new_permissions = self.clone()
|
||||||
|
new_permissions._model_admin = admin
|
||||||
|
return new_permissions
|
||||||
|
|
||||||
|
def bind_object(self, obj):
|
||||||
|
'''
|
||||||
|
Return a clone of the permission wrapper with a new object bind
|
||||||
|
to it for object-level permissions.
|
||||||
|
'''
|
||||||
|
new_permissions = self.clone()
|
||||||
|
new_permissions._obj = obj
|
||||||
|
return new_permissions
|
||||||
|
|
||||||
|
def get_view_by_name(self, view_name):
|
||||||
|
view_name = self.view_name_mapping[view_name]
|
||||||
|
model_admin = self._model_admin
|
||||||
view_class = getattr(model_admin, view_name)
|
view_class = getattr(model_admin, view_name)
|
||||||
view = view_class(
|
view = view_class(
|
||||||
request=self.request,
|
request=self._request,
|
||||||
**model_admin.get_default_view_kwargs())
|
**model_admin.get_default_view_kwargs())
|
||||||
return TemplatePermission(view)
|
return view
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
# interface exposed to the template users
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
match = self.has_named_permission_regex.match(key)
|
match = self._has_named_permission_regex.match(key)
|
||||||
if match:
|
if match:
|
||||||
# the key was a has_*_permission, so get the *has permission
|
# the key was a has_*_permission, so get the *has permission
|
||||||
# wrapper*
|
# wrapper*
|
||||||
view_name = match.groupdict()['name']
|
view_name = match.groupdict()['name']
|
||||||
if view_name not in self.view_name_mapping:
|
if view_name not in self.view_name_mapping:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
view_name = self.view_name_mapping[view_name]
|
view = self.get_view_by_name(view_name)
|
||||||
return self.get_template_permission_object(view_name)
|
return self.bind_view(view)
|
||||||
# the name might be a named object admin. So get that one and try to
|
# the name might be a named object admin. So get that one and try to
|
||||||
# check the permission there for further traversal
|
# check the permission there for further traversal
|
||||||
try:
|
try:
|
||||||
admin_site = self.view.model_admin.admin
|
admin_site = self._model_admin.admin
|
||||||
model_admin = admin_site.get_admin_by_name(key)
|
model_admin = admin_site.get_admin_by_name(key)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise KeyError
|
raise KeyError
|
||||||
return self.__class__(self.request, self.view, model_admin)
|
return self.bind_admin(model_admin)
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
# if no view is bound we will return false, since we don't know which
|
||||||
|
# permission to check we stay save in disallowing the access
|
||||||
|
if self._view is None:
|
||||||
|
return False
|
||||||
|
if self._obj is None:
|
||||||
|
return self._view.has_permission()
|
||||||
|
else:
|
||||||
|
return self._view.has_permission(self._obj)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
if self._view is None:
|
||||||
|
return ''
|
||||||
|
return unicode(bool(self))
|
||||||
|
|
|
||||||
|
|
@ -46,16 +46,26 @@ def formset_visible_fieldlist(formset):
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def for_object(callable, obj):
|
def for_object(permissions, obj):
|
||||||
"""
|
"""
|
||||||
Applies the provided argument ``obj`` to the piped value. Example::
|
Only useful in the permission handling. This filter binds a new object to
|
||||||
|
the permission handler to check for object-level permissions.
|
||||||
{{ view.has_permission|for_object:object }}
|
|
||||||
|
|
||||||
Translates roughly into the following python code::
|
|
||||||
|
|
||||||
context['view'].has_permission(context['object'])
|
|
||||||
"""
|
"""
|
||||||
if callable == '':
|
# some permission check has failed earlier, so we don't bother trying to
|
||||||
return callable
|
# bind a new object to it.
|
||||||
return callable(obj)
|
if permissions == '':
|
||||||
|
return permissions
|
||||||
|
return permissions.bind_object(obj)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def for_admin(permissions, admin):
|
||||||
|
"""
|
||||||
|
Only useful in the permission handling. This filter binds a new admin to
|
||||||
|
the permission handler to allow checking views of an arbitrary admin.
|
||||||
|
"""
|
||||||
|
# some permission check has failed earlier, so we don't bother trying to
|
||||||
|
# bind a new admin to it.
|
||||||
|
if permissions == '':
|
||||||
|
return permissions
|
||||||
|
return permissions.bind_admin(admin)
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,7 @@ class PermissionMixin(object):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(PermissionMixin, self).get_context_data(**kwargs)
|
context = super(PermissionMixin, self).get_context_data(**kwargs)
|
||||||
permission_checker = permissions.TemplatePermissionChecker(
|
permission_checker = permissions.TemplatePermissionChecker(
|
||||||
self.request,
|
self.request, self.model_admin)
|
||||||
self)
|
|
||||||
context.update({
|
context.update({
|
||||||
'permissions': permission_checker,
|
'permissions': permission_checker,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
model_admin = ModelAdmin2(Post, djadmin2.default)
|
||||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
view = model_admin.index_view(
|
permissions = TemplatePermissionChecker(request, model_admin)
|
||||||
request=request,
|
|
||||||
model_admin=model_admin)
|
|
||||||
permissions = TemplatePermissionChecker(request, view)
|
|
||||||
context = {
|
context = {
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +51,6 @@ class TemplatePermissionTest(TestCase):
|
||||||
if hasattr(self.user, '_perm_cache'):
|
if hasattr(self.user, '_perm_cache'):
|
||||||
del self.user._perm_cache
|
del self.user._perm_cache
|
||||||
|
|
||||||
permissions['has_add_permission']()
|
|
||||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||||
self.assertEqual(result, 'True')
|
self.assertEqual(result, 'True')
|
||||||
|
|
||||||
|
|
@ -68,10 +64,7 @@ class TemplatePermissionTest(TestCase):
|
||||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
model_admin = ModelAdmin2(Post, djadmin2.default)
|
||||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
view = model_admin.index_view(
|
permissions = TemplatePermissionChecker(request, model_admin)
|
||||||
request=request,
|
|
||||||
model_admin=model_admin)
|
|
||||||
permissions = TemplatePermissionChecker(request, view)
|
|
||||||
context = {
|
context = {
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
}
|
}
|
||||||
|
|
@ -95,14 +88,59 @@ class TemplatePermissionTest(TestCase):
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, '')
|
self.assertEqual(result, '')
|
||||||
|
|
||||||
|
def test_admin_binding(self):
|
||||||
|
user_admin = djadmin2.default.get_admin_by_name('auth_user')
|
||||||
|
post_admin = djadmin2.default.get_admin_by_name('blog_post')
|
||||||
|
request = self.factory.get(reverse('admin2:auth_user_index'))
|
||||||
|
request.user = self.user
|
||||||
|
permissions = TemplatePermissionChecker(request, user_admin)
|
||||||
|
|
||||||
|
post = Post.objects.create(title='Hello', body='world')
|
||||||
|
context = {
|
||||||
|
'post': post,
|
||||||
|
'post_admin': post_admin,
|
||||||
|
'permissions': permissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self.render(
|
||||||
|
'{% load admin2_tags %}'
|
||||||
|
'{{ permissions|for_admin:post_admin }}',
|
||||||
|
context)
|
||||||
|
self.assertEqual(result, '')
|
||||||
|
|
||||||
|
result = self.render(
|
||||||
|
'{% load admin2_tags %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
|
'{% with permissions|for_admin:post_admin as permissions %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
|
'{% endwith %}',
|
||||||
|
context)
|
||||||
|
self.assertEqual(result, 'FalseFalse')
|
||||||
|
|
||||||
|
post_add_permission = Permission.objects.get(
|
||||||
|
content_type__app_label='blog',
|
||||||
|
content_type__model='post',
|
||||||
|
codename='add_post')
|
||||||
|
self.user.user_permissions.add(post_add_permission)
|
||||||
|
# invalidate the users permission cache
|
||||||
|
if hasattr(self.user, '_perm_cache'):
|
||||||
|
del self.user._perm_cache
|
||||||
|
|
||||||
|
result = self.render(
|
||||||
|
'{% load admin2_tags %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
|
'{% with permissions|for_admin:post_admin as permissions %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
|
'{% endwith %}'
|
||||||
|
'{{ permissions.blog_post.has_add_permission }}',
|
||||||
|
context)
|
||||||
|
self.assertEqual(result, 'FalseTrueTrue')
|
||||||
|
|
||||||
def test_object_level_permission(self):
|
def test_object_level_permission(self):
|
||||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
model_admin = ModelAdmin2(Post, djadmin2.default)
|
||||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
view = model_admin.index_view(
|
permissions = TemplatePermissionChecker(request, model_admin)
|
||||||
request=request,
|
|
||||||
model_admin=model_admin)
|
|
||||||
permissions = TemplatePermissionChecker(request, view)
|
|
||||||
|
|
||||||
post = Post.objects.create(title='Hello', body='world')
|
post = Post.objects.create(title='Hello', body='world')
|
||||||
context = {
|
context = {
|
||||||
|
|
@ -133,9 +171,19 @@ class TemplatePermissionTest(TestCase):
|
||||||
|
|
||||||
# object level permission are not supported by default. So this will
|
# object level permission are not supported by default. So this will
|
||||||
# return ``False``.
|
# return ``False``.
|
||||||
permissions['has_add_permission']()
|
|
||||||
result = self.render(
|
result = self.render(
|
||||||
'{% load admin2_tags %}'
|
'{% load admin2_tags %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
'{{ permissions.has_add_permission|for_object:post }}',
|
'{{ permissions.has_add_permission|for_object:post }}',
|
||||||
context)
|
context)
|
||||||
self.assertEqual(result, 'False')
|
self.assertEqual(result, 'TrueFalse')
|
||||||
|
|
||||||
|
# binding an object and then checking for a specific view also works
|
||||||
|
result = self.render(
|
||||||
|
'{% load admin2_tags %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
|
'{% with permissions|for_object:post as permissions %}'
|
||||||
|
'{{ permissions.has_add_permission }}'
|
||||||
|
'{% endwith %}',
|
||||||
|
context)
|
||||||
|
self.assertEqual(result, 'TrueFalse')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue