diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index dd5269e..dd74c31 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -236,9 +236,33 @@ class TemplatePermissionChecker(object): except ValueError: return '' new_permissions = self.clone() + new_permissions._view = None new_permissions._model_admin = admin return new_permissions + def bind_view(self, view): + ''' + Return a clone of the permission wrapper with a new view bind to it. + ''' + if isinstance(view, six.string_types): + if view not in self.view_name_mapping: + return '' + view_name = self.view_name_mapping[view] + view = getattr(self._model_admin, view_name) + # we don't support binding view classes yet, only the name of views + # are processed. We have the problem with view classes that we cannot + # tell which model admin it was attached to. + else: + return '' + # if view is a class and not instantiated yet, do it! + if isinstance(view, type): + view = view( + request=self._request, + **self._model_admin.get_default_view_kwargs()) + new_permissions = self.clone() + new_permissions._view = view + return new_permissions + def bind_object(self, obj): ''' Return a clone of the permission wrapper with a new object bind @@ -248,30 +272,17 @@ class TemplatePermissionChecker(object): 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, - **model_admin.get_default_view_kwargs()) - return view - ######################################### # interface exposed to the template users def __getitem__(self, key): match = self._has_named_permission_regex.match(key) if match: - # the key was a has_*_permission, so get the *has permission - # wrapper* + # the key was a has_*_permission, so bind the correspodning view view_name = match.groupdict()['name'] - if view_name not in self.view_name_mapping: - raise KeyError - 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 + return self.bind_view(view_name) + # the name might be a named object admin. So get that one and bind it + # to the permission checking try: admin_site = self._model_admin.admin model_admin = admin_site.get_admin_by_name(key) diff --git a/djadmin2/templatetags/admin2_tags.py b/djadmin2/templatetags/admin2_tags.py index ac8fbbc..f0a0080 100644 --- a/djadmin2/templatetags/admin2_tags.py +++ b/djadmin2/templatetags/admin2_tags.py @@ -45,19 +45,6 @@ def formset_visible_fieldlist(formset): return [f.label for f in formset.forms[0].visible_fields()] -@register.filter -def for_object(permissions, obj): - """ - Only useful in the permission handling. This filter binds a new object to - the permission handler to check for object-level permissions. - """ - # 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): """ @@ -69,3 +56,30 @@ def for_admin(permissions, admin): if permissions == '': return permissions return permissions.bind_admin(admin) + + +@register.filter +def for_view(permissions, view): + """ + Only useful in the permission handling. This filter binds a new view to + the permission handler to check for view names that are not known during + template compile time. + """ + # 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_view(view) + + +@register.filter +def for_object(permissions, obj): + """ + Only useful in the permission handling. This filter binds a new object to + the permission handler to check for object-level permissions. + """ + # 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) diff --git a/djadmin2/viewmixins.py b/djadmin2/viewmixins.py index e54e796..b8b5bf5 100644 --- a/djadmin2/viewmixins.py +++ b/djadmin2/viewmixins.py @@ -12,6 +12,7 @@ from .utils import admin2_urlname, model_options class PermissionMixin(object): + do_not_call_in_templates = True permission_classes = (permissions.IsStaffPermission,) def __init__(self, **kwargs): diff --git a/example/blog/tests/test_permissions.py b/example/blog/tests/test_permissions.py index 142ce71..9b6be1f 100644 --- a/example/blog/tests/test_permissions.py +++ b/example/blog/tests/test_permissions.py @@ -154,6 +154,86 @@ class TemplatePermissionTest(TestCase): context) self.assertEqual(result, '') + def test_view_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) + + context = { + 'post_admin': post_admin, + 'post_add_view': post_admin.create_view, + 'permissions': permissions, + } + + result = self.render( + '{% load admin2_tags %}' + '{{ permissions|for_view:"add" }}', + context) + self.assertEqual(result, 'False') + + # view classes are not supported yet + result = self.render( + '{% load admin2_tags %}' + '{{ permissions|for_view:post_add_view }}', + context) + self.assertEqual(result, '') + + result = self.render( + '{% load admin2_tags %}' + # user add permission + '{{ permissions.has_add_permission }}' + '{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}' + # post add permission + '{{ post_add_perm }}' + '{% 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) + user_change_permission = Permission.objects.get( + content_type__app_label='auth', + content_type__model='user', + codename='change_user') + self.user.user_permissions.add(user_change_permission) + + # invalidate the users permission cache + if hasattr(self.user, '_perm_cache'): + del self.user._perm_cache + + result = self.render( + '{% load admin2_tags %}' + # user add permission + '{{ permissions.has_add_permission }}' + '{% with permissions|for_admin:"blog_post"|for_view:"add" as post_add_perm %}' + # post add permission + '{{ post_add_perm }}' + '{% endwith %}' + # user change permission + '{{ permissions|for_view:"change" }}', + context) + self.assertEqual(result, 'FalseTrueTrue') + + # giving a string (the name of the view) also works + result = self.render( + '{% load admin2_tags %}' + '{% with permissions|for_view:"change" as user_change_perm %}' + '1{{ user_change_perm }}' + '2{{ user_change_perm|for_view:"add" }}' + # this shouldn't return True or False but '' since the + # previously bound change view doesn't belong to the newly + # bound blog_post admin + '3{{ user_change_perm|for_admin:"blog_post" }}' + '4{{ user_change_perm|for_admin:"blog_post"|for_view:"add" }}' + '{% endwith %}', + context) + self.assertEqual(result, '1True2False34True') + def test_object_level_permission(self): model_admin = ModelAdmin2(Post, djadmin2.default) request = self.factory.get(reverse('admin2:blog_post_index'))