mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-17 06:30:25 +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}'),)
|
||||
|
||||
|
||||
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):
|
||||
'''
|
||||
Can be used in the template like::
|
||||
Can be used in the template like:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{{ permissions.has_view_permission }}
|
||||
{{ permissions.has_add_permission }}
|
||||
{{ permissions.has_change_permission }}
|
||||
{{ permissions.has_delete_permission|for_object:object }}
|
||||
{{ permissions.has_delete_permission }}
|
||||
{{ permissions.blog_post.has_view_permission }}
|
||||
{{ permissions.blog_comment.has_add_permission }}
|
||||
|
||||
So in general::
|
||||
So in general:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{{ permissions.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
|
||||
dictionary lookup (implemented in ``__getitem__``). This will return a
|
||||
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:
|
||||
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': 'detail_view',
|
||||
|
|
@ -219,38 +211,79 @@ class TemplatePermissionChecker(object):
|
|||
'delete': 'delete_view',
|
||||
}
|
||||
|
||||
def __init__(self, request, view, model_admin=None):
|
||||
self.request = request
|
||||
self.view = view
|
||||
self.model_admin = model_admin
|
||||
def __init__(self, request, model_admin, view=None, obj=None):
|
||||
self._request = request
|
||||
self._model_admin = model_admin
|
||||
self._view = view
|
||||
self._obj = obj
|
||||
|
||||
def get_template_permission_object(self, view_name):
|
||||
if self.model_admin is None:
|
||||
model_admin = self.view.model_admin
|
||||
else:
|
||||
model_admin = self.model_admin
|
||||
def clone(self):
|
||||
return self.__class__(
|
||||
request=self._request,
|
||||
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 = view_class(
|
||||
request=self.request,
|
||||
request=self._request,
|
||||
**model_admin.get_default_view_kwargs())
|
||||
return TemplatePermission(view)
|
||||
return view
|
||||
|
||||
#########################################
|
||||
# interface exposed to the template users
|
||||
|
||||
def __getitem__(self, key):
|
||||
match = self.has_named_permission_regex.match(key)
|
||||
match = self._has_named_permission_regex.match(key)
|
||||
if match:
|
||||
# the key was a has_*_permission, so get the *has permission
|
||||
# wrapper*
|
||||
view_name = match.groupdict()['name']
|
||||
if view_name not in self.view_name_mapping:
|
||||
raise KeyError
|
||||
view_name = self.view_name_mapping[view_name]
|
||||
return self.get_template_permission_object(view_name)
|
||||
view = self.get_view_by_name(view_name)
|
||||
return self.bind_view(view)
|
||||
# the name might be a named object admin. So get that one and try to
|
||||
# check the permission there for further traversal
|
||||
try:
|
||||
admin_site = self.view.model_admin.admin
|
||||
admin_site = self._model_admin.admin
|
||||
model_admin = admin_site.get_admin_by_name(key)
|
||||
except ValueError:
|
||||
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
|
||||
def for_object(callable, obj):
|
||||
def for_object(permissions, obj):
|
||||
"""
|
||||
Applies the provided argument ``obj`` to the piped value. Example::
|
||||
|
||||
{{ view.has_permission|for_object:object }}
|
||||
|
||||
Translates roughly into the following python code::
|
||||
|
||||
context['view'].has_permission(context['object'])
|
||||
Only useful in the permission handling. This filter binds a new object to
|
||||
the permission handler to check for object-level permissions.
|
||||
"""
|
||||
if callable == '':
|
||||
return callable
|
||||
return callable(obj)
|
||||
# some permission check has failed earlier, so we don't bother trying to
|
||||
# bind a new object to it.
|
||||
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):
|
||||
context = super(PermissionMixin, self).get_context_data(**kwargs)
|
||||
permission_checker = permissions.TemplatePermissionChecker(
|
||||
self.request,
|
||||
self)
|
||||
self.request, self.model_admin)
|
||||
context.update({
|
||||
'permissions': permission_checker,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,10 +29,7 @@ class TemplatePermissionTest(TestCase):
|
|||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||
request.user = self.user
|
||||
view = model_admin.index_view(
|
||||
request=request,
|
||||
model_admin=model_admin)
|
||||
permissions = TemplatePermissionChecker(request, view)
|
||||
permissions = TemplatePermissionChecker(request, model_admin)
|
||||
context = {
|
||||
'permissions': permissions,
|
||||
}
|
||||
|
|
@ -54,7 +51,6 @@ class TemplatePermissionTest(TestCase):
|
|||
if hasattr(self.user, '_perm_cache'):
|
||||
del self.user._perm_cache
|
||||
|
||||
permissions['has_add_permission']()
|
||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||
self.assertEqual(result, 'True')
|
||||
|
||||
|
|
@ -68,10 +64,7 @@ class TemplatePermissionTest(TestCase):
|
|||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||
request.user = self.user
|
||||
view = model_admin.index_view(
|
||||
request=request,
|
||||
model_admin=model_admin)
|
||||
permissions = TemplatePermissionChecker(request, view)
|
||||
permissions = TemplatePermissionChecker(request, model_admin)
|
||||
context = {
|
||||
'permissions': permissions,
|
||||
}
|
||||
|
|
@ -95,14 +88,59 @@ class TemplatePermissionTest(TestCase):
|
|||
context)
|
||||
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):
|
||||
model_admin = ModelAdmin2(Post, djadmin2.default)
|
||||
request = self.factory.get(reverse('admin2:blog_post_index'))
|
||||
request.user = self.user
|
||||
view = model_admin.index_view(
|
||||
request=request,
|
||||
model_admin=model_admin)
|
||||
permissions = TemplatePermissionChecker(request, view)
|
||||
permissions = TemplatePermissionChecker(request, model_admin)
|
||||
|
||||
post = Post.objects.create(title='Hello', body='world')
|
||||
context = {
|
||||
|
|
@ -133,9 +171,19 @@ class TemplatePermissionTest(TestCase):
|
|||
|
||||
# object level permission are not supported by default. So this will
|
||||
# return ``False``.
|
||||
permissions['has_add_permission']()
|
||||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
'{{ permissions.has_add_permission }}'
|
||||
'{{ permissions.has_add_permission|for_object:post }}',
|
||||
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