From bd12d587ee8aff8a8f09940748fea14d8add1e90 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 5 Feb 2019 05:40:01 -0400 Subject: [PATCH] Refactor document indexing app Convert half the widget to HTML widgets. Rename links and views to use the nomeclature _template_ and _instance_ to differenciate between index instances and index templates. Update URL parameters to use the "_id" form. Add more tests. Add model permission inheritance to the IndexTemplateNode, and IndexInstanceNode models. Remove the level and document count display from the instance node. Display instead the total items. Use a FilteredSelectionForm subclass to display the list of index templates to rebuild. Add missing icons. Add keyword arguments to links. Modernize tests to use the document test mixin. Update the permission requirements for the index template document type selection screen. The document type view permission is now required in addition to the index template edit permission. Use ExternalObjectMixin to reduce the code in all views. Signed-off-by: Roberto Rosario --- HISTORY.rst | 4 + mayan/apps/document_indexing/apps.py | 102 ++-- mayan/apps/document_indexing/forms.py | 27 +- .../{widgets.py => html_widgets.py} | 70 +-- mayan/apps/document_indexing/icons.py | 35 +- mayan/apps/document_indexing/links.py | 103 ++-- mayan/apps/document_indexing/models.py | 6 +- mayan/apps/document_indexing/tasks.py | 4 +- .../index_instance_node.html | 13 + .../index_template_node_indentation.html | 11 + .../document_indexing/node_details.html | 1 - .../apps/document_indexing/tests/literals.py | 2 + mayan/apps/document_indexing/tests/mixins.py | 29 +- .../document_indexing/tests/test_models.py | 111 ++-- .../document_indexing/tests/test_views.py | 463 +++++++++++---- mayan/apps/document_indexing/urls.py | 120 ++-- mayan/apps/document_indexing/views.py | 526 ++++++++---------- 17 files changed, 947 insertions(+), 680 deletions(-) rename mayan/apps/document_indexing/{widgets.py => html_widgets.py} (52%) create mode 100644 mayan/apps/document_indexing/templates/document_indexing/index_instance_node.html create mode 100644 mayan/apps/document_indexing/templates/document_indexing/index_template_node_indentation.html diff --git a/HISTORY.rst b/HISTORY.rst index 22eb460148..4adcf98872 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -233,6 +233,10 @@ entire converter class is no longer cached and instead loaded on demand. This allows the garbage collector to clear the memory used. +- Update the permission requirements for the index template + document type selection screen. The document type view + permission is now required in addition to the index + template edit permission. 3.1.9 (2018-11-01) ================== diff --git a/mayan/apps/document_indexing/apps.py b/mayan/apps/document_indexing/apps.py index b6dc7a35b8..b252b699f7 100644 --- a/mayan/apps/document_indexing/apps.py +++ b/mayan/apps/document_indexing/apps.py @@ -27,23 +27,25 @@ from .handlers import ( handler_index_document, handler_post_save_index_document, handler_remove_document ) +from .html_widgets import ( + IndexInstanceNodeWidget, IndexTemplateNodeIndentationWidget, + get_instance_link +) from .licenses import * # NOQA from .links import ( - link_document_index_list, link_index_main_menu, link_index_setup, - link_index_setup_create, link_index_setup_delete, - link_index_setup_document_types, link_index_setup_edit, - link_index_setup_list, link_index_setup_view, link_rebuild_index_instances, - link_template_node_create, link_template_node_delete, - link_template_node_edit + link_document_index_instance_list, link_index_instances_rebuild, + link_index_main_menu, link_index_setup, link_index_template_create, + link_index_template_delete, link_index_template_document_types, + link_index_template_edit, link_index_template_list, + link_index_template_view, link_index_template_node_create, + link_index_template_node_delete, link_index_template_node_edit ) from .permissions import ( permission_document_indexing_create, permission_document_indexing_delete, - permission_document_indexing_edit, - permission_document_indexing_instance_view, + permission_document_indexing_edit, permission_document_indexing_instance_view, permission_document_indexing_rebuild, permission_document_indexing_view ) from .queues import * # NOQA -from .widgets import get_instance_link, index_instance_item_link, node_level class DocumentIndexingApp(MayanAppConfig): @@ -86,53 +88,63 @@ class DocumentIndexingApp(MayanAppConfig): ) ) - SourceColumn(attribute='label', is_identifier=True, source=Index) - SourceColumn(attribute='slug', source=Index) + ModelPermission.register_inheritance( + model=IndexTemplateNode, related='index' + ) + + ModelPermission.register_inheritance( + model=IndexInstanceNode, related='index_template_node__index' + ) + SourceColumn( - attribute='enabled', source=Index, widget=TwoStateWidget + attribute='label', is_identifier=True, is_sortable=True, source=Index + ) + SourceColumn( + attribute='slug', include_label=True, is_sortable=True, source=Index + ) + SourceColumn( + attribute='enabled', include_label=True, is_sortable=True, + source=Index, widget=TwoStateWidget ) SourceColumn( func=lambda context: context[ 'object' - ].instance_root.get_descendants_count(), label=_('Total levels'), - source=IndexInstance, + ].instance_root.get_descendants_count(), include_label=True, + label=_('Total levels'), source=IndexInstance, ) SourceColumn( func=lambda context: context[ 'object' ].instance_root.get_descendants_document_count( user=context['request'].user - ), label=_('Total documents'), source=IndexInstance + ), include_label=True, label=_('Total documents'), source=IndexInstance + ) + SourceColumn( + label=_('Level'), is_identifier=True, source=IndexTemplateNode, + widget=IndexTemplateNodeIndentationWidget + ) + SourceColumn( + attribute='enabled', include_label=True, is_sortable=True, + source=IndexTemplateNode, widget=TwoStateWidget + ) + SourceColumn( + attribute='link_documents', include_label=True, + is_sortable=True, source=IndexTemplateNode, widget=TwoStateWidget ) SourceColumn( - func=lambda context: node_level(context['object']), - label=_('Level'), source=IndexTemplateNode - ) - SourceColumn( - attribute='enabled', source=IndexTemplateNode, - widget=TwoStateWidget - ) - SourceColumn( - attribute='link_documents', source=IndexTemplateNode, - widget=TwoStateWidget + is_identifier=True, is_sortable=True, label=_('Level'), + sort_field='value', source=IndexInstanceNode, + widget=IndexInstanceNodeWidget ) - SourceColumn( - func=lambda context: index_instance_item_link(context['object']), - label=_('Level'), source=IndexInstanceNode - ) - SourceColumn( - func=lambda context: context['object'].get_descendants_count(), - label=_('Levels'), source=IndexInstanceNode - ) SourceColumn( func=lambda context: context[ 'object' - ].get_descendants_document_count( + ].get_item_count( user=context['request'].user - ), label=_('Documents'), source=IndexInstanceNode + ), include_label=True, label=_('Items'), source=IndexInstanceNode ) SourceColumn( @@ -174,35 +186,35 @@ class DocumentIndexingApp(MayanAppConfig): ) menu_facet.bind_links( - links=(link_document_index_list,), sources=(Document,) + links=(link_document_index_instance_list,), sources=(Document,) ) menu_list_facet.bind_links( links=( - link_acl_list, link_index_setup_document_types, - link_index_setup_view, + link_acl_list, link_index_template_document_types, + link_index_template_view, ), sources=(Index,) ) menu_object.bind_links( links=( - link_index_setup_edit, link_index_setup_delete + link_index_template_edit, link_index_template_delete ), sources=(Index,) ) menu_object.bind_links( links=( - link_template_node_create, link_template_node_edit, - link_template_node_delete + link_index_template_node_create, link_index_template_node_edit, + link_index_template_node_delete ), sources=(IndexTemplateNode,) ) menu_main.bind_links(links=(link_index_main_menu,), position=98) menu_secondary.bind_links( - links=(link_index_setup_list, link_index_setup_create), + links=(link_index_template_list, link_index_template_create), sources=( - Index, 'indexing:index_setup_list', - 'indexing:index_setup_create' + Index, 'indexing:index_template_list', + 'indexing:index_template_create' ) ) menu_setup.bind_links(links=(link_index_setup,)) - menu_tools.bind_links(links=(link_rebuild_index_instances,)) + menu_tools.bind_links(links=(link_index_instances_rebuild,)) post_delete.connect( dispatch_uid='document_indexing_handler_delete_empty', diff --git a/mayan/apps/document_indexing/forms.py b/mayan/apps/document_indexing/forms.py index c9e2455d7f..790a5e6e2e 100644 --- a/mayan/apps/document_indexing/forms.py +++ b/mayan/apps/document_indexing/forms.py @@ -4,30 +4,23 @@ from django import forms from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ -from mayan.apps.acls.models import AccessControlList from mayan.apps.common.classes import ModelProperty +from mayan.apps.common.forms import FilteredSelectionForm from mayan.apps.documents.models import Document from .models import Index, IndexTemplateNode from .permissions import permission_document_indexing_rebuild -class IndexListForm(forms.Form): - indexes = forms.ModelMultipleChoiceField( - help_text=_('Indexes to be queued for rebuilding.'), - label=_('Indexes'), queryset=Index.objects.none(), - required=False, widget=forms.widgets.CheckboxSelectMultiple() - ) - - def __init__(self, *args, **kwargs): - user = kwargs.pop('user') - super(IndexListForm, self).__init__(*args, **kwargs) - queryset = AccessControlList.objects.restrict_queryset( - permission=permission_document_indexing_rebuild, - queryset=Index.objects.filter(enabled=True), - user=user - ) - self.fields['indexes'].queryset = queryset +class IndexTemplateFilteredForm(FilteredSelectionForm): + class Meta: + allow_multiple = True + field_name = 'index_templates' + help_text = _('Index templates to be queued for rebuilding.') + label = _('Index templates') + queryset = Index.objects.filter(enabled=True) + permission = permission_document_indexing_rebuild + widget_attributes = {'class': 'select2'} class IndexTemplateNodeForm(forms.ModelForm): diff --git a/mayan/apps/document_indexing/widgets.py b/mayan/apps/document_indexing/html_widgets.py similarity index 52% rename from mayan/apps/document_indexing/widgets.py rename to mayan/apps/document_indexing/html_widgets.py index d55f834c27..803810d295 100644 --- a/mayan/apps/document_indexing/widgets.py +++ b/mayan/apps/document_indexing/html_widgets.py @@ -1,10 +1,33 @@ from __future__ import unicode_literals -from django.apps import apps -from django.utils.encoding import force_text +from django.template.loader import render_to_string from django.utils.html import escape, mark_safe -from .icons import icon_index, icon_index_level_up, icon_node_with_documents +from .icons import ( + icon_index, icon_index_level_up, + icon_index_instance_node_with_documents +) + + +class IndexInstanceNodeWidget(object): + def render(self, name, value): + return render_to_string( + template_name='document_indexing/index_instance_node.html', + context={ + 'index_instance_node': value, + } + ) + + +class IndexTemplateNodeIndentationWidget(object): + def render(self, name, value): + return render_to_string( + template_name='document_indexing/index_template_node_indentation.html', + context={ + 'index_template_node': value, + 'index_template_node_level': range(value.get_level()), + } + ) def get_instance_link(index_instance_node): @@ -19,45 +42,6 @@ def get_instance_link(index_instance_node): ) -def index_instance_item_link(index_instance_item): - #TODO: Replace with a file template - IndexInstanceNode = apps.get_model( - app_label='document_indexing', model_name='IndexInstanceNode' - ) - - if isinstance(index_instance_item, IndexInstanceNode): - if index_instance_item.index_template_node.link_documents: - icon = icon_node_with_documents.render() - else: - icon = icon_index_level_up.render() - else: - icon = '' - - return mark_safe( - s='%(icon)s %(text)s' % { - 'url': index_instance_item.get_absolute_url(), - 'icon': icon, - 'text': index_instance_item - } - ) - - -def node_level(node): - """ - Render an indented tree like output for a specific node - """ - #TODO: Replace with a file template - return mark_safe( - s=''.join( - [ - '     ' * node.get_level(), - '' if node.is_root_node() else icon_index_level_up.render(), - force_text(node) - ] - ) - ) - - def node_tree(node, user): #TODO: Replace with a file template result = [] @@ -71,7 +55,7 @@ def node_tree(node, user): else: element = ancestor if element.index_template_node.link_documents: - icon = icon_node_with_documents + icon = icon_index_instance_node_with_documents else: icon = icon_index_level_up diff --git a/mayan/apps/document_indexing/icons.py b/mayan/apps/document_indexing/icons.py index 9c6724aabb..8041935456 100644 --- a/mayan/apps/document_indexing/icons.py +++ b/mayan/apps/document_indexing/icons.py @@ -2,14 +2,37 @@ from __future__ import absolute_import, unicode_literals from mayan.apps.appearance.classes import Icon -icon_document_index_list = Icon(driver_name='fontawesome', symbol='list-ul') +icon_document_index_instance_list = Icon( + driver_name='fontawesome', symbol='list-ul' +) +icon_index = Icon(driver_name='fontawesome', symbol='list-ul') icon_index_level_up = Icon( driver_name='fontawesomecss', css_classes='fa-level-up-alt fa-rotate-90' ) -icon_index = Icon(driver_name='fontawesome', symbol='list-ul') -icon_index_create = Icon(driver_name='fontawesome', symbol='plus') -icon_index_setup_view = Icon(driver_name='fontawesome', symbol='folder-open') -icon_node_with_documents = Icon(driver_name='fontawesome', symbol='folder') -icon_rebuild_index_instances = Icon( +icon_index_instance_node_with_documents = Icon( + driver_name='fontawesome', symbol='folder' +) +icon_index_instances_rebuild = Icon( driver_name='fontawesome', symbol='list-ul' ) +icon_index_template_create = Icon( + driver_name='fontawesome-dual', primary_symbol='list-ul', + secondary_symbol='plus' +) + +icon_index_template_delete = Icon(driver_name='fontawesome', symbol='times') +icon_index_template_edit = Icon(driver_name='fontawesome', symbol='pencil-alt') +icon_index_template_list = Icon(driver_name='fontawesome', symbol='list-ul') + +icon_index_template_node_create = Icon( + driver_name='fontawesomecss', css_classes='fa-level-up-alt fa-rotate-90' +) + +icon_index_template_node_delete = Icon( + driver_name='fontawesome', symbol='times' +) +icon_index_template_node_edit = Icon( + driver_name='fontawesome', symbol='pencil-alt' +) + +icon_index_template_view = Icon(driver_name='fontawesome', symbol='folder-open') diff --git a/mayan/apps/document_indexing/links.py b/mayan/apps/document_indexing/links.py index cde0f1ab43..0ccce73e73 100644 --- a/mayan/apps/document_indexing/links.py +++ b/mayan/apps/document_indexing/links.py @@ -6,8 +6,11 @@ from mayan.apps.documents.icons import icon_document_type from mayan.apps.navigation import Link, get_cascade_condition from .icons import ( - icon_document_index_list, icon_index, icon_index_create, - icon_index_setup_view, icon_rebuild_index_instances + icon_document_index_instance_list, icon_index, icon_index_template_create, + icon_index_template_delete, icon_index_template_edit, + icon_index_template_list, icon_index_template_node_create, + icon_index_template_node_delete, icon_index_template_node_edit, + icon_index_template_view, icon_index_instances_rebuild ) from .permissions import ( permission_document_indexing_create, permission_document_indexing_delete, @@ -17,20 +20,30 @@ from .permissions import ( ) -def is_not_root_node(context): +def condition_is_not_root_node(context): return not context['resolved_object'].is_root_node() -link_document_index_list = Link( - args='resolved_object.pk', icon_class=icon_document_index_list, - text=_('Indexes'), view='indexing:document_index_list', +link_document_index_instance_list = Link( + icon_class=icon_document_index_instance_list, + kwargs={'document_id': 'resolved_object.pk'}, text=_('Indexes'), + view='indexing:document_index_instace_list', +) +link_index_instances_rebuild = Link( + condition=get_cascade_condition( + app_label='document_indexing', model_name='Index', + object_permission=permission_document_indexing_rebuild, + ), icon_class=icon_index_instances_rebuild, + description=_( + 'Deletes and creates from scratch all the document indexes.' + ), text=_('Rebuild indexes'), view='indexing:index_instances_rebuild' ) link_index_main_menu = Link( condition=get_cascade_condition( app_label='document_indexing', model_name='Index', object_permission=permission_document_indexing_instance_view, ), icon_class=icon_index, text=_('Indexes'), - view='indexing:index_list' + view='indexing:index_instance_list' ) link_index_setup = Link( condition=get_cascade_condition( @@ -38,55 +51,53 @@ link_index_setup = Link( object_permission=permission_document_indexing_view, view_permission=permission_document_indexing_create, ), icon_class=icon_index, text=_('Indexes'), - view='indexing:index_setup_list' + view='indexing:index_template_list' ) -link_index_setup_list = Link( - text=_('Indexes'), view='indexing:index_setup_list' +link_index_template_list = Link( + icon_class=icon_index_template_list, + text=_('Indexes'), view='indexing:index_template_list' ) -link_index_setup_create = Link( - icon_class=icon_index_create, +link_index_template_create = Link( + icon_class=icon_index_template_create, permission=permission_document_indexing_create, text=_('Create index'), - view='indexing:index_setup_create' + view='indexing:index_template_create' ) -link_index_setup_edit = Link( - args='resolved_object.pk', - permission=permission_document_indexing_edit, text=_('Edit'), - view='indexing:index_setup_edit', -) -link_index_setup_delete = Link( - args='resolved_object.pk', +link_index_template_delete = Link( + icon_class=icon_index_template_delete, + kwargs={'index_template_id': 'resolved_object.pk'}, permission=permission_document_indexing_delete, tags='dangerous', - text=_('Delete'), view='indexing:index_setup_delete', + text=_('Delete'), view='indexing:index_template_delete' ) -link_index_setup_view = Link( - args='resolved_object.pk', icon_class=icon_index_setup_view, +link_index_template_edit = Link( + icon_class=icon_index_template_edit, + kwargs={'index_template_id': 'resolved_object.pk'}, + permission=permission_document_indexing_edit, text=_('Edit'), + view='indexing:index_template_edit' +) +link_index_template_view = Link( + kwargs={'index_template_id': 'resolved_object.pk'}, icon_class=icon_index_template_view, permission=permission_document_indexing_edit, text=_('Tree template'), - view='indexing:index_setup_view', + view='indexing:index_template_view' ) -link_index_setup_document_types = Link( - args='resolved_object.pk', icon_class=icon_document_type, +link_index_template_document_types = Link( + kwargs={'index_template_id': 'resolved_object.pk'}, icon_class=icon_document_type, permission=permission_document_indexing_edit, text=_('Document types'), - view='indexing:index_setup_document_types', + view='indexing:index_template_document_types' ) -link_rebuild_index_instances = Link( - condition=get_cascade_condition( - app_label='document_indexing', model_name='Index', - object_permission=permission_document_indexing_rebuild, - ), icon_class=icon_rebuild_index_instances, - description=_( - 'Deletes and creates from scratch all the document indexes.' - ), - text=_('Rebuild indexes'), view='indexing:rebuild_index_instances' +link_index_template_node_create = Link( + icon_class=icon_index_template_node_create, + kwargs={'index_template_node_id': 'resolved_object.pk'}, + text=_('New node'), view='indexing:index_template_node_create' ) -link_template_node_create = Link( - args='resolved_object.pk', text=_('New child node'), - view='indexing:template_node_create', +link_index_template_node_delete = Link( + condition=condition_is_not_root_node, + icon_class=icon_index_template_node_delete, + kwargs={'index_template_node_id': 'resolved_object.pk'}, tags='dangerous', + text=_('Delete'), view='indexing:index_template_node_delete' ) -link_template_node_edit = Link( - args='resolved_object.pk', condition=is_not_root_node, text=_('Edit'), - view='indexing:template_node_edit', -) -link_template_node_delete = Link( - args='resolved_object.pk', condition=is_not_root_node, tags='dangerous', - text=_('Delete'), view='indexing:template_node_delete', +link_index_template_node_edit = Link( + condition=condition_is_not_root_node, + icon_class=icon_index_template_node_edit, + kwargs={'index_template_node_id': 'resolved_object.pk'}, text=_('Edit'), + view='indexing:index_template_node_edit' ) diff --git a/mayan/apps/document_indexing/models.py b/mayan/apps/document_indexing/models.py index ee339d71a1..d5f9fdc5cf 100644 --- a/mayan/apps/document_indexing/models.py +++ b/mayan/apps/document_indexing/models.py @@ -66,7 +66,7 @@ class Index(models.Model): try: return reverse( viewname='indexing:index_instance_node_view', - kwargs={'index_instance_node_pk': self.instance_root.pk} + kwargs={'index_instance_node_id': self.instance_root.pk} ) except IndexInstanceNode.DoesNotExist: return '#' @@ -183,7 +183,7 @@ class IndexTemplateNode(MPTTModel): ) index = models.ForeignKey( on_delete=models.CASCADE, related_name='node_templates', to=Index, - verbose_name=_('Index') + verbose_name=_('Index template') ) expression = models.TextField( help_text=_( @@ -356,7 +356,7 @@ class IndexInstanceNode(MPTTModel): def get_absolute_url(self): return reverse( viewname='indexing:index_instance_node_view', - kwargs={'index_instance_node_pk': self.pk} + kwargs={'index_instance_node_id': self.pk} ) def get_children_count(self): diff --git a/mayan/apps/document_indexing/tasks.py b/mayan/apps/document_indexing/tasks.py index 506c6e243b..f14db26da6 100644 --- a/mayan/apps/document_indexing/tasks.py +++ b/mayan/apps/document_indexing/tasks.py @@ -58,13 +58,13 @@ def task_index_document(self, document_id): @app.task(bind=True, default_retry_delay=RETRY_DELAY, ignore_result=True) -def task_rebuild_index(self, index_id): +def task_rebuild_index(self, index_template_id): Index = apps.get_model( app_label='document_indexing', model_name='Index' ) try: - index = Index.objects.get(pk=index_id) + index = Index.objects.get(pk=index_template_id) index.rebuild() except LockError as exception: # This index is being rebuilt by another task, retry later diff --git a/mayan/apps/document_indexing/templates/document_indexing/index_instance_node.html b/mayan/apps/document_indexing/templates/document_indexing/index_instance_node.html new file mode 100644 index 0000000000..edd1b16155 --- /dev/null +++ b/mayan/apps/document_indexing/templates/document_indexing/index_instance_node.html @@ -0,0 +1,13 @@ +{% load appearance_tags %} + +{% get_icon 'mayan.apps.document_indexing.icons.icon_index_instance_node_with_documents' as icon_index_instance_node_with_documents %} +{% get_icon 'mayan.apps.document_indexing.icons.icon_index_level_up' as icon_index_level_up %} + + + {% if index_instance_node.index_template_node.link_documents %} + {{ icon_index_instance_node_with_documents }} + {% else %} + {{ icon_index_level_up }} + {% endif %} + {{ index_instance_node }} + diff --git a/mayan/apps/document_indexing/templates/document_indexing/index_template_node_indentation.html b/mayan/apps/document_indexing/templates/document_indexing/index_template_node_indentation.html new file mode 100644 index 0000000000..53bd36a9e8 --- /dev/null +++ b/mayan/apps/document_indexing/templates/document_indexing/index_template_node_indentation.html @@ -0,0 +1,11 @@ +{% load appearance_tags %} + +{% get_icon 'mayan.apps.document_indexing.icons.icon_index_level_up' as icon_index_level_up %} + +{% for level in index_template_node_level %} +       +{% endfor %} +{% if not index_template_node.is_root_node %} + {{ icon_index_level_up }} +{% endif %} +{{ index_template_node }} diff --git a/mayan/apps/document_indexing/templates/document_indexing/node_details.html b/mayan/apps/document_indexing/templates/document_indexing/node_details.html index a1ac26b6bf..31567ea8da 100644 --- a/mayan/apps/document_indexing/templates/document_indexing/node_details.html +++ b/mayan/apps/document_indexing/templates/document_indexing/node_details.html @@ -20,5 +20,4 @@ {% endif %} - {% endblock %} diff --git a/mayan/apps/document_indexing/tests/literals.py b/mayan/apps/document_indexing/tests/literals.py index e58a75cb4b..96cf041a06 100644 --- a/mayan/apps/document_indexing/tests/literals.py +++ b/mayan/apps/document_indexing/tests/literals.py @@ -9,3 +9,5 @@ TEST_METADATA_VALUE = '0001' TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{{{ document.get_metadata("{}").value }}}}'.format(TEST_METADATA_TYPE_NAME) TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION = '{{ document.label }}' TEST_INDEX_TEMPLATE_DOCUMENT_DESCRIPTION_EXPRESSION = '{{ document.description }}' +TEST_INDEX_TEMPLATE_DOCUMENT_UUID_EXPRESSION = '{{ document.uuid }}' +TEST_INDEX_TEMPLATE_NODE_EXPRESSION_EDITED = 'expression edited' diff --git a/mayan/apps/document_indexing/tests/mixins.py b/mayan/apps/document_indexing/tests/mixins.py index 239b8de7dd..b0396dab28 100644 --- a/mayan/apps/document_indexing/tests/mixins.py +++ b/mayan/apps/document_indexing/tests/mixins.py @@ -2,13 +2,30 @@ from __future__ import unicode_literals from ..models import Index -from .literals import TEST_INDEX_LABEL +from .literals import ( + TEST_INDEX_LABEL, TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION +) -class DocumentIndexingTestMixin(object): - def _create_index(self): +class IndexTemplateTestMixin(object): + def _create_index_template(self, add_document_type=False): # Create empty index - self.index = Index.objects.create(label=TEST_INDEX_LABEL) + self.test_index_template = Index.objects.create(label=TEST_INDEX_LABEL) - # Add our document type to the new index - self.index.document_types.add(self.document_type) + if add_document_type: + # Add our document type to the new index + self.test_index_template.document_types.add(self.test_document_type) + + def _create_index_template_node(self, expression=None, rebuild=False): + self._create_index_template(add_document_type=True) + + expression = expression or TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION + + self.test_index_template_node = self.test_index_template.node_templates.create( + parent=self.test_index_template.template_root, + expression=expression, link_documents=True + ) + + # Rebuild indexes + if rebuild: + Index.objects.rebuild() diff --git a/mayan/apps/document_indexing/tests/test_models.py b/mayan/apps/document_indexing/tests/test_models.py index 3077e0dce3..acaff8e9a4 100644 --- a/mayan/apps/document_indexing/tests/test_models.py +++ b/mayan/apps/document_indexing/tests/test_models.py @@ -3,9 +3,7 @@ from __future__ import unicode_literals from django.utils.encoding import force_text from mayan.apps.common.tests import BaseTestCase -from mayan.apps.documents.tests import ( - TEST_SMALL_DOCUMENT_PATH, DocumentTestMixin -) +from mayan.apps.documents.tests import DocumentTestMixin from mayan.apps.documents.tests.literals import ( TEST_DOCUMENT_DESCRIPTION, TEST_DOCUMENT_DESCRIPTION_EDITED, TEST_DOCUMENT_LABEL_EDITED @@ -20,65 +18,54 @@ from .literals import ( TEST_INDEX_TEMPLATE_METADATA_EXPRESSION, TEST_METADATA_TYPE_LABEL, TEST_METADATA_TYPE_NAME, TEST_METADATA_VALUE ) -from .mixins import DocumentIndexingTestMixin +from .mixins import IndexTemplateTestMixin -class IndexTestCase(DocumentIndexingTestMixin, DocumentTestMixin, BaseTestCase): +class IndexTestCase(IndexTemplateTestMixin, DocumentTestMixin, BaseTestCase): def test_document_description_index(self): - self._create_index() - - self.index.node_templates.create( - parent=self.index.template_root, + self.test_document.description = TEST_DOCUMENT_DESCRIPTION + self.test_document.save() + self._create_index_template_node( expression=TEST_INDEX_TEMPLATE_DOCUMENT_DESCRIPTION_EXPRESSION, - link_documents=True + rebuild=True ) - self.document.description = TEST_DOCUMENT_DESCRIPTION - self.document.save() - - self.index.rebuild() - self.assertEqual( - IndexInstanceNode.objects.last().value, self.document.description + IndexInstanceNode.objects.last().value, self.test_document.description ) - self.document.description = TEST_DOCUMENT_DESCRIPTION_EDITED - self.document.save() + self.test_document.description = TEST_DOCUMENT_DESCRIPTION_EDITED + self.test_document.save() self.assertEqual( - IndexInstanceNode.objects.last().value, self.document.description + IndexInstanceNode.objects.last().value, self.test_document.description ) def test_document_label_index(self): - self._create_index() - - self.index.node_templates.create( - parent=self.index.template_root, + self._create_index_template_node( expression=TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION, - link_documents=True + rebuild=True ) - self.index.rebuild() - self.assertEqual( - IndexInstanceNode.objects.last().value, self.document.label + IndexInstanceNode.objects.last().value, self.test_document.label ) - self.document.label = TEST_DOCUMENT_LABEL_EDITED - self.document.save() + self.test_document.label = TEST_DOCUMENT_LABEL_EDITED + self.test_document.save() self.assertEqual( - IndexInstanceNode.objects.last().value, self.document.label + IndexInstanceNode.objects.last().value, self.test_document.label ) def test_date_based_index(self): - self._create_index() + self._create_index_template(add_document_type=True) - level_year = self.index.node_templates.create( - parent=self.index.template_root, + level_year = self.test_index_template.node_templates.create( + parent=self.test_index_template.template_root, expression='{{ document.date_added.year }}', link_documents=False ) - self.index.node_templates.create( + self.test_index_template.node_templates.create( parent=level_year, expression='{{ document.date_added.month }}', link_documents=True @@ -89,18 +76,18 @@ class IndexTestCase(DocumentIndexingTestMixin, DocumentTestMixin, BaseTestCase): self.document.delete() # Uploading a new should not trigger an error - document = self.upload_document() + self._create_document() self.assertEqual( - [instance.value for instance in IndexInstanceNode.objects.all().order_by('index_instance_node_pk')], + [instance.value for instance in IndexInstanceNode.objects.all()], [ - '', force_text(document.date_added.year), - force_text(document.date_added.month).zfill(2) + '', force_text(self.test_document.date_added.year), + force_text(self.test_document.date_added.month) ] ) self.assertTrue( - document in list(IndexInstanceNode.objects.order_by('index_instance_node_pk').last().documents.all()) + self.test_document in list(IndexInstanceNode.objects.last().documents.all()) ) def test_dual_level_dual_document_index(self): @@ -109,33 +96,33 @@ class IndexTestCase(DocumentIndexingTestMixin, DocumentTestMixin, BaseTestCase): values and two second levels with the same value but as separate children of each of the first levels. GitLab issue #391 """ - with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object: - self.document_2 = self.document_type.new_document( - file_object=file_object - ) + self.document_2 = self.upload_document() - self._create_index() + self._create_index_template(add_document_type=True) # Create simple index template - root = self.index.template_root - level_1 = self.index.node_templates.create( + root = self.test_index_template.template_root + level_1 = self.test_index_template.node_templates.create( parent=root, expression='{{ document.uuid }}', link_documents=False ) - self.index.node_templates.create( + self.test_index_template.node_templates.create( parent=level_1, expression='{{ document.label }}', link_documents=True ) Index.objects.rebuild() + # Typecast to sets to make sorting irrelevant in the comparison. self.assertEqual( - [instance.value for instance in IndexInstanceNode.objects.all().order_by('index_instance_node_pk')], - [ - '', force_text(self.document_2.uuid), self.document_2.label, - force_text(self.document.uuid), self.document.label - ] + set(IndexInstanceNode.objects.values_list('value', flat=True)), + set( + [ + '', force_text(self.document_2.uuid), self.document_2.label, + force_text(self.document.uuid), self.document.label + ] + ) ) def test_metadata_indexing(self): @@ -146,11 +133,11 @@ class IndexTestCase(DocumentIndexingTestMixin, DocumentTestMixin, BaseTestCase): document_type=self.document_type, metadata_type=metadata_type ) - self._create_index() + self._create_index_template(add_document_type=True) # Create simple index template - root = self.index.template_root - self.index.node_templates.create( + root = self.test_index_template.template_root + self.test_index_template.node_templates.create( parent=root, expression=TEST_INDEX_TEMPLATE_METADATA_EXPRESSION, link_documents=True ) @@ -231,15 +218,15 @@ class IndexTestCase(DocumentIndexingTestMixin, DocumentTestMixin, BaseTestCase): On a two level template if the first level doesn't return a result the indexing should stop. GitLab issue #391. """ - self._create_index() + self._create_index_template(add_document_type=True) - level_1 = self.index.node_templates.create( - parent=self.index.template_root, + level_1 = self.test_index_template.node_templates.create( + parent=self.test_index_template.template_root, expression='', link_documents=True ) - self.index.node_templates.create( + self.test_index_template.node_templates.create( parent=level_1, expression='{{ document.label }}', link_documents=True ) @@ -260,11 +247,11 @@ class IndexTestCase(DocumentIndexingTestMixin, DocumentTestMixin, BaseTestCase): metadata_type=metadata_type, value=TEST_METADATA_VALUE ) - self._create_index() + self._create_index_template(add_document_type=True) # Create simple index template - root = self.index.template_root - self.index.node_templates.create( + root = self.test_index_template.template_root + self.test_index_template.node_templates.create( parent=root, expression=TEST_INDEX_TEMPLATE_METADATA_EXPRESSION, link_documents=True ) diff --git a/mayan/apps/document_indexing/tests/test_views.py b/mayan/apps/document_indexing/tests/test_views.py index 56fcd49c6f..9fee007312 100644 --- a/mayan/apps/document_indexing/tests/test_views.py +++ b/mayan/apps/document_indexing/tests/test_views.py @@ -1,5 +1,8 @@ from __future__ import absolute_import, unicode_literals +from mayan.apps.documents.permissions import ( + permission_document_view, permission_document_type_view +) from mayan.apps.documents.tests import GenericDocumentViewTestCase from ..models import Index @@ -7,23 +10,213 @@ from ..permissions import ( permission_document_indexing_create, permission_document_indexing_delete, permission_document_indexing_edit, permission_document_indexing_instance_view, - permission_document_indexing_rebuild + permission_document_indexing_rebuild, permission_document_indexing_view ) from .literals import ( TEST_INDEX_LABEL, TEST_INDEX_LABEL_EDITED, TEST_INDEX_SLUG, - TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION + TEST_INDEX_TEMPLATE_DOCUMENT_UUID_EXPRESSION, + TEST_INDEX_TEMPLATE_NODE_EXPRESSION_EDITED ) +from .mixins import IndexTemplateTestMixin -class IndexViewTestCase(GenericDocumentViewTestCase): - def setUp(self): - super(IndexViewTestCase, self).setUp() - self.login_user() +class IndexInstanceViewTestCase(IndexTemplateTestMixin, GenericDocumentViewTestCase): + def _request_index_instance_list_view(self): + return self.get( + viewname='indexing:index_instance_list', + ) + + def test_index_instance_list_view_no_permission(self): + self._create_index_template_node() + + response = self._request_index_instance_list_view() + + self.assertNotContains( + response=response, text=TEST_INDEX_LABEL, status_code=200 + ) + + def test_index_instance_list_view_with_access(self): + self._create_index_template_node(rebuild=True) + + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_instance_view + ) + + response = self._request_index_instance_list_view() + + self.assertContains( + response=response, text=TEST_INDEX_LABEL, status_code=200 + ) + + def _request_index_instance_node_view(self, index_instance_node=None): + index_instance_node = index_instance_node or self.test_index_template.instance_root + + return self.get( + viewname='indexing:index_instance_node_view', + kwargs={ + 'index_instance_node_id': index_instance_node.pk + } + ) + + def test_index_instance_node_view_no_permission(self): + self._create_index_template_node() + + response = self._request_index_instance_node_view() + + self.assertEqual(response.status_code, 404) + + def test_index_instance_node_view_with_access(self): + self._create_index_template_node() + + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_instance_view + ) + + response = self._request_index_instance_node_view() + + self.assertContains( + response=response, text=TEST_INDEX_LABEL, status_code=200 + ) + + def _test_index_instance_node_documents_view_base(self, index_access=False, document_access=False): + self._create_index_template_node( + expression=TEST_INDEX_TEMPLATE_DOCUMENT_UUID_EXPRESSION, + rebuild=True + ) + + if index_access: + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_instance_view + ) + + if document_access: + self.grant_access( + obj=self.test_document, + permission=permission_document_view + ) + + index_instance_node = self.test_index_template.instance_root.get_children().get( + value=self.test_document.uuid + ) + + return self._request_index_instance_node_view( + index_instance_node=index_instance_node + ) + + def test_index_instance_node_documents_view_no_permission(self): + response = self._test_index_instance_node_documents_view_base() + + self.assertNotContains( + response=response, text=TEST_INDEX_LABEL, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_document.label, status_code=404 + ) + + def test_index_instance_node_documents_view_with_index_access(self): + response = self._test_index_instance_node_documents_view_base( + index_access=True + ) + + self.assertContains( + response=response, text=TEST_INDEX_LABEL, status_code=200 + ) + self.assertNotContains( + response=response, text=self.test_document.label, status_code=200 + ) + + def test_index_instance_node_documents_view_with_document_access(self): + response = self._test_index_instance_node_documents_view_base( + document_access=True + ) + + self.assertNotContains( + response=response, text=TEST_INDEX_LABEL, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_document.label, status_code=404 + ) + + def test_index_instance_node_documents_view_with_full_access(self): + response = self._test_index_instance_node_documents_view_base( + index_access=True, document_access=True + ) + + self.assertContains( + response=response, text=TEST_INDEX_LABEL, status_code=200 + ) + self.assertContains( + response=response, text=self.test_document.label, status_code=200 + ) + + def _request_index_instances_rebuild_get_view(self): + return self.get( + viewname='indexing:index_instances_rebuild', + ) + + def _request_index_instances_rebuild_post_view(self): + return self.post( + viewname='indexing:index_instances_rebuild', data={ + 'index_templates': self.test_index_template.pk + } + ) + + def test_index_instances_rebuild_get_view_no_permission(self): + self._create_index_template_node(rebuild=False) + + response = self._request_index_instances_rebuild_get_view() + self.assertNotContains( + response=response, text=self.test_index_template.label, status_code=200 + ) + + def test_index_instances_rebuild_get_view_with_access(self): + self._create_index_template_node(rebuild=False) + + self.grant_access( + obj=self.test_index_template, permission=permission_document_indexing_rebuild + ) + + response = self._request_index_instances_rebuild_get_view() + self.assertContains( + response=response, text=self.test_index_template.label, status_code=200 + ) + + def test_index_instances_rebuild_post_view_no_permission(self): + self._create_index_template_node(rebuild=False) + + response = self._request_index_instances_rebuild_post_view() + # No error since we just don't see the index + self.assertEqual(response.status_code, 200) + + self.assertEqual( + self.test_index_template.instance_root.get_children_count(), 0 + ) + + def test_index_instances_rebuild_post_view_with_access(self): + self._create_index_template_node(rebuild=False) + + self.grant_access( + obj=self.test_index_template, permission=permission_document_indexing_rebuild + ) + + response = self._request_index_instances_rebuild_post_view() + self.assertEqual(response.status_code, 302) + + # An instance root exists + self.assertTrue(self.test_index_template.instance_root.pk) + + +class IndexTemplateViewTestCase(IndexTemplateTestMixin, GenericDocumentViewTestCase): + auto_create_document_type = False + auto_upload_document = False def _request_index_create_view(self): return self.post( - 'indexing:index_setup_create', data={ + 'indexing:index_template_create', data={ 'label': TEST_INDEX_LABEL, 'slug': TEST_INDEX_SLUG } ) @@ -31,6 +224,7 @@ class IndexViewTestCase(GenericDocumentViewTestCase): def test_index_create_view_no_permission(self): response = self._request_index_create_view() self.assertEquals(response.status_code, 403) + self.assertEqual(Index.objects.count(), 0) def test_index_create_view_with_permission(self): @@ -41,159 +235,218 @@ class IndexViewTestCase(GenericDocumentViewTestCase): response = self._request_index_create_view() self.assertEqual(response.status_code, 302) + self.assertEqual(Index.objects.count(), 1) self.assertEqual(Index.objects.first().label, TEST_INDEX_LABEL) - def _request_index_delete_view(self, index): + def _request_index_template_delete_view(self): return self.post( - viewname='indexing:index_setup_delete', - kwargs={'index_pk': index.pk} + viewname='indexing:index_template_delete', + kwargs={'index_template_id': self.test_index_template.pk} ) - def test_index_delete_view_no_permission(self): - index = Index.objects.create( - label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG - ) + def test_index_template_delete_view_no_permission(self): + self._create_index_template() - response = self._request_index_delete_view(index=index) + response = self._request_index_template_delete_view() self.assertEqual(response.status_code, 404) + self.assertEqual(Index.objects.count(), 1) - def test_index_delete_view_with_permission(self): - self.role.permissions.add( - permission_document_indexing_delete.stored_permission + def test_index_template_delete_view_with_access(self): + self._create_index_template() + + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_delete ) - - index = Index.objects.create( - label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG - ) - - response = self._request_index_delete_view(index=index) - + response = self._request_index_template_delete_view() self.assertEqual(response.status_code, 302) + self.assertEqual(Index.objects.count(), 0) - def _request_index_edit_view(self, index): + def _request_index_template_document_types_view(self): return self.post( - viewname='indexing:index_setup_edit', kwargs={ - 'index_pk': index.pk + viewname='indexing:index_template_document_types', + kwargs={'index_template_id': self.test_index_template.pk} + ) + + def test_index_template_document_types_view_no_permission(self): + self._create_document_type() + self._create_index_template() + + response = self._request_index_template_document_types_view() + self.assertEqual(response.status_code, 404) + + def test_index_template_document_types_view_with_index_access(self): + self._create_document_type() + self._create_index_template() + + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_edit + ) + response = self._request_index_template_document_types_view() + self.assertNotContains( + response=response, text=self.document_type.label, status_code=200 + ) + + def test_index_template_document_types_view_with_document_type_access(self): + self._create_document_type() + self._create_index_template() + + self.grant_access( + obj=self.test_document_type, + permission=permission_document_type_view + ) + response = self._request_index_template_document_types_view() + self.assertEqual(response.status_code, 404) + + def test_index_template_document_types_view_with_full_access(self): + self._create_document_type() + self._create_index_template() + + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_edit + ) + self.grant_access( + obj=self.test_document_type, + permission=permission_document_type_view + ) + response = self._request_index_template_document_types_view() + self.assertContains( + response=response, text=self.document_type.label, status_code=200 + ) + + def _request_index_edit_view(self): + return self.post( + viewname='indexing:index_template_edit', kwargs={ + 'index_template_id': self.test_index_template.pk }, data={ 'label': TEST_INDEX_LABEL_EDITED, 'slug': TEST_INDEX_SLUG } ) def test_index_edit_view_no_permission(self): - index = Index.objects.create( - label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG - ) + self._create_index_template() - response = self._request_index_edit_view(index=index) + response = self._request_index_edit_view() self.assertEqual(response.status_code, 404) - index = Index.objects.get(pk=index.pk) - self.assertEqual(index.label, TEST_INDEX_LABEL) + + self.test_index_template.refresh_from_db() + self.assertEqual(self.test_index_template.label, TEST_INDEX_LABEL) def test_index_edit_view_with_access(self): - index = Index.objects.create( - label=TEST_INDEX_LABEL, slug=TEST_INDEX_SLUG - ) + self._create_index_template() self.grant_access( - permission=permission_document_indexing_edit, - obj=index + obj=self.test_index_template, + permission=permission_document_indexing_edit ) - response = self._request_index_edit_view(index=index) + response = self._request_index_edit_view() self.assertEqual(response.status_code, 302) - index.refresh_from_db() - self.assertEqual(index.label, TEST_INDEX_LABEL_EDITED) - def _create_index(self, rebuild=True): - # Create empty index - self.index = Index.objects.create(label=TEST_INDEX_LABEL) + self.test_index_template.refresh_from_db() + self.assertEqual(self.test_index_template.label, TEST_INDEX_LABEL_EDITED) - # Add our document type to the new index - self.index.document_types.add(self.document_type) + def _request_index_template_list_view(self): + return self.get(viewname='indexing:index_template_list') - # Create simple index template - root = self.index.template_root - self.index.node_templates.create( - parent=root, expression=TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION, - link_documents=True + def test_index_template_list_view_no_permission(self): + self._create_index_template() + + response = self._request_index_template_list_view() + self.assertNotContains( + response=response, text=self.test_index_template.label, status_code=200 ) - # Rebuild indexes - if rebuild: - Index.objects.rebuild() - - def _request_index_instance_node_view(self, index_instance_node): - return self.get( - viewname='indexing:index_instance_node_view', - kwargs={'index_instance_node_pk': index_instance_node.pk} - ) - - def test_index_instance_node_view_no_permission(self): - self._create_index() - - response = self._request_index_instance_node_view( - index_instance_node=self.index.instance_root - ) - - self.assertEqual(response.status_code, 403) - - def test_index_instance_node_view_with_access(self): - self._create_index() + def test_index_template_list_view_with_access(self): + self._create_index_template() self.grant_access( - permission=permission_document_indexing_instance_view, - obj=self.index - ) - - response = self._request_index_instance_node_view( - index_instance_node=self.index.instance_root + obj=self.test_index_template, + permission=permission_document_indexing_view ) + response = self._request_index_template_list_view() self.assertContains( - response=response, text=TEST_INDEX_LABEL, status_code=200 + response=response, text=self.test_index_template.label, status_code=200 ) - def _request_index_rebuild_get_view(self): - return self.get( - viewname='indexing:rebuild_index_instances', - ) - def _request_index_rebuild_post_view(self): +class IndexTemplaceNodeViewTestCase(IndexTemplateTestMixin, GenericDocumentViewTestCase): + auto_upload_document = False + + def _request_index_instance_node_delete_view(self): return self.post( - viewname='indexing:rebuild_index_instances', data={ - 'indexes': self.index.pk + viewname='indexing:index_template_node_delete', kwargs={ + 'index_template_node_id': self.test_index_template_node.pk } ) - def test_index_rebuild_no_permission(self): - self._create_index(rebuild=False) + def test_index_template_node_delete_view_no_permission(self): + self._create_index_template_node() - response = self._request_index_rebuild_get_view() - self.assertNotContains(response=response, text=self.index.label, status_code=200) + response = self._request_index_instance_node_delete_view() - response = self._request_index_rebuild_post_view() - # No error since we just don't see the index - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 404) - self.assertEqual( - self.index.instance_root.get_children_count(), 0 - ) + # Root node plus the defaul test document label node + self.assertEqual(self.test_index_template.node_templates.count(), 2) - def test_index_rebuild_with_access(self): - self._create_index(rebuild=False) + def test_index_template_node_delete_view_with_access(self): + self._create_index_template_node() self.grant_access( - permission=permission_document_indexing_rebuild, obj=self.index + obj=self.test_index_template, + permission=permission_document_indexing_edit ) - response = self._request_index_rebuild_get_view() - self.assertContains(response=response, text=self.index.label, status_code=200) + response = self._request_index_instance_node_delete_view() - response = self._request_index_rebuild_post_view() self.assertEqual(response.status_code, 302) - # An instance root exists - self.assertTrue(self.index.instance_root.pk) + # Root node only + self.assertEqual(self.test_index_template.node_templates.count(), 1) + + def _request_index_instance_node_edit_view(self): + return self.post( + viewname='indexing:index_template_node_edit', kwargs={ + 'index_template_node_id': self.test_index_template_node.pk + }, data={ + 'expression': TEST_INDEX_TEMPLATE_NODE_EXPRESSION_EDITED, + 'index': self.test_index_template.pk, + 'parent': self.test_index_template_node.parent.pk, + + } + ) + + def test_index_template_node_edit_view_no_permission(self): + self._create_index_template_node() + original_expression = self.test_index_template_node.expression + + response = self._request_index_instance_node_edit_view() + self.assertEqual(response.status_code, 404) + + self.test_index_template_node.refresh_from_db() + self.assertEqual( + self.test_index_template_node.expression, original_expression + ) + + def test_index_template_node_edit_view_with_access(self): + self._create_index_template_node() + self.grant_access( + obj=self.test_index_template, + permission=permission_document_indexing_edit + ) + + response = self._request_index_instance_node_edit_view() + self.assertEqual(response.status_code, 302) + + self.test_index_template_node.refresh_from_db() + self.assertEqual( + self.test_index_template_node.expression, + TEST_INDEX_TEMPLATE_NODE_EXPRESSION_EDITED + ) diff --git a/mayan/apps/document_indexing/urls.py b/mayan/apps/document_indexing/urls.py index 5aa96bed20..0d97e64a6e 100644 --- a/mayan/apps/document_indexing/urls.py +++ b/mayan/apps/document_indexing/urls.py @@ -2,101 +2,109 @@ from __future__ import unicode_literals from django.conf.urls import url +""" from .api_views import ( - APIDocumentIndexListView, APIIndexListView, + APIDocumentIndexTemplateListView, APIIndexTemplateListView, APIIndexNodeInstanceDocumentListView, APIIndexTemplateListView, APIIndexTemplateView, APIIndexView ) +""" from .views import ( - DocumentIndexNodeListView, IndexInstanceNodeView, IndexListView, - RebuildIndexesView, SetupIndexCreateView, SetupIndexDeleteView, - SetupIndexDocumentTypesView, SetupIndexEditView, SetupIndexListView, - SetupIndexTreeTemplateListView, TemplateNodeCreateView, - TemplateNodeDeleteView, TemplateNodeEditView + DocumentIndexInstanceNodeListView, IndexInstanceNodeView, IndexInstancesRebuildView, + IndexInstanceView, IndexTemplateCreateView, IndexTemplateDeleteView, + IndexTemplateDocumentTypesView, IndexTemplateEditView, + IndexTemplateListView, IndexTemplateNodeCreateView, + IndexTemplateNodeDeleteView, IndexTemplateNodeEditView, + IndexTemplateNodeListView ) urlpatterns = [ url( - regex=r'^indexes/$', name='index_setup_list', - view=SetupIndexListView.as_view() + regex=r'^documents/(?P\d+)/indexes/$', + name='document_index_instance_list', + view=DocumentIndexInstanceNodeListView.as_view() ), url( - regex=r'^indexes/create/$', name='index_setup_create', - view=SetupIndexCreateView.as_view() + regex=r'^index_instances/$', name='index_instance_list', + view=IndexInstanceView.as_view() ), url( - regex=r'^indexes/(?P\d+)/delete/$', - name='index_setup_delete', view=SetupIndexDeleteView.as_view() - ), - url( - regex=r'^indexes/(?P\d+)/edit/$', - name='index_setup_edit', view=SetupIndexEditView.as_view() - ), - url( - regex=r'^indexes/(?P\d+)/templates/$', - name='index_setup_view', view=SetupIndexTreeTemplateListView.as_view() - ), - url( - regex=r'^indexes/(?P\d+)/document_types/$', - name='index_setup_document_types', - view=SetupIndexDocumentTypesView.as_view() - ), - url( - regex=r'^indexes/templates/nodes/(?P\d+)/create/child/$', - name='template_node_create', view=TemplateNodeCreateView.as_view() - ), - url( - regex=r'^indexes/templates/nodes/(?P\d+)/edit/$', - name='template_node_edit', view=TemplateNodeEditView.as_view() - ), - url( - regex=r'^indexes/templates/nodes/(?P\d+)/delete/$', - name='template_node_delete', view=TemplateNodeDeleteView.as_view() - ), - url( - regex=r'^indexes/instances/list/$', name='index_list', - view=IndexListView.as_view() - ), - url( - regex=r'^indexes/instances/node/(?P\d+)/$', + regex=r'^index_instances/nodes/(?P\d+)/$', name='index_instance_node_view', view=IndexInstanceNodeView.as_view() ), - url( - regex=r'^indexes/rebuild/$', name='rebuild_index_instances', - view=RebuildIndexesView.as_view() + regex=r'^index_instances/rebuild/$', name='index_instances_rebuild', + view=IndexInstancesRebuildView.as_view() ), url( - regex=r'^documents/(?P\d+)/indexes/$', - name='document_index_list', view=DocumentIndexNodeListView.as_view() + regex=r'^index_templates/$', name='index_template_list', + view=IndexTemplateListView.as_view() ), + url( + regex=r'^index_templates/create/$', name='index_template_create', + view=IndexTemplateCreateView.as_view() + ), + url( + regex=r'^index_templates/(?P\d+)/delete/$', + name='index_template_delete', view=IndexTemplateDeleteView.as_view() + ), + url( + regex=r'^index_templates/(?P\d+)/document_types/$', + name='index_template_document_types', + view=IndexTemplateDocumentTypesView.as_view() + ), + url( + regex=r'^index_templates/(?P\d+)/edit/$', + name='index_template_edit', view=IndexTemplateEditView.as_view() + ), + url( + regex=r'^index_templates/(?P\d+)/nodes/$', + name='index_template_view', view=IndexTemplateNodeListView.as_view() + ), + url( + regex=r'^index_templates/nodes/(?P\d+)/create/$', + name='index_template_node_create', + view=IndexTemplateNodeCreateView.as_view() + ), + url( + regex=r'^index_templates/nodes/(?P\d+)/delete/$', + name='index_template_node_delete', + view=IndexTemplateNodeDeleteView.as_view() + ), + url( + regex=r'^index_templates/nodes/(?P\d+)/edit/$', + name='index_template_node_edit', + view=IndexTemplateNodeEditView.as_view() + ) ] +""" api_urls = [ url( - regex=r'^indexes/nodes/(?P[0-9]+)/documents/$', + regex=r'^index_templates/nodes/(?P\d+)/documents/$', name='index-node-documents', view=APIIndexNodeInstanceDocumentListView.as_view(), ), url( - regex=r'^indexes/templates/(?P[0-9]+)/$', + regex=r'^index_templates/templates/(?P\d+)/$', name='index-template-detail', view=APIIndexTemplateView.as_view() ), url( - regex=r'^indexes/(?P\d+)/$', name='index-detail', + regex=r'^index_templates/(?P\d+)/$', name='index-detail', view=APIIndexView.as_view() ), url( - regex=r'^indexes/(?P\d+)/templates/$', + regex=r'^index_templates/(?P\d+)/templates/$', name='index-template-detail', view=APIIndexTemplateListView.as_view() ), url( - regex=r'^indexes/$', name='index-list', - view=APIIndexListView.as_view() + regex=r'^index_templates/$', name='index-list', + view=APIIndexTemplateListView.as_view() ), url( regex=r'^documents/(?P\d+)/indexes/$', name='document-index-list', - view=APIDocumentIndexListView.as_view() + view=APIDocumentIndexTemplateListView.as_view() ), ] +""" diff --git a/mayan/apps/document_indexing/views.py b/mayan/apps/document_indexing/views.py index 0de438b29d..57158d956d 100644 --- a/mayan/apps/document_indexing/views.py +++ b/mayan/apps/document_indexing/views.py @@ -1,7 +1,6 @@ from __future__ import absolute_import, unicode_literals from django.contrib import messages -from django.shortcuts import get_object_or_404 from django.template import RequestContext from django.urls import reverse, reverse_lazy from django.utils.html import mark_safe @@ -13,13 +12,15 @@ from mayan.apps.common.generics import ( AssignRemoveView, FormView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView ) +from mayan.apps.common.mixins import ExternalObjectMixin from mayan.apps.documents.models import Document, DocumentType -from mayan.apps.documents.permissions import permission_document_view +from mayan.apps.documents.permissions import permission_document_type_view from mayan.apps.documents.views import DocumentListView -from .forms import IndexListForm, IndexTemplateNodeForm +from .forms import IndexTemplateFilteredForm, IndexTemplateNodeForm +from .html_widgets import node_tree from .icons import icon_index -from .links import link_index_setup_create +from .links import link_index_template_create from .models import ( DocumentIndexInstanceNode, Index, IndexInstance, IndexInstanceNode, IndexTemplateNode @@ -31,218 +32,49 @@ from .permissions import ( permission_document_indexing_view ) from .tasks import task_rebuild_index -from .widgets import node_tree -# Setup views -class SetupIndexCreateView(SingleObjectCreateView): - extra_context = {'title': _('Create index')} - fields = ('label', 'slug', 'enabled') - model = Index - post_action_redirect = reverse_lazy(viewname='indexing:index_setup_list') - view_permission = permission_document_indexing_create - - -class SetupIndexDeleteView(SingleObjectDeleteView): - model = Index - object_permission = permission_document_indexing_delete - pk_url_kwarg = 'index_pk' - post_action_redirect = reverse_lazy(viewname='indexing:index_setup_list') - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'title': _('Delete the index: %s?') % self.get_object(), - } - - -class SetupIndexEditView(SingleObjectEditView): - fields = ('label', 'slug', 'enabled') - model = Index - object_permission = permission_document_indexing_edit - pk_url_kwarg = 'index_pk' - post_action_redirect = reverse_lazy(viewname='indexing:index_setup_list') - - 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 +class DocumentIndexInstanceNodeListView(ExternalObjectMixin, SingleObjectListView): + """ + Show a list of indexes where the current document can be found + """ + external_object_class = Document + external_object_permission = permission_document_indexing_instance_view + external_object_pk_url_kwarg = 'document_id' + object_permission = permission_document_indexing_instance_view def get_extra_context(self): return { 'hide_object': True, 'no_results_icon': icon_index, - 'no_results_main_link': link_index_setup_create.resolve( - context=RequestContext(request=self.request) - ), 'no_results_text': _( - 'Indexes group document automatically into levels. Indexe are ' - 'defined using template whose markers are replaced with ' - 'direct properties of documents like label or description, or ' - 'that of extended properties like metadata.' + 'Assign the document type of this document ' + 'to an index to have it appear in instances of ' + 'those indexes organization units. ' ), - 'no_results_title': _('There are no indexes.'), - '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): - return AccessControlList.objects.restrict_queryset( - permission=permission_document_view, - queryset=DocumentType.objects.all(), user=self.request.user - ) - - def get_extra_context(self): - return { - 'object': self.get_object(), - 'subtitle': _( - 'Only the documents of the types selected will be shown ' - 'in the index when built. Only the events of the documents ' - 'of the types select will trigger updates in the index.' + 'no_results_title': _( + 'This document is not in any index instance' ), + 'object': self.external_object, 'title': _( - 'Document types linked to index: %s' - ) % self.get_object() + 'Index instance nodes containing document: %s' + ) % self.external_object, } - def get_object(self): - return get_object_or_404(klass=Index, pk=self.kwargs['index_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( - choices=self.get_document_queryset() & self.get_object().document_types.all() - ) - - -class SetupIndexTreeTemplateListView(SingleObjectListView): - object_permission = permission_document_indexing_edit - - def get_extra_context(self): - return { - 'hide_object': True, - 'index': self.get_index(), - 'navigation_object_list': ('index',), - 'title': _('Tree template nodes for index: %s') % self.get_index(), - } - - def get_index(self): - return get_object_or_404(klass=Index, pk=self.kwargs['index_pk']) - def get_source_queryset(self): - return self.get_index().template_root.get_descendants( - include_self=True + return DocumentIndexInstanceNode.objects.get_for( + document=self.external_object ) -class TemplateNodeCreateView(SingleObjectCreateView): - form_class = IndexTemplateNodeForm - model = IndexTemplateNode - - def dispatch(self, request, *args, **kwargs): - AccessControlList.objects.check_access( - obj=self.get_parent_node().index, - permission=permission_document_indexing_edit, user=request.user - ) - - return super( - TemplateNodeCreateView, self - ).dispatch(request=request, *args, **kwargs) - - def get_extra_context(self): - return { - '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 - } - - def get_parent_node(self): - return get_object_or_404( - klass=IndexTemplateNode, pk=self.kwargs['index_template_node_pk'] - ) - - -class TemplateNodeDeleteView(SingleObjectDeleteView): - model = IndexTemplateNode - object_permission = permission_document_indexing_edit - - def get_extra_context(self): - return { - 'index': self.get_object().index, - 'navigation_object_list': ('index', 'node'), - 'node': self.get_object(), - 'title': _( - 'Delete the index template node: %s?' - ) % self.get_object(), - } - - def get_post_action_redirect(self): - return reverse( - viewname='indexing:index_setup_view', - kwargs={'index_pk': self.get_object().index.pk} - ) - - -class TemplateNodeEditView(SingleObjectEditView): - form_class = IndexTemplateNodeForm - model = IndexTemplateNode - object_permission = permission_document_indexing_edit - - 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( - viewname='indexing:index_setup_view', - kwargs={'index_pk': self.get_object().index.pk} - ) - - -class IndexListView(SingleObjectListView): +class IndexInstanceView(SingleObjectListView): object_permission = permission_document_indexing_instance_view def get_extra_context(self): return { 'hide_links': True, 'no_results_icon': icon_index, - 'no_results_main_link': link_index_setup_create.resolve( + 'no_results_main_link': link_index_template_create.resolve( context=RequestContext(request=self.request) ), 'no_results_text': _( @@ -261,53 +93,33 @@ class IndexListView(SingleObjectListView): ).distinct() -class IndexInstanceNodeView(DocumentListView): +class IndexInstanceNodeView(ExternalObjectMixin, DocumentListView): + external_object_class = IndexInstanceNode + external_object_permission = permission_document_indexing_instance_view + external_object_pk_url_kwarg = 'index_instance_node_id' template_name = 'document_indexing/node_details.html' - def dispatch(self, request, *args, **kwargs): - self.index_instance_node = get_object_or_404( - klass=IndexInstanceNode, pk=self.kwargs['index_instance_node_pk'] - ) - - AccessControlList.objects.check_access( - obj=self.index_instance_node.index(), - permission=permission_document_indexing_instance_view, - user=request.user - ) - - if self.index_instance_node: - if self.index_instance_node.index_template_node.link_documents: - return super(IndexInstanceNodeView, self).dispatch( - request=request, *args, **kwargs - ) - - return SingleObjectListView.dispatch( - self, request=request, *args, **kwargs - ) - - def get_document_queryset(self): - if self.index_instance_node: - if self.index_instance_node.index_template_node.link_documents: - return self.index_instance_node.documents.all() - def get_extra_context(self): context = super(IndexInstanceNodeView, self).get_extra_context() + if not self.external_object.index_template_node.link_documents: + context.pop('table_cell_container_classes', None) + context.update( { 'column_class': 'col-xs-12 col-sm-6 col-md-4 col-lg-3', - 'object': self.index_instance_node, + 'object': self.external_object, 'navigation': mark_safe( _('Navigation: %s') % node_tree( - node=self.index_instance_node, user=self.request.user + node=self.external_object, user=self.request.user ) ), 'title': _( - 'Contents for index: %s' - ) % self.index_instance_node.get_full_path(), + 'Contents for index instance: %s' + ) % self.external_object.get_full_path(), } ) - if self.index_instance_node and not self.index_instance_node.index_template_node.link_documents: + if not self.external_object.index_template_node.link_documents: context.update( { 'hide_object': True, @@ -318,89 +130,39 @@ class IndexInstanceNodeView(DocumentListView): return context def get_source_queryset(self): - if self.index_instance_node: - if self.index_instance_node.index_template_node.link_documents: - return super(IndexInstanceNodeView, self).get_source_queryset() - else: - self.object_permission = None - return self.index_instance_node.get_children().order_by( - 'value' - ) + if self.external_object.index_template_node.link_documents: + return self.external_object.documents.all() else: - self.object_permission = None - return IndexInstanceNode.objects.none() + return self.external_object.get_children().order_by( + 'value' + ) -class DocumentIndexNodeListView(SingleObjectListView): - """ - Show a list of indexes where the current document can be found - """ - object_permission = permission_document_indexing_instance_view - - def dispatch(self, request, *args, **kwargs): - AccessControlList.objects.check_access( - obj=self.get_document(), permission=permission_document_view, - user=request.user - ) - - return super( - DocumentIndexNodeListView, self - ).dispatch(request=request, *args, **kwargs) - - def get_document(self): - return get_object_or_404( - klass=Document, pk=self.kwargs['document_pk'] - ) - - def get_extra_context(self): - return { - 'hide_object': True, - 'no_results_icon': icon_index, - 'no_results_text': _( - 'Assign the document type of this document ' - 'to an index to have it appear in instances of ' - 'those indexes organization units. ' - ), - 'no_results_title': _( - 'This document is not in any index' - ), - 'object': self.get_document(), - 'title': _( - 'Indexes nodes containing document: %s' - ) % self.get_document(), - } - - def get_source_queryset(self): - return DocumentIndexInstanceNode.objects.get_for( - document=self.get_document() - ) - - -class RebuildIndexesView(FormView): +class IndexInstancesRebuildView(FormView): extra_context = { - 'title': _('Rebuild indexes'), + 'title': _('Rebuild index instances'), } - form_class = IndexListForm + form_class = IndexTemplateFilteredForm def form_valid(self, form): count = 0 - for index in form.cleaned_data['indexes']: + for index_template in form.cleaned_data['index_templates']: task_rebuild_index.apply_async( - kwargs=dict(index_id=index.pk) + kwargs=dict(index_template_id=index_template.pk) ) count += 1 messages.success( request=self.request, message=ungettext( - singular='%(count)d index queued for rebuild.', - plural='%(count)d indexes queued for rebuild.', + singular='%(count)d index template queued for rebuild.', + plural='%(count)d indexes templates queued for rebuild.', number=count ) % { 'count': count, } ) - return super(RebuildIndexesView, self).form_valid(form=form) + return super(IndexInstancesRebuildView, self).form_valid(form=form) def get_form_extra_kwargs(self): return { @@ -409,3 +171,191 @@ class RebuildIndexesView(FormView): def get_post_action_redirect(self): return reverse(viewname='common:tools_list') + + +class IndexTemplateCreateView(SingleObjectCreateView): + extra_context = {'title': _('Create index')} + fields = ('label', 'slug', 'enabled') + model = Index + post_action_redirect = reverse_lazy(viewname='indexing:index_template_list') + view_permission = permission_document_indexing_create + + +class IndexTemplateDeleteView(SingleObjectDeleteView): + model = Index + object_permission = permission_document_indexing_delete + pk_url_kwarg = 'index_template_id' + post_action_redirect = reverse_lazy(viewname='indexing:index_template_list') + + def get_extra_context(self): + return { + 'object': self.object, + 'title': _('Delete the index template: %s?') % self.object, + } + + +class IndexTemplateDocumentTypesView(ExternalObjectMixin, AssignRemoveView): + decode_content_type = True + external_object_class = Index + external_object_permission = permission_document_indexing_edit + external_object_pk_url_kwarg = 'index_template_id' + left_list_title = _('Available document types') + object_permission = permission_document_indexing_edit + right_list_title = _('Document types linked') + + def add(self, item): + self.external_object.document_types.add(item) + + def get_document_type_queryset(self): + return AccessControlList.objects.restrict_queryset( + permission=permission_document_type_view, + queryset=DocumentType.objects.all(), user=self.request.user + ) + + def get_extra_context(self): + return { + 'object': self.external_object, + 'subtitle': _( + 'Only the documents of the types selected will be shown ' + 'in the index when built. Only the events of the documents ' + 'of the types select will trigger updates in the index.' + ), + 'title': _( + 'Document types linked to index template: %s' + ) % self.external_object + } + + def left_list(self): + return AssignRemoveView.generate_choices( + self.get_document_type_queryset().exclude( + id__in=self.external_object.document_types.all() + ) + ) + + def remove(self, item): + self.external_object.document_types.remove(item) + + def right_list(self): + return AssignRemoveView.generate_choices( + choices=self.get_document_type_queryset() & self.external_object.document_types.all() + ) + + +class IndexTemplateEditView(SingleObjectEditView): + fields = ('label', 'slug', 'enabled') + model = Index + object_permission = permission_document_indexing_edit + pk_url_kwarg = 'index_template_id' + post_action_redirect = reverse_lazy(viewname='indexing:index_template_list') + + def get_extra_context(self): + return { + 'object': self.object, + 'title': _('Edit index template: %s') % self.object, + } + + +class IndexTemplateListView(SingleObjectListView): + model = Index + object_permission = permission_document_indexing_view + + def get_extra_context(self): + return { + 'hide_object': True, + 'no_results_icon': icon_index, + 'no_results_main_link': link_index_template_create.resolve( + context=RequestContext(request=self.request) + ), + 'no_results_text': _( + 'Indexes group document automatically into levels. Indexe are ' + 'defined using template whose markers are replaced with ' + 'direct properties of documents like label or description, or ' + 'that of extended properties like metadata.' + ), + 'no_results_title': _('There are no index templates.'), + 'title': _('Index templates'), + } + + +class IndexTemplateNodeCreateView(ExternalObjectMixin, SingleObjectCreateView): + external_object_class = IndexTemplateNode + external_object_permission = permission_document_indexing_edit + external_object_pk_url_kwarg = 'index_template_node_id' + form_class = IndexTemplateNodeForm + model = IndexTemplateNode + + def get_extra_context(self): + return { + 'index': self.external_object.index, + 'navigation_object_list': ('index',), + 'title': _('Create child node of: %s') % self.external_object, + } + + def get_initial(self): + return { + 'index': self.external_object.index, 'parent': self.external_object + } + + +class IndexTemplateNodeDeleteView(SingleObjectDeleteView): + model = IndexTemplateNode + object_permission = permission_document_indexing_edit + pk_url_kwarg = 'index_template_node_id' + + def get_extra_context(self): + return { + 'index': self.object.index, + 'navigation_object_list': ('index', 'node'), + 'node': self.object, + 'title': _( + 'Delete the index template node: %s?' + ) % self.object, + } + + def get_post_action_redirect(self): + return reverse( + viewname='indexing:index_template_view', + kwargs={'index_template_id': self.object.index.pk} + ) + + +class IndexTemplateNodeEditView(SingleObjectEditView): + form_class = IndexTemplateNodeForm + model = IndexTemplateNode + object_permission = permission_document_indexing_edit + pk_url_kwarg = 'index_template_node_id' + + def get_extra_context(self): + return { + 'index': self.object.index, + 'navigation_object_list': ('index', 'node'), + 'node': self.object, + 'title': _( + 'Edit the index template node: %s?' + ) % self.object, + } + + def get_post_action_redirect(self): + return reverse( + viewname='indexing:index_template_view', + kwargs={'index_template_id': self.object.index.pk} + ) + + +class IndexTemplateNodeListView(ExternalObjectMixin, SingleObjectListView): + external_object_class = Index + external_object_permission = permission_document_indexing_edit + external_object_pk_url_kwarg = 'index_template_id' + + def get_extra_context(self): + return { + 'hide_object': True, + 'index': self.external_object, + 'navigation_object_list': ('index',), + 'title': _('Nodes for index template: %s') % self.external_object, + } + + def get_source_queryset(self): + return self.external_object.template_root.get_descendants( + include_self=True + )