mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-16 22:20:24 +00:00
Adding the ability to check for permissions in templates.
This commit is contained in:
parent
324cdbaa17
commit
ab778191c6
7 changed files with 203 additions and 14 deletions
|
|
@ -1,9 +1,8 @@
|
|||
from django.conf import settings
|
||||
|
||||
MODEL_ADMIN_ATTRS = (
|
||||
'list_display', 'list_display_links', 'list_filter',
|
||||
'admin', 'has_permission', 'has_add_permission',
|
||||
'has_edit_permission', 'has_delete_permission', 'get_actions'
|
||||
)
|
||||
'list_display', 'list_display_links', 'list_filter', 'admin',
|
||||
'index_view', 'detail_view', 'create_view', 'update_view', 'delete_view',
|
||||
'get_default_view_kwargs', 'get_actions')
|
||||
|
||||
ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "admin2/bootstrap")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ interface:
|
|||
The permission classes are then just fancy wrappers of these basic checks of
|
||||
which it can hold multiple.
|
||||
'''
|
||||
import re
|
||||
|
||||
|
||||
def is_authenticated(request, view, obj=None):
|
||||
|
|
@ -119,3 +120,62 @@ class ModelChangePermission(AdminPermission):
|
|||
|
||||
class ModelDeletePermission(AdminPermission):
|
||||
permissions = (model_permission('%(app_label)s.delete_%(model_name)s'),)
|
||||
|
||||
|
||||
class TemplatePermission(object):
|
||||
do_not_call_in_templates = True
|
||||
|
||||
def __init__(self, permission_check):
|
||||
self._permission_check = permission_check
|
||||
|
||||
def __nonzero__(self):
|
||||
return self._permission_check()
|
||||
|
||||
def __call__(self, obj=None):
|
||||
return self._permission_check(obj)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(bool(self))
|
||||
|
||||
|
||||
class TemplatePermissionChecker(object):
|
||||
'''
|
||||
Can be used in the template like::
|
||||
|
||||
{{ permissions.has_view_permission }}
|
||||
{{ permissions.has_add_permission }}
|
||||
{{ permissions.has_change_permission }}
|
||||
{{ permissions.has_delete_permission|for_object:object }}
|
||||
|
||||
The attribute access of ``has_create_permission`` will be done via a
|
||||
dictionary lookup (implemented in ``__getitem__``). This will return a
|
||||
callable that can be passed in an object to check object-level
|
||||
permissions.
|
||||
'''
|
||||
has_named_permission_regex = re.compile('^has_(?P<name>\w+)_permission$')
|
||||
|
||||
view_name_mapping = {
|
||||
'view': 'detail_view',
|
||||
'add': 'create_view',
|
||||
'change': 'update_view',
|
||||
'delete': 'delete_view',
|
||||
}
|
||||
|
||||
def __init__(self, request, view):
|
||||
self.request = request
|
||||
self.view = view
|
||||
|
||||
def get_permission_check(self, view_name):
|
||||
def permission_check(obj=None):
|
||||
return self.view.has_permission(obj, view_name=view_name)
|
||||
return permission_check
|
||||
|
||||
def __getitem__(self, key):
|
||||
match = self.has_named_permission_regex.match(key)
|
||||
if not match:
|
||||
raise KeyError
|
||||
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 TemplatePermission(self.get_permission_check(view_name))
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@
|
|||
<button type="Submit" class="btn">Go</button>
|
||||
<span id="selected-count">0</span> of {{ object_list|length }} selected
|
||||
<div class="pull-right">
|
||||
{# if has_add_permission #}
|
||||
<a href="{% url view|admin2_urlname:'create' %}" class="btn"><i class="icon-plus"></i> {% blocktrans with model_name=model_name %}Add {{ model_name }}{% endblocktrans %}</a>
|
||||
{# endif #}
|
||||
{% if permissions.has_add_permission %}
|
||||
<a href="{% url view|admin2_urlname:'create' %}" class="btn"><i class="icon-plus"></i> {% blocktrans with model_verbose_name=model|model_verbose_name %}Add {{ model_verbose_name }}{% endblocktrans %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -46,14 +46,13 @@
|
|||
<tr>
|
||||
<td><input type="checkbox" class="model-select" name="selected_model_pk" value="{{ obj.pk }}"></td>
|
||||
<td>
|
||||
{{ obj }}
|
||||
<a href="{% url view|admin2_urlname:'detail' pk=obj.pk %}">{% trans "Detail" %}</a>
|
||||
{# if has_edit_permission #}
|
||||
{{ obj }} <a href="{% url view|admin2_urlname:'detail' pk=obj.pk %}">{% trans "Detail" %}</a>
|
||||
{% if permissions.has_change_permission %}
|
||||
<a href="{% url view|admin2_urlname:'update' pk=obj.pk %}">{% trans "Edit" %}</a>
|
||||
{# endif #}
|
||||
{# if has_delete_permission #}
|
||||
{% endif %}
|
||||
{% if permissions.has_delete_permission %}
|
||||
<a href="{% url view|admin2_urlname:'delete' pk=obj.pk %}">{% trans "Delete" %}</a>
|
||||
{# endif #}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -43,3 +43,19 @@ def formset_visible_fieldlist(formset):
|
|||
Returns the labels of a formset's visible fields as an array.
|
||||
"""
|
||||
return [f.label for f in formset.forms[0].visible_fields()]
|
||||
|
||||
|
||||
@register.filter
|
||||
def for_object(callable, 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'])
|
||||
"""
|
||||
if callable == '':
|
||||
return callable
|
||||
return callable(obj)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,16 @@ class PermissionMixin(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(PermissionMixin, self).get_context_data(**kwargs)
|
||||
permission_checker = permissions.TemplatePermissionChecker(
|
||||
self.request,
|
||||
self)
|
||||
context.update({
|
||||
'permissions': permission_checker,
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class Admin2Mixin(PermissionMixin):
|
||||
# are set in the ModelAdmin2 class when creating the view via
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from test_auth_admin import *
|
||||
from test_apiviews import *
|
||||
from test_builtin_api_resources import *
|
||||
from test_views import *
|
||||
from test_permissions import *
|
||||
from test_modelforms import *
|
||||
from test_views import *
|
||||
|
|
|
|||
104
example/blog/tests/test_permissions.py
Normal file
104
example/blog/tests/test_permissions.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
from django.contrib.auth.models import User, Permission
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import Template, Context
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
|
||||
import djadmin2
|
||||
from djadmin2.models import ModelAdmin2
|
||||
from djadmin2.permissions import TemplatePermissionChecker
|
||||
from blog.models import Post
|
||||
|
||||
|
||||
class TemplatePermissionTest(TestCase):
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.user = User(
|
||||
username='admin',
|
||||
is_staff=True)
|
||||
self.user.set_password('admin')
|
||||
self.user.save()
|
||||
|
||||
def render(self, template, context):
|
||||
template = Template(template)
|
||||
context = Context(context)
|
||||
return template.render(context)
|
||||
|
||||
def test_permission_wrapper(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)
|
||||
context = {
|
||||
'permissions': permissions,
|
||||
}
|
||||
|
||||
result = self.render(
|
||||
'{{ permissions.has_unvalid_permission }}',
|
||||
context)
|
||||
self.assertEqual(result, '')
|
||||
|
||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||
self.assertEqual(result, 'False')
|
||||
|
||||
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
|
||||
|
||||
permissions['has_add_permission']()
|
||||
result = self.render('{{ permissions.has_add_permission }}', context)
|
||||
self.assertEqual(result, 'True')
|
||||
|
||||
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)
|
||||
|
||||
post = Post.objects.create(title='Hello', body='world')
|
||||
context = {
|
||||
'post': post,
|
||||
'permissions': permissions,
|
||||
}
|
||||
|
||||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
'{{ permissions.has_unvalid_permission|for_object:post }}',
|
||||
context)
|
||||
self.assertEqual(result, '')
|
||||
|
||||
result = self.render(
|
||||
'{% load admin2_tags %}'
|
||||
'{{ permissions.has_add_permission|for_object:post }}',
|
||||
context)
|
||||
self.assertEqual(result, 'False')
|
||||
|
||||
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
|
||||
|
||||
# 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|for_object:post }}',
|
||||
context)
|
||||
self.assertEqual(result, 'False')
|
||||
Loading…
Reference in a new issue