From 1cd3c4e7d0f87dabb53a566de92dc9c2a59b4927 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 3 Jan 2012 18:47:28 -0400 Subject: [PATCH 1/4] Add related support to 'filter_objects_by_access' and 'get_allowed_class_objects' methods --- apps/acls/managers.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/acls/managers.py b/apps/acls/managers.py index 6d674bbc59..0b22db3cbd 100644 --- a/apps/acls/managers.py +++ b/apps/acls/managers.py @@ -95,11 +95,20 @@ class AccessEntryManager(models.Manager): raise PermissionDenied(ugettext(u'Insufficient access.')) - def get_allowed_class_objects(self, permission, actor, cls): + def get_allowed_class_objects(self, permission, actor, cls, related=None): + logger.debug('related: %s' % related) + actor_type = ContentType.objects.get_for_model(actor) content_type = ContentType.objects.get_for_model(cls) - - return (obj.content_object for obj in self.model.objects.filter(holder_type=actor_type, holder_id=actor.pk, content_type=content_type, permission=permission.get_stored_permission)) + if related: + master_list = [obj.content_object for obj in self.model.objects.select_related().filter(holder_type=actor_type, holder_id=actor.pk, permission=permission.get_stored_permission)] + logger.debug('master_list: %s' % master_list) + # TODO: update to use Q objects and check performance diff + # kwargs = {'%s__in' % related: master_list} + # Q(**kwargs) + return (obj for obj in cls.objects.all() if getattr(obj, related) in master_list) + else: + return (obj.content_object for obj in self.model.objects.filter(holder_type=actor_type, holder_id=actor.pk, content_type=content_type, permission=permission.get_stored_permission)) def get_acl_url(self, obj): content_type = ContentType.objects.get_for_model(obj) @@ -132,7 +141,7 @@ class AccessEntryManager(models.Manager): content_type = ContentType.objects.get_for_model(obj) return (access.permission for access in self.model.objects.filter(content_type=content_type, object_id=obj.pk, holder_type=actor_type, holder_id=actor.pk)) - def filter_objects_by_access(self, permission, actor, object_list, exception_on_empty=False): + def filter_objects_by_access(self, permission, actor, object_list, exception_on_empty=False, related=None): logger.debug('exception_on_empty: %s' % exception_on_empty) logger.debug('object_list: %s' % object_list) @@ -150,7 +159,7 @@ class AccessEntryManager(models.Manager): try: # Try to process as a QuerySet - qs = object_list.filter(pk__in=[obj.pk for obj in self.get_allowed_class_objects(permission, actor, object_list[0])]) + qs = object_list.filter(pk__in=[obj.pk for obj in self.get_allowed_class_objects(permission, actor, object_list[0].__class__, related)]) logger.debug('qs: %s' % qs) if qs.count() == 0 and exception_on_empty == True: @@ -158,13 +167,13 @@ class AccessEntryManager(models.Manager): return qs except AttributeError: - # Fallback to a list filtered list - obj_list = list(set(object_list) & set(self.get_allowed_class_objects(permission, actor, object_list[0]))) - logger.debug('obj_list: %s' % obj_list) - if len(obj_list) == 0 and exception_on_empty == True: + # Fallback to a filtered list + object_list = list(set(object_list) & set(self.get_allowed_class_objects(permission, actor, object_list[0].__class__, related))) + logger.debug('object_list: %s' % object_list) + if len(object_list) == 0 and exception_on_empty == True: raise PermissionDenied - return obj_list + return object_list class DefaultAccessEntryManager(models.Manager): From 268c1386f09c9abd329a670264a9f3dd3b6f5196 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 3 Jan 2012 18:48:12 -0400 Subject: [PATCH 2/4] Remove the unimplemented PERMISSION_COMMENT_EDIT permission --- apps/document_comments/permissions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/document_comments/permissions.py b/apps/document_comments/permissions.py index 820a16ede5..37e1391b61 100644 --- a/apps/document_comments/permissions.py +++ b/apps/document_comments/permissions.py @@ -8,5 +8,4 @@ comments_namespace = PermissionNamespace('comments', _(u'Comments')) PERMISSION_COMMENT_CREATE = Permission.objects.register(comments_namespace, 'comment_create', _(u'Create new comments')) PERMISSION_COMMENT_DELETE = Permission.objects.register(comments_namespace, 'comment_delete', _(u'Delete comments')) -PERMISSION_COMMENT_EDIT = Permission.objects.register(comments_namespace, 'comment_edit', _(u'Edit comments')) PERMISSION_COMMENT_VIEW = Permission.objects.register(comments_namespace, 'comment_view', _(u'View comments')) From ffd90c03406a9b109dec955144fa44aad86e8ff3 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 3 Jan 2012 18:48:42 -0400 Subject: [PATCH 3/4] Remove unneeded module --- apps/document_comments/utils.py | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 apps/document_comments/utils.py diff --git a/apps/document_comments/utils.py b/apps/document_comments/utils.py deleted file mode 100644 index cca6e07098..0000000000 --- a/apps/document_comments/utils.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import absolute_import - -from django.utils.translation import ugettext_lazy as _ -from django.contrib.comments.models import Comment -from . import comment_delete - - -def get_comments_subtemplate(obj): - """ - Return all the settings to render a subtemplate containing an - object's comments - """ - return { - 'name': 'generic_list_subtemplate.html', - 'context': { - 'title': _(u'comments'), - 'object_list': Comment.objects.for_model(obj).order_by('-submit_date'), - 'hide_link': True, - 'hide_object': True, - 'navigation_object_links': [comment_delete], - 'scrollable_content': True, - 'scrollable_content_height': '200px', - } - } From edd344b924da6dfebb7a102d846c9797ff70fd85 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 3 Jan 2012 18:49:03 -0400 Subject: [PATCH 4/4] Add ACL support to the document comments app --- apps/document_comments/__init__.py | 17 +++++++++------ apps/document_comments/urls.py | 8 +++---- apps/document_comments/views.py | 34 +++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/apps/document_comments/__init__.py b/apps/document_comments/__init__.py index 5d4cdec3bb..6758a06811 100644 --- a/apps/document_comments/__init__.py +++ b/apps/document_comments/__init__.py @@ -8,20 +8,19 @@ from django.contrib.contenttypes import generic from navigation.api import register_links, register_model_list_columns from permissions.models import PermissionNamespace, Permission from common.utils import encapsulate - +from acls.api import class_permissions from documents.models import Document if 'django.contrib.comments' not in settings.INSTALLED_APPS: raise Exception('This app depends on the django.contrib.comments app.') from .permissions import (PERMISSION_COMMENT_CREATE, - PERMISSION_COMMENT_DELETE, PERMISSION_COMMENT_EDIT, - PERMISSION_COMMENT_VIEW) + PERMISSION_COMMENT_DELETE, PERMISSION_COMMENT_VIEW) comment_delete = {'text': _('delete'), 'view': 'comment_delete', 'args': 'object.pk', 'famfam': 'comment_delete', 'permissions': [PERMISSION_COMMENT_DELETE]} comment_multiple_delete = {'text': _('delete'), 'view': 'comment_multiple_delete', 'args': 'object.pk', 'famfam': 'comments_delete', 'permissions': [PERMISSION_COMMENT_DELETE]} comment_add = {'text': _('add comment'), 'view': 'comment_add', 'args': 'object.pk', 'famfam': 'comment_add', 'permissions': [PERMISSION_COMMENT_CREATE]} -comments_for_object = {'text': _('comments'), 'view': 'comments_for_object', 'args': 'object.pk', 'famfam': 'comments', 'permissions': [PERMISSION_COMMENT_VIEW], 'children_view_regex': ['comment']} +comments_for_document = {'text': _('comments'), 'view': 'comments_for_document', 'args': 'object.pk', 'famfam': 'comments', 'permissions': [PERMISSION_COMMENT_VIEW], 'children_view_regex': ['comment']} register_model_list_columns(Comment, [ { @@ -38,9 +37,9 @@ register_model_list_columns(Comment, [ } ]) -register_links(['comments_for_object', 'comment_add', 'comment_delete', 'comment_multiple_delete'], [comment_add], menu_name='sidebar') +register_links(['comments_for_document', 'comment_add', 'comment_delete', 'comment_multiple_delete'], [comment_add], menu_name='sidebar') register_links(Comment, [comment_delete]) -register_links(Document, [comments_for_object], menu_name='form_header') +register_links(Document, [comments_for_document], menu_name='form_header') Document.add_to_class( 'comments', @@ -50,3 +49,9 @@ Document.add_to_class( object_id_field='object_pk' ) ) + +class_permissions(Document, [ + PERMISSION_COMMENT_CREATE, + PERMISSION_COMMENT_DELETE, + PERMISSION_COMMENT_VIEW +]) diff --git a/apps/document_comments/urls.py b/apps/document_comments/urls.py index f977004825..d9230c22d5 100644 --- a/apps/document_comments/urls.py +++ b/apps/document_comments/urls.py @@ -1,8 +1,8 @@ from django.conf.urls.defaults import patterns, url urlpatterns = patterns('document_comments.views', - url(r'^(?P\d+)/delete/$', 'comment_delete', (), 'comment_delete'), - url(r'^multiple/delete/$', 'comment_multiple_delete', (), 'comment_multiple_delete'), - url(r'^add_to_document/(?P\d+)/$', 'comment_add', (), 'comment_add'), - url(r'^for/object/(?P\d+)/$', 'comments_for_object', (), 'comments_for_object'), + url(r'^comment/(?P\d+)/delete/$', 'comment_delete', (), 'comment_delete'), + url(r'^comment/multiple/delete/$', 'comment_multiple_delete', (), 'comment_multiple_delete'), + url(r'^(?P\d+)/comment/add/$', 'comment_add', (), 'comment_add'), + url(r'^(?P\d+)/comment/list/$', 'comments_for_document', (), 'comments_for_document'), ) diff --git a/apps/document_comments/views.py b/apps/document_comments/views.py index 9e474a2cb6..cb9cc0a92e 100644 --- a/apps/document_comments/views.py +++ b/apps/document_comments/views.py @@ -8,27 +8,33 @@ from django.template import RequestContext from django.contrib import messages from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site +from django.core.exceptions import PermissionDenied +from acls.models import AccessEntry from permissions.models import Permission from documents.models import Document from .permissions import (PERMISSION_COMMENT_CREATE, - PERMISSION_COMMENT_DELETE, PERMISSION_COMMENT_EDIT, - PERMISSION_COMMENT_VIEW) + PERMISSION_COMMENT_DELETE, PERMISSION_COMMENT_VIEW) from .forms import CommentForm def comment_delete(request, comment_id=None, comment_id_list=None): - Permission.objects.check_permissions(request.user, [PERMISSION_COMMENT_DELETE]) post_action_redirect = None if comment_id: comments = [get_object_or_404(Comment, pk=comment_id)] elif comment_id_list: comments = [get_object_or_404(Comment, pk=comment_id) for comment_id in comment_id_list.split(',')] - else: + + try: + Permission.objects.check_permissions(request.user, [PERMISSION_COMMENT_DELETE]) + except PermissionDenied: + comments = AccessEntry.objects.filter_objects_by_access(PERMISSION_COMMENT_DELETE, request.user, comments, related='content_object') + + if not comments: messages.error(request, _(u'Must provide at least one comment.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) + return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/'))) next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/'))) @@ -69,9 +75,13 @@ def comment_multiple_delete(request): def comment_add(request, document_id): - Permission.objects.check_permissions(request.user, [PERMISSION_COMMENT_CREATE]) - document = get_object_or_404(Document, pk=document_id) + + try: + Permission.objects.check_permissions(request.user, [PERMISSION_COMMENT_CREATE]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_COMMENT_CREATE, request.user, document) + post_action_redirect = None next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/'))) @@ -99,16 +109,20 @@ def comment_add(request, document_id): }, context_instance=RequestContext(request)) -def comments_for_object(request, document_id): +def comments_for_document(request, document_id): ''' Show a list of all the comments related to the passed object ''' - Permission.objects.check_permissions(request.user, [PERMISSION_COMMENT_VIEW]) - document = get_object_or_404(Document, pk=document_id) + try: + Permission.objects.check_permissions(request.user, [PERMISSION_COMMENT_VIEW]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_COMMENT_VIEW, request.user, document) + return render_to_response('generic_list.html', { 'object': document, + 'access_object': document, 'title': _(u'comments: %s') % document, 'object_list': Comment.objects.for_model(document).order_by('-submit_date'), 'hide_link': True,