Adding the ability to check for permissions in templates.

This commit is contained in:
Gregor Müllegger 2013-05-24 01:02:08 +02:00
parent 324cdbaa17
commit ab778191c6
7 changed files with 203 additions and 14 deletions

View file

@ -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")

View file

@ -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))

View file

@ -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 %}

View file

@ -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)

View file

@ -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

View file

@ -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 *

View 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')