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