From 7cde1fe78fa542de458ba04afc9d6fbaabd55d9b Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Sat, 19 Mar 2016 02:51:45 -0400 Subject: [PATCH] Add ACLs to document indexes. Convert all document indexing views to CBV. Remove the document index setup permission. Add view tests to the document indexing app. Use MPTT methods and stop relying on undocumented API. --- mayan/apps/document_indexing/apps.py | 23 +- mayan/apps/document_indexing/links.py | 34 +-- mayan/apps/document_indexing/models.py | 2 +- mayan/apps/document_indexing/permissions.py | 5 +- .../document_indexing/tests/test_views.py | 114 ++++++++ mayan/apps/document_indexing/urls.py | 13 +- mayan/apps/document_indexing/views.py | 255 +++++++++--------- mayan/apps/document_indexing/widgets.py | 7 +- 8 files changed, 287 insertions(+), 166 deletions(-) create mode 100644 mayan/apps/document_indexing/tests/test_views.py diff --git a/mayan/apps/document_indexing/apps.py b/mayan/apps/document_indexing/apps.py index fed0dadcaa..97b176a16b 100644 --- a/mayan/apps/document_indexing/apps.py +++ b/mayan/apps/document_indexing/apps.py @@ -6,6 +6,10 @@ from django.apps import apps from django.db.models.signals import post_save, post_delete from django.utils.translation import ugettext_lazy as _ +from acls import ModelPermission +from acls.links import link_acl_list +from acls.permissions import permission_acl_edit, permission_acl_view + from common import ( MayanAppConfig, menu_facet, menu_main, menu_object, menu_secondary, menu_setup, menu_tools @@ -29,6 +33,11 @@ from .links import ( link_template_node_create, link_template_node_delete, link_template_node_edit ) +from .permissions import ( + permission_document_indexing_create, permission_document_indexing_delete, + permission_document_indexing_edit, permission_document_indexing_view, + permission_document_indexing_rebuild +) from .widgets import get_instance_link, index_instance_item_link, node_level @@ -51,6 +60,7 @@ class DocumentIndexingApp(MayanAppConfig): ) DocumentIndexInstanceNode = self.get_model('DocumentIndexInstanceNode') + Index = self.get_model('Index') IndexInstance = self.get_model('IndexInstance') IndexInstanceNode = self.get_model('IndexInstanceNode') @@ -58,6 +68,16 @@ class DocumentIndexingApp(MayanAppConfig): APIEndPoint(app=self, version_string='1') + ModelPermission.register( + model=Index, permissions=( + permission_acl_edit, permission_acl_view, + permission_document_indexing_create, + permission_document_indexing_delete, + permission_document_indexing_edit, + permission_document_indexing_view, + ) + ) + Package(label='Django MPTT', license_text=''' Django MPTT ----------- @@ -179,7 +199,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. menu_object.bind_links( links=( link_index_setup_edit, link_index_setup_view, - link_index_setup_document_types, link_index_setup_delete + link_index_setup_document_types, link_acl_list, + link_index_setup_delete ), sources=(Index,) ) menu_object.bind_links( diff --git a/mayan/apps/document_indexing/links.py b/mayan/apps/document_indexing/links.py index d5b622393f..2fc8b50622 100644 --- a/mayan/apps/document_indexing/links.py +++ b/mayan/apps/document_indexing/links.py @@ -7,9 +7,8 @@ from navigation import Link from .permissions import ( permission_document_indexing_create, permission_document_indexing_edit, - permission_document_indexing_delete, - permission_document_indexing_rebuild_indexes, - permission_document_indexing_setup, permission_document_indexing_view + permission_document_indexing_delete, permission_document_indexing_rebuild, + permission_document_indexing_view ) @@ -18,23 +17,17 @@ def is_not_root_node(context): link_document_index_list = Link( - icon='fa fa-list-ul', permissions=(permission_document_view,), - text=_('Indexes'), view='indexing:document_index_list', args='object.pk' -) -link_index_list = Link( - permissions=(permission_document_indexing_view,), text=_('Index list'), - view='indexing:index_list' + icon='fa fa-list-ul', text=_('Indexes'), + view='indexing:document_index_list', args='object.pk' ) link_index_main_menu = Link( icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_list' ) link_index_setup = Link( - icon='fa fa-list-ul', permissions=(permission_document_indexing_setup,), - text=_('Indexes'), view='indexing:index_setup_list' + icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_setup_list' ) link_index_setup_list = Link( - permissions=(permission_document_indexing_setup,), text=_('Indexes'), - view='indexing:index_setup_list' + text=_('Indexes'), view='indexing:index_setup_list' ) link_index_setup_create = Link( permissions=(permission_document_indexing_create,), text=_('Create index'), @@ -50,7 +43,7 @@ link_index_setup_delete = Link( args='resolved_object.pk' ) link_index_setup_view = Link( - permissions=(permission_document_indexing_setup,), text=_('Tree template'), + permissions=(permission_document_indexing_edit,), text=_('Tree template'), view='indexing:index_setup_view', args='resolved_object.pk' ) link_index_setup_document_types = Link( @@ -62,23 +55,18 @@ link_rebuild_index_instances = Link( description=_( 'Deletes and creates from scratch all the document indexes.' ), - permissions=(permission_document_indexing_rebuild_indexes,), + permissions=(permission_document_indexing_rebuild,), text=_('Rebuild indexes'), view='indexing:rebuild_index_instances' ) link_template_node_create = Link( - permissions=(permission_document_indexing_setup,), text=_('New child node'), view='indexing:template_node_create', args='resolved_object.pk' ) link_template_node_edit = Link( - condition=is_not_root_node, - permissions=(permission_document_indexing_setup,), text=_('Edit'), + condition=is_not_root_node, text=_('Edit'), view='indexing:template_node_edit', args='resolved_object.pk' ) link_template_node_delete = Link( - condition=is_not_root_node, permissions=( - permission_document_indexing_setup, - ), - tags='dangerous', text=_('Delete'), view='indexing:template_node_delete', - args='resolved_object.pk' + condition=is_not_root_node, tags='dangerous', text=_('Delete'), + view='indexing:template_node_delete', args='resolved_object.pk' ) diff --git a/mayan/apps/document_indexing/models.py b/mayan/apps/document_indexing/models.py index 744d94767e..e6c983813c 100644 --- a/mayan/apps/document_indexing/models.py +++ b/mayan/apps/document_indexing/models.py @@ -136,7 +136,7 @@ class IndexTemplateNode(MPTTModel): def __str__(self): if self.is_root_node(): - return ugettext('<%s Root>') % self.index + return ugettext('Root') else: return self.expression diff --git a/mayan/apps/document_indexing/permissions.py b/mayan/apps/document_indexing/permissions.py index ab735647a8..4fa5ad4253 100644 --- a/mayan/apps/document_indexing/permissions.py +++ b/mayan/apps/document_indexing/permissions.py @@ -6,9 +6,6 @@ from permissions import PermissionNamespace namespace = PermissionNamespace('document_indexing', _('Indexing')) -permission_document_indexing_setup = namespace.add_permission( - name='document_index_setup', label=_('Configure document indexes') -) permission_document_indexing_create = namespace.add_permission( name='document_index_create', label=_('Create new document indexes') ) @@ -21,6 +18,6 @@ permission_document_indexing_delete = namespace.add_permission( permission_document_indexing_view = namespace.add_permission( name='document_index_view', label=_('View document indexes') ) -permission_document_indexing_rebuild_indexes = namespace.add_permission( +permission_document_indexing_rebuild = namespace.add_permission( name='document_rebuild_indexes', label=_('Rebuild document indexes') ) diff --git a/mayan/apps/document_indexing/tests/test_views.py b/mayan/apps/document_indexing/tests/test_views.py new file mode 100644 index 0000000000..576f2f73db --- /dev/null +++ b/mayan/apps/document_indexing/tests/test_views.py @@ -0,0 +1,114 @@ +from __future__ import absolute_import, unicode_literals + +from documents.permissions import permission_document_view +from documents.tests.test_views import GenericDocumentViewTestCase +from user_management.tests import ( + TEST_USER_USERNAME, TEST_USER_PASSWORD +) + +from ..models import Index +from ..permissions import ( + permission_document_indexing_create, permission_document_indexing_delete, + permission_document_indexing_edit, permission_document_indexing_view +) + +TEST_INDEX_LABEL = 'test label' +TEST_INDEX_EDITED_LABEL = 'test edited label' +TEST_INDEX_SLUG = 'test_slug' + + +class IndexViewTestCase(GenericDocumentViewTestCase): + def test_index_create_view_no_permission(self): + self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) + + response = self.post( + 'indexing:index_setup_create', data={ + 'label': TEST_INDEX_LABEL, 'slug': TEST_INDEX_SLUG + } + ) + + self.assertEquals(response.status_code, 403) + self.assertEqual(Index.objects.count(), 0) + + def test_index_create_view_with_permission(self): + self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) + + self.role.permissions.add( + permission_document_indexing_create.stored_permission + ) + + response = self.post( + 'indexing:index_setup_create', data={ + 'label': TEST_INDEX_LABEL, 'slug': TEST_INDEX_SLUG + }, follow=True + ) + + self.assertContains(response, text='created', status_code=200) + self.assertEqual(Index.objects.count(), 1) + self.assertEqual(Index.objects.first().label, TEST_INDEX_LABEL) + + def test_index_delete_view_no_permission(self): + self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) + + index = Index.objects.create( + label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG + ) + + response = self.post('indexing:index_setup_delete', args=(index.pk,)) + self.assertEqual(response.status_code, 403) + self.assertEqual(Index.objects.count(), 1) + + def test_index_delete_view_with_permission(self): + self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) + + self.role.permissions.add( + permission_document_indexing_delete.stored_permission + ) + + index = Index.objects.create( + label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG + ) + + response = self.post( + 'indexing:index_setup_delete', args=(index.pk,), follow=True + ) + + self.assertContains(response, text='deleted', status_code=200) + self.assertEqual(Index.objects.count(), 0) + + def test_index_edit_view_no_permission(self): + self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) + + index = Index.objects.create( + label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG + ) + + response = self.post( + 'indexing:index_setup_edit', args=(index.pk,), data={ + 'label': TEST_INDEX_EDITED_LABEL, 'slug': TEST_INDEX_SLUG + } + ) + self.assertEqual(response.status_code, 403) + index = Index.objects.get(pk=index.pk) + self.assertEqual(index.label, TEST_INDEX_LABEL) + + def test_index_edit_view_with_permission(self): + self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) + + self.role.permissions.add( + permission_document_indexing_edit.stored_permission + ) + + index = Index.objects.create( + label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG + ) + + response = self.post( + 'indexing:index_setup_edit', args=(index.pk,), data={ + 'label': TEST_INDEX_EDITED_LABEL, 'slug': TEST_INDEX_SLUG + }, follow=True + ) + + index = Index.objects.get(pk=index.pk) + self.assertContains(response, text='update', status_code=200) + self.assertEqual(index.label, TEST_INDEX_EDITED_LABEL) diff --git a/mayan/apps/document_indexing/urls.py b/mayan/apps/document_indexing/urls.py index d7af8cd1d0..1aa8306b90 100644 --- a/mayan/apps/document_indexing/urls.py +++ b/mayan/apps/document_indexing/urls.py @@ -11,11 +11,12 @@ from .views import ( DocumentIndexNodeListView, IndexInstanceNodeView, IndexListView, RebuildIndexesConfirmView, SetupIndexDocumentTypesView, SetupIndexCreateView, SetupIndexDeleteView, SetupIndexEditView, - SetupIndexListView, SetupIndexTreeTemplateListView, TemplateNodeDeleteView + SetupIndexListView, SetupIndexTreeTemplateListView, TemplateNodeCreateView, + TemplateNodeDeleteView, TemplateNodeEditView ) urlpatterns = patterns( - 'document_indexing.views', + '', url( r'^setup/index/list/$', SetupIndexListView.as_view(), name='index_setup_list' @@ -42,12 +43,12 @@ urlpatterns = patterns( name='index_setup_document_types' ), url( - r'^setup/template/node/(?P\d+)/create/child/$', - 'template_node_create', name='template_node_create' + r'^setup/template/node/(?P\d+)/create/child/$', + TemplateNodeCreateView.as_view(), name='template_node_create' ), url( - r'^setup/template/node/(?P\d+)/edit/$', 'template_node_edit', - name='template_node_edit' + r'^setup/template/node/(?P\d+)/edit/$', + TemplateNodeEditView.as_view(), name='template_node_edit' ), url( r'^setup/template/node/(?P\d+)/delete/$', diff --git a/mayan/apps/document_indexing/views.py b/mayan/apps/document_indexing/views.py index fb0da0beb7..b510a81fa8 100644 --- a/mayan/apps/document_indexing/views.py +++ b/mayan/apps/document_indexing/views.py @@ -26,9 +26,8 @@ from .models import ( ) from .permissions import ( permission_document_indexing_create, permission_document_indexing_delete, - permission_document_indexing_edit, - permission_document_indexing_rebuild_indexes, - permission_document_indexing_setup, permission_document_indexing_view + permission_document_indexing_edit, permission_document_indexing_rebuild, + permission_document_indexing_view ) from .tasks import task_do_rebuild_all_indexes from .widgets import node_tree @@ -43,34 +42,10 @@ class SetupIndexCreateView(SingleObjectCreateView): view_permission = permission_document_indexing_create -class SetupIndexListView(SingleObjectListView): - model = Index - view_permission = permission_document_indexing_setup - - def get_extra_context(self): - return { - 'hide_object': True, - 'title': _('Indexes'), - } - - -class SetupIndexEditView(SingleObjectEditView): - fields = ('label', 'slug', 'enabled') - model = Index - post_action_redirect = reverse_lazy('indexing:index_setup_list') - view_permission = permission_document_indexing_edit - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'title': _('Edit index: %s') % self.get_object(), - } - - class SetupIndexDeleteView(SingleObjectDeleteView): model = Index post_action_redirect = reverse_lazy('indexing:index_setup_list') - view_permission = permission_document_indexing_delete + object_permission = permission_document_indexing_delete def get_extra_context(self): return { @@ -79,8 +54,82 @@ class SetupIndexDeleteView(SingleObjectDeleteView): } +class SetupIndexEditView(SingleObjectEditView): + fields = ('label', 'slug', 'enabled') + model = Index + post_action_redirect = reverse_lazy('indexing:index_setup_list') + object_permission = permission_document_indexing_edit + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'title': _('Edit index: %s') % self.get_object(), + } + + +class SetupIndexListView(SingleObjectListView): + model = Index + object_permission = permission_document_indexing_view + + def get_extra_context(self): + return { + 'hide_object': True, + 'title': _('Indexes'), + } + + +class SetupIndexDocumentTypesView(AssignRemoveView): + decode_content_type = True + left_list_title = _('Available document types') + object_permission = permission_document_indexing_edit + right_list_title = _('Document types linked') + + def add(self, item): + self.get_object().document_types.add(item) + + def get_document_queryset(self): + queryset = DocumentType.objects.all() + + try: + Permission.check_permissions( + self.request.user, (permission_document_view,) + ) + except PermissionDenied: + queryset = AccessControlList.objects.filter_by_access( + permission_document_view, self.request.user, queryset + ) + + return queryset + + def get_extra_context(self): + return { + 'object': self.get_object(), + 'title': _( + 'Document types linked to index: %s' + ) % self.get_object() + } + + def get_object(self): + return get_object_or_404(Index, pk=self.kwargs['pk']) + + def left_list(self): + return AssignRemoveView.generate_choices( + self.get_document_queryset().exclude( + id__in=self.get_object().document_types.all() + ) + ) + + def remove(self, item): + self.get_object().document_types.remove(item) + + def right_list(self): + return AssignRemoveView.generate_choices( + self.get_document_queryset() & self.get_object().document_types.all() + ) + + class SetupIndexTreeTemplateListView(SingleObjectListView): - view_permission = permission_document_indexing_setup + object_permission = permission_document_indexing_edit def get_index(self): return get_object_or_404(Index, pk=self.kwargs['pk']) @@ -98,116 +147,46 @@ class SetupIndexTreeTemplateListView(SingleObjectListView): 'title': _('Tree template nodes for index: %s') % self.get_index(), } +class TemplateNodeCreateView(SingleObjectCreateView): + form_class = IndexTemplateNodeForm + model = IndexTemplateNode -class SetupIndexDocumentTypesView(AssignRemoveView): - decode_content_type = True - object_permission = permission_document_indexing_edit - left_list_title = _('Available document types') - right_list_title = _('Document types linked') - - def add(self, item): - self.get_object().document_types.add(item) - - def get_object(self): - return get_object_or_404(Index, pk=self.kwargs['pk']) - - def left_list(self): - return AssignRemoveView.generate_choices( - DocumentType.objects.exclude( - pk__in=self.get_object().document_types.all() + def dispatch(self, request, *args, **kwargs): + try: + Permission.check_permissions( + request.user, (permission_document_indexing_edit,) + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_document_indexing_edit, request.user, + self.get_parent_node().index ) - ) - def right_list(self): - return AssignRemoveView.generate_choices( - self.get_object().document_types.all() - ) - - def remove(self, item): - self.get_object().document_types.remove(item) + return super( + TemplateNodeCreateView, self + ).dispatch(request, *args, **kwargs) def get_extra_context(self): return { - 'object': self.get_object(), - 'title': _( - 'Document types linked to index: %s' - ) % self.get_object() + 'index': self.get_parent_node().index, + 'navigation_object_list': ('index',), + 'title': _('Create child node of: %s') % self.get_parent_node(), } + def get_initial(self): + parent_node = self.get_parent_node() + return { + 'index': parent_node.index, 'parent': parent_node + } -# Node views -def template_node_create(request, parent_pk): - parent_node = get_object_or_404(IndexTemplateNode, pk=parent_pk) - - try: - Permission.check_permissions( - request.user, (permission_document_indexing_edit,) - ) - except PermissionDenied: - AccessControlList.objects.check_access( - permission_document_indexing_edit, request.user, parent_node.index - ) - - if request.method == 'POST': - form = IndexTemplateNodeForm(request.POST) - if form.is_valid(): - node = form.save() - messages.success( - request, _('Index template node created successfully.') - ) - return HttpResponseRedirect( - reverse('indexing:index_setup_view', args=(node.index.pk,)) - ) - else: - form = IndexTemplateNodeForm( - initial={'index': parent_node.index, 'parent': parent_node} - ) - - return render_to_response('appearance/generic_form.html', { - 'form': form, - 'index': parent_node.index, - 'navigation_object_list': ('index',), - 'title': _('Create child node'), - }, context_instance=RequestContext(request)) - - -def template_node_edit(request, node_pk): - node = get_object_or_404(IndexTemplateNode, pk=node_pk) - - try: - Permission.check_permissions( - request.user, (permission_document_indexing_edit,) - ) - except PermissionDenied: - AccessControlList.objects.check_access( - permission_document_indexing_edit, request.user, node.index - ) - - if request.method == 'POST': - form = IndexTemplateNodeForm(request.POST, instance=node) - if form.is_valid(): - form.save() - messages.success( - request, _('Index template node edited successfully') - ) - return HttpResponseRedirect( - reverse('indexing:index_setup_view', args=(node.index.pk,)) - ) - else: - form = IndexTemplateNodeForm(instance=node) - - return render_to_response('appearance/generic_form.html', { - 'form': form, - 'index': node.index, - 'navigation_object_list': ('index', 'node'), - 'node': node, - 'title': _('Edit index template node: %s') % node, - }, context_instance=RequestContext(request)) + def get_parent_node(self): + return get_object_or_404(IndexTemplateNode, pk=self.kwargs['pk']) class TemplateNodeDeleteView(SingleObjectDeleteView): model = IndexTemplateNode - view_permission = permission_document_indexing_edit + object_permission = permission_document_indexing_edit + object_permission_related = 'index' def get_extra_context(self): return { @@ -225,6 +204,28 @@ class TemplateNodeDeleteView(SingleObjectDeleteView): ) +class TemplateNodeEditView(SingleObjectEditView): + form_class = IndexTemplateNodeForm + model = IndexTemplateNode + object_permission = permission_document_indexing_edit + object_permission_related = 'index' + + def get_extra_context(self): + return { + 'index': self.get_object().index, + 'navigation_object_list': ('index', 'node'), + 'node': self.get_object(), + 'title': _( + 'Edit the index template node: %s?' + ) % self.get_object(), + } + + def get_post_action_redirect(self): + return reverse( + 'indexing:index_setup_view', args=(self.get_object().index.pk,) + ) + + class IndexListView(SingleObjectListView): object_permission = permission_document_indexing_view queryset = IndexInstance.objects.filter(enabled=True) @@ -341,7 +342,7 @@ class RebuildIndexesConfirmView(ConfirmView): 'message': _('On large databases this operation may take some time to execute.'), 'title': _('Rebuild all indexes?'), } - view_permission = permission_document_indexing_rebuild_indexes + view_permission = permission_document_indexing_rebuild def get_post_action_redirect(self): return reverse('common:tools_list') diff --git a/mayan/apps/document_indexing/widgets.py b/mayan/apps/document_indexing/widgets.py index 7c684dfc29..70d9fdd16a 100644 --- a/mayan/apps/document_indexing/widgets.py +++ b/mayan/apps/document_indexing/widgets.py @@ -49,10 +49,9 @@ def node_level(node): return mark_safe( ''.join( [ - '     ' * ( - getattr(node, node._mptt_meta.level_attr) - 1 - ), '' if node.is_root_node() else ' ', - ugettext('Root') if node.is_root_node() else unicode(node) + '     ' * node.get_level(), + '' if node.is_root_node() else ' ', + unicode(node) ] ) )