From b5839c0662b84eff2483c9da9cd44dfc06b493de Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 29 Jan 2019 04:20:54 -0400 Subject: [PATCH] Refactor the tags app Remove the widget from the model. Add keyword arguments. Separate form widgets from html widgets. HTML widgets now go in the html_widgets module. Update the TagMultipleSelectionForm class to be a subclass of FilteredSelectionForm. Move Select2 specific JavaScript from the appearence app to the tags app. Update tag attachment and removal view names. Modernize tests. Add more tests. Consolidate repeated test code into test mixins. Update views to comply with MERCs 5 and 6. Use uniform nomeclature for URLs. Update URLs parameters to use the '_id' form. Signed-off-by: Roberto Rosario --- .../static/appearance/js/mayan_app.js | 24 +- mayan/apps/tags/admin.py | 2 +- mayan/apps/tags/api_views.py | 392 +++++++++++++----- mayan/apps/tags/apps.py | 82 ++-- mayan/apps/tags/events.py | 2 +- mayan/apps/tags/forms.py | 33 +- mayan/apps/tags/html_widgets.py | 26 ++ mayan/apps/tags/icons.py | 22 +- mayan/apps/tags/links.py | 42 +- mayan/apps/tags/methods.py | 12 +- mayan/apps/tags/models.py | 28 +- mayan/apps/tags/routers.py | 20 + mayan/apps/tags/search.py | 5 +- mayan/apps/tags/serializers.py | 336 +++++++++++++-- mayan/apps/tags/static/tags/js/tags_form.js | 26 ++ .../templates/tags/document_tags_widget.html | 5 + .../tags/forms/widgets/tag_select_option.html | 1 - mayan/apps/tags/tests/literals.py | 16 +- mayan/apps/tags/tests/mixins.py | 136 +++--- mayan/apps/tags/tests/test_api.py | 351 ++++++++-------- mayan/apps/tags/tests/test_events.py | 14 +- mayan/apps/tags/tests/test_indexing.py | 10 +- mayan/apps/tags/tests/test_links.py | 41 ++ mayan/apps/tags/tests/test_views.py | 361 ++++++++++------ mayan/apps/tags/tests/test_wizard_steps.py | 15 +- mayan/apps/tags/urls.py | 87 ++-- mayan/apps/tags/views.py | 159 +++---- mayan/apps/tags/widgets.py | 34 +- mayan/apps/tags/wizard_steps.py | 20 +- mayan/apps/tags/workflow_actions.py | 5 +- 30 files changed, 1489 insertions(+), 818 deletions(-) create mode 100644 mayan/apps/tags/html_widgets.py create mode 100644 mayan/apps/tags/routers.py create mode 100644 mayan/apps/tags/static/tags/js/tags_form.js create mode 100644 mayan/apps/tags/templates/tags/document_tags_widget.html create mode 100644 mayan/apps/tags/tests/test_links.py diff --git a/mayan/apps/appearance/static/appearance/js/mayan_app.js b/mayan/apps/appearance/static/appearance/js/mayan_app.js index b77e94859e..b3df0c8e14 100644 --- a/mayan/apps/appearance/static/appearance/js/mayan_app.js +++ b/mayan/apps/appearance/static/appearance/js/mayan_app.js @@ -8,10 +8,10 @@ class MayanApp { ajaxMenusOptions: [] } - this.ajaxSpinnerSeletor = '#ajax-spinner'; this.ajaxExecuting = false; this.ajaxMenusOptions = options.ajaxMenusOptions; this.ajaxMenuHashes = {}; + this.ajaxSpinnerSeletor = '#ajax-spinner'; this.window = $(window); } @@ -81,22 +81,6 @@ class MayanApp { }); } - static tagSelectionTemplate (tag, container) { - var $tag = $( - ' ' + tag.text + '' - ); - container[0].style.background = tag.element.dataset.color; - return $tag; - } - - static tagResultTemplate (tag) { - if (!tag.element) { return ''; } - var $tag = $( - ' ' + tag.text + '' - ); - return $tag; - } - static updateNavbarState () { var uri = new URI(window.location.hash); var uriFragment = uri.fragment(); @@ -445,12 +429,6 @@ class MayanApp { dropdownAutoWidth: true, width: '100%' }); - - $('.select2-tags').select2({ - templateSelection: MayanApp.tagSelectionTemplate, - templateResult: MayanApp.tagResultTemplate, - width: '100%' - }); } resizeFullHeight () { diff --git a/mayan/apps/tags/admin.py b/mayan/apps/tags/admin.py index 7d0f1a007a..a703ba2e03 100644 --- a/mayan/apps/tags/admin.py +++ b/mayan/apps/tags/admin.py @@ -8,4 +8,4 @@ from .models import Tag @admin.register(Tag) class TagAdmin(admin.ModelAdmin): filter_horizontal = ('documents',) - list_display = ('label', 'color', 'get_preview_widget') + list_display = ('label', 'color') diff --git a/mayan/apps/tags/api_views.py b/mayan/apps/tags/api_views.py index e4d109c08f..7018c54599 100644 --- a/mayan/apps/tags/api_views.py +++ b/mayan/apps/tags/api_views.py @@ -1,14 +1,20 @@ from __future__ import absolute_import, unicode_literals -from rest_framework import generics +from rest_framework import generics, status +from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.response import Response -from mayan.apps.common.mixins import ExternalObjectViewMixin +from mayan.apps.common.mixins import ExternalObjectMixin +from mayan.apps.documents.api_views import DocumentViewSet from mayan.apps.documents.models import Document from mayan.apps.documents.permissions import permission_document_view from mayan.apps.documents.serializers import DocumentSerializer from mayan.apps.rest_api.filters import MayanObjectPermissionsFilter +from mayan.apps.rest_api.generics import ( + ListAPIView, ListCreateAPIView, RetrieveDestroyAPIView, + RetrieveUpdateDestroyAPIView +) from mayan.apps.rest_api.permissions import MayanPermission from .models import Tag @@ -17,116 +23,295 @@ from .permissions import ( permission_tag_edit, permission_tag_remove, permission_tag_view ) from .serializers import ( - DocumentTagSerializer, TagSerializer, WritableTagSerializer + DocumentTagAttachSerializer, DocumentTagSerializer, TagAttachSerializer, + TagRemoveSerializer, TagSerializer, ) -class APITagListView(generics.ListCreateAPIView): + +from django.conf.urls import url, include +from django.contrib.auth.models import User + +from rest_framework import routers, serializers, viewsets +from rest_framework.decorators import detail_route +from rest_framework.response import Response + +from drf_yasg.utils import swagger_auto_schema + + +class TagViewSet(viewsets.ModelViewSet): + filter_backends = (MayanObjectPermissionsFilter,) + lookup_field = 'pk' + lookup_url_kwarg='tag_id' + permission_classes = (MayanPermission,) + queryset = Tag.objects.all() + serializer_class = TagSerializer + + + #@swagger_auto_schema(operation_description='GET /articles/today/') + @swagger_auto_schema( + operation_description="partial_update description override", responses={200: TagAttachSerializer} + ) + @action( + detail=True, lookup_field='pk', lookup_url_kwarg='tag_id', + methods=('post',), serializer_class=TagAttachSerializer, + url_name='document-attach', url_path='attach' + ) + def attach(self, request, *args, **kwargs): + #print '!!! attach', args, kwargs#, self.context + #return Response({}) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + #print '((((((((', serializer.validated_data + #self.perform_attach(serializer=serializer) + serializer.attach(instance=self.get_object()) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) + + #def perform_attach(self, serializer): + # #print '!!!!', serializer + # serializer.attach(instance=self.get_object()) + + #def get_success_headers(self, data): + # try: + # return {'Location': str(data[api_settings.URL_FIELD_NAME])} + # except (TypeError, KeyError): + # return {} + + @action( + detail=True, lookup_field='pk', lookup_url_kwarg='tag_id', + url_name='document-list', url_path='documents' + ) + def document_list(self, request, *args, **kwargs): + queryset = self.get_object().documents.all() + + #TODO:Filter queryset + #queryset = self.filter_queryset(self.get_queryset()) + + page = self.paginate_queryset(queryset) + if page is not None: + #serializer = self.get_serializer(page, many=True) + serializer = DocumentSerializer(page, many=True, context={'request': request}) + return self.get_paginated_response(serializer.data) + + #serializer = self.get_serializer(queryset, many=True) + serializer = DocumentSerializer(queryset, many=True, context={'request': request}) + return Response(serializer.data) + + + #serializer = DocumentSerializer( + # instance=, many=True, + # context={'request': request} + #) + #return Response(serializer.data) + + + @action( + detail=True, lookup_field='pk', lookup_url_kwarg='tag_id', + methods=('post',), serializer_class=TagRemoveSerializer, + url_name='document-remove', url_path='remove' + ) + def remove(self, request, *args, **kwargs): + #print '!!! attach', args, kwargs#, self.context + #return Response({}) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + #print '((((((((', serializer.validated_data + #self.perform_attach(serializer=serializer) + serializer.remove(instance=self.get_object()) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) + + #def get_serializer_class(self, *args, **kwargs): + # #if self.action == 'attach': + # print '!!!!get_serializer_class', args, kwargs + # return TagAttachSerializer + + + +class DocumentTagViewSet(ExternalObjectMixin, viewsets.ReadOnlyModelViewSet): + external_object_class = Document + external_object_pk_url_kwarg = 'document_id' + external_object_permission = permission_tag_view + lookup_field = 'pk' + object_permission = { + 'list': permission_document_view, + 'retrieve': permission_document_view + } + serializer_class = DocumentTagSerializer + + @action( + detail=True, lookup_field='pk', lookup_url_kwarg='document_id', + methods=('post',), serializer_class=DocumentTagAttachSerializer, + url_name='tag-attach', url_path='attach' + ) + def attach(self, request, *args, **kwargs): + return Response({}) + + + ''' + serializer = DocumentSerializer( + instance=self.get_object().documents.all(), many=True, + context={'request': request} + ) + return Response(serializer.data) + ''' + + def get_document(self): + return self.get_external_object() + + def get_queryset(self): + #return self.get_document().get_tags(user=self.request.user).all() + return self.get_document().tags.all() + + #@detail_route(lookup_url_kwarg='tag_id') + #def document_list(self, request, *args, **kwargs): + # serializer = DocumentSerializer( + ## instance=self.get_object().documents.all(), many=True, + # context={'request': request} + # ) + # return Response(serializer.data) + +''' + +class APITagListView(ListCreateAPIView): """ get: Returns a list of all the tags. post: Create a new tag. """ - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_tag_view,)} - mayan_view_permissions = {'POST': (permission_tag_create,)} - permission_classes = (MayanPermission,) + object_permission = {'GET': permission_tag_view} queryset = Tag.objects.all() - - def get_serializer(self, *args, **kwargs): - if not self.request: - return None - - return super(APITagListView, self).get_serializer(*args, **kwargs) - - def get_serializer_class(self): - if self.request.method == 'GET': - return TagSerializer - elif self.request.method == 'POST': - return WritableTagSerializer + serializer_class = TagSerializer + view_permission = {'POST': permission_tag_create} -class APITagView(generics.RetrieveUpdateDestroyAPIView): +class APITagView(RetrieveUpdateDestroyAPIView): """ delete: Delete the selected tag. get: Return the details of the selected tag. patch: Edit the selected tag. put: Edit the selected tag. """ - filter_backends = (MayanObjectPermissionsFilter,) lookup_url_kwarg = 'tag_pk' - mayan_object_permissions = { - 'DELETE': (permission_tag_delete,), - 'GET': (permission_tag_view,), - 'PATCH': (permission_tag_edit,), - 'PUT': (permission_tag_edit,) + object_permission = { + 'DELETE': permission_tag_delete, + 'GET': permission_tag_view, + 'PATCH': permission_tag_edit, + 'PUT': permission_tag_edit } queryset = Tag.objects.all() + serializer_class = TagSerializer - def get_serializer(self, *args, **kwargs): - if not self.request: - return None +## - return super(APITagView, self).get_serializer(*args, **kwargs) - - def get_serializer_class(self): - if self.request.method == 'GET': - return TagSerializer - else: - return WritableTagSerializer - - -class APITagDocumentListView(ExternalObjectViewMixin, generics.ListAPIView): +class APITagDocumentListView(ExternalObjectMixin, ListCreateAPIView): """ get: Returns a list of all the documents tagged by a particular tag. """ external_object_class = Tag external_object_pk_url_kwarg = 'tag_pk' external_object_permission = permission_tag_view - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_document_view,)} - serializer_class = DocumentSerializer + object_permission = {'GET': permission_document_view} + serializer_class = TagDocumentSerializer def get_queryset(self): - return self.get_tag().documents.all() + return self.get_tag().get_documents(user=self.request.user).all() def get_tag(self): return self.get_external_object() +## +''' -class APIDocumentTagListView(ExternalObjectViewMixin, generics.ListCreateAPIView): + +''' +class APITagView(RetrieveDestroyAPIView): """ - get: Returns a list of all the tags attached to a document. - post: Attach a tag to a document. + delete: Delete the selected tag document. + get: Return the details of the selected tag document. """ + lookup_url_kwarg = 'tag_pk' + object_permission = { + 'DELETE': permission_tag_delete, + 'GET': permission_tag_view, + 'PATCH': permission_tag_edit, + 'PUT': permission_tag_edit + } + queryset = Tag.objects.all() + serializer_class = TagSerializer +''' +## +''' +class DocumentSourceMixin(ExternalObjectMixin): external_object_class = Document external_object_pk_url_kwarg = 'document_pk' - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = { - 'GET': (permission_tag_view,), - 'POST': (permission_tag_attach,) - } + serializer_class = DocumentTagSerializer def get_document(self): return self.get_external_object() def get_external_object_permission(self): - if self.request.method == 'POST': - return permission_tag_attach - else: - return permission_tag_view + permission_dictionary = { + 'DELETE': permission_tag_remove, + 'GET': permission_tag_view, + 'POST': permission_tag_attach + } + + return permission_dictionary.get(self.request.method) def get_queryset(self): - return self.get_document().get_tags().all() + return self.get_document().tags.all() - def get_serializer(self, *args, **kwargs): - if not self.request: - return None + def get_serializer_context(self): + context = super(DocumentSourceMixin, self).get_serializer_context() + if self.kwargs: + context.update( + { + 'document': self.get_document(), + } + ) - return super(APIDocumentTagListView, self).get_serializer(*args, **kwargs) + return context - def get_serializer_class(self): - return DocumentTagSerializer +class APIDocumentTagListView(DocumentSourceMixin, ListCreateAPIView): + """ + get: Returns a list of all the tags attached to a document. + post: Attach a tag to a document. + """ + #external_object_class = Document + #external_object_pk_url_kwarg = 'document_pk' + object_permission = { + 'GET': permission_tag_view, + #'POST': permission_tag_attach + } + #serializer_class = DocumentTagSerializer + + #def get_document(self): + # return self.get_external_object() + + #def get_external_object_permission(self): + # if self.request.method == 'POST': + # return permission_tag_attach + # else: + # return permission_tag_view + + #def get_queryset(self): + # #if self.request.method == 'POST': + # # permission = permission_tag_attach + # #else: + # # permission = permission_tag_view + # + # return self.get_document().tags().all() + # #return self.get_document().get_tags( + ## # permission=permission, user=self.request.user + # #).all() + + """ def get_serializer_context(self): """ Extra context provided to the serializer class. @@ -140,41 +325,44 @@ class APIDocumentTagListView(ExternalObjectViewMixin, generics.ListCreateAPIView ) return context + """ - -class APIDocumentTagView(ExternalObjectViewMixin, generics.RetrieveDestroyAPIView): +class APIDocumentTagView(DocumentSourceMixin, RetrieveDestroyAPIView): """ delete: Remove a tag from the selected document. get: Returns the details of the selected document tag. """ - external_object_class = Document - external_object_pk_url_kwarg = 'document_pk' - filter_backends = (MayanObjectPermissionsFilter,) + #external_object_class = Document + #external_object_pk_url_kwarg = 'document_pk' lookup_url_kwarg = 'tag_pk' - mayan_object_permissions = { - 'GET': (permission_tag_view,), - 'DELETE': (permission_tag_remove,) + mayan_object_permission = { + 'GET': permission_tag_view, + 'DELETE': permission_tag_remove } - serializer_class = DocumentTagSerializer + #serializer_class = DocumentTagSerializer - def get_document(self): - return self.get_external_object() + #def get_document(self): + # return self.get_external_object() - def get_external_object_permission(self): - if self.request.method == 'DELETE': - return permission_tag_remove - else: - return permission_tag_view + #def get_external_object_permission(self): + # if self.request.method == 'DELETE': + # return permission_tag_remove + # else: + # return permission_tag_view + """ def get_queryset(self): - return self.get_document().get_tags().all() + if self.request.method == 'DELETE': + permission = permission_tag_remove + else: + permission = permission_tag_view - def get_serializer(self, *args, **kwargs): - if not self.request: - return None - - return super(APIDocumentTagView, self).get_serializer(*args, **kwargs) + return self.get_document().get_tags( + permission=permission, user=self.request.user + ).all() + """ + """ def get_serializer_context(self): """ Extra context provided to the serializer class. @@ -188,15 +376,33 @@ class APIDocumentTagView(ExternalObjectViewMixin, generics.RetrieveDestroyAPIVie ) return context + """ def perform_destroy(self, instance): - try: - instance.documents.remove(self.get_document()) - except Exception as exception: - raise ValidationError(exception) + # try: + from mayan.apps.acls.models import AccessControlList + from rest_framework.generics import get_object_or_404 - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() + queryset = AccessControlList.objects.restrict_queryset( + permission=permission_tag_remove, queryset=Tag.objects.all(), + user=self.request.user + ) + instance = get_object_or_404(queryset=queryset, pk=instance.pk) + #instance.attach_to( + # document=self.context['document'], + # user=self.context['request'].user + #) - serializer = self.get_serializer(instance) - return Response(serializer.data) + instance.remove_from( + document=self.get_document(), user=self.request.user + ) + #instance.documents.remove(self.get_document()) + # except Exception as exception: + # raise ValidationError(exception) + + #def retrieve(self, request, *args, **kwargs): + # instance = self.get_object() + + # serializer = self.get_serializer(instance) + # return Response(serializer.data) +''' diff --git a/mayan/apps/tags/apps.py b/mayan/apps/tags/apps.py index ee9c6617af..75209a5517 100644 --- a/mayan/apps/tags/apps.py +++ b/mayan/apps/tags/apps.py @@ -24,12 +24,13 @@ from .events import ( event_tag_attach, event_tag_created, event_tag_edited, event_tag_remove ) from .handlers import handler_index_document, handler_tag_pre_delete +from .html_widgets import DocumentTagsWidget, TagWidget from .links import ( - link_document_tag_list, link_multiple_documents_attach_tag, - link_multiple_documents_tag_remove, - link_single_document_multiple_tag_remove, link_tag_attach, - link_tag_create, link_tag_delete, link_tag_edit, link_tag_list, - link_tag_multiple_delete, link_tag_tagged_item_list + link_document_tag_list, link_document_multiple_tag_multiple_attach, + link_document_multiple_tag_multiple_remove, + link_document_tag_multiple_remove, link_document_tag_multiple_attach, + link_tag_create, link_tag_delete, link_tag_document_list, link_tag_edit, + link_tag_list, link_tag_multiple_delete ) from .menus import menu_tags from .methods import method_get_tags @@ -38,7 +39,6 @@ from .permissions import ( permission_tag_remove, permission_tag_view ) from .search import tag_search # NOQA -from .widgets import widget_document_tags class TagsApp(MayanAppConfig): @@ -63,13 +63,11 @@ class TagsApp(MayanAppConfig): app_label='documents', model_name='DocumentPageSearchResult' ) - DocumentTag = self.get_model('DocumentTag') - Tag = self.get_model('Tag') + DocumentTag = self.get_model(model_name='DocumentTag') + Tag = self.get_model(model_name='Tag') Document.add_to_class(name='get_tags', value=method_get_tags) - ModelAttribute(model=Document, name='get_tags') - ModelEventType.register( model=Tag, event_types=( event_tag_attach, event_tag_created, event_tag_edited, @@ -78,10 +76,10 @@ class TagsApp(MayanAppConfig): ) ModelField( - Document, name='tags__label' + model=Document, name='tags__label' ) ModelField( - Document, name='tags__color' + model=Document, name='tags__color' ) ModelPermission.register( @@ -96,29 +94,31 @@ class TagsApp(MayanAppConfig): permission_acl_edit, permission_acl_view, permission_events_view, permission_tag_attach, permission_tag_delete, permission_tag_edit, - permission_tag_remove, permission_tag_view, + permission_tag_remove, permission_tag_view ) ) SourceColumn( attribute='label', is_identifier=True, is_sortable=True, - source=DocumentTag, + source=DocumentTag ) SourceColumn( - attribute='get_preview_widget', source=DocumentTag + label=_('Preview'), source=DocumentTag, widget=TagWidget ) SourceColumn( - func=lambda context: widget_document_tags( - document=context['object'], user=context['request'].user - ), label=_('Tags'), source=Document - ) - - SourceColumn( - func=lambda context: widget_document_tags( - document=context['object'].document, + func=lambda context: context['object'].get_tags( + permission=permission_tag_view, user=context['request'].user - ), label=_('Tags'), source=DocumentPageSearchResult + ), label=_('Tags'), source=Document, widget=DocumentTagsWidget + ) + + SourceColumn( + func=lambda context: context['object'].document.get_tag( + permission=permission_tag_view, + user=context['request'].user + ), label=_('Tags'), source=DocumentPageSearchResult, + widget=DocumentTagsWidget ) SourceColumn( @@ -126,12 +126,12 @@ class TagsApp(MayanAppConfig): source=Tag ) SourceColumn( - attribute='get_preview_widget', source=Tag + label=_('Preview'), source=Tag, widget=TagWidget ) SourceColumn( func=lambda context: context['object'].get_document_count( user=context['request'].user - ), label=_('Documents'), source=Tag + ), include_label=True, label=_('Documents'), source=Tag ) document_page_search.add_model_field( @@ -147,19 +147,17 @@ class TagsApp(MayanAppConfig): links=( link_acl_list, link_events_for_object, link_object_event_types_user_subcriptions_list, - link_tag_tagged_item_list, - ), - sources=(Tag,) + link_tag_document_list, + ), sources=(Tag,) ) menu_main.bind_links(links=(menu_tags,), position=98) menu_multi_item.bind_links( links=( - link_multiple_documents_attach_tag, - link_multiple_documents_tag_remove - ), - sources=(Document,) + link_document_multiple_tag_multiple_attach, + link_document_multiple_tag_multiple_remove + ), sources=(Document,) ) menu_multi_item.bind_links( links=(link_tag_multiple_delete,), sources=(Tag,) @@ -167,14 +165,14 @@ class TagsApp(MayanAppConfig): menu_object.bind_links( links=( link_tag_edit, link_tag_delete - ), - sources=(Tag,) + ), sources=(Tag,) ) menu_sidebar.bind_links( - links=(link_tag_attach, link_single_document_multiple_tag_remove), - sources=( - 'tags:tag_attach', 'tags:document_tags', - 'tags:single_document_multiple_tag_remove' + links=( + link_document_tag_multiple_attach, link_document_tag_multiple_remove + ), sources=( + 'tags:document_tag_multiple_attach', 'tags:document_tag_list', + 'tags:document_tag_multiple_remove' ) ) menu_tags.bind_links( @@ -188,13 +186,11 @@ class TagsApp(MayanAppConfig): # Index update m2m_changed.connect( - handler_index_document, dispatch_uid='tags_handler_index_document', - sender=Tag.documents.through + receiver=handler_index_document, sender=Tag.documents.through ) pre_delete.connect( - handler_tag_pre_delete, dispatch_uid='tags_handler_tag_pre_delete', - sender=Tag + receiver=handler_tag_pre_delete, sender=Tag ) diff --git a/mayan/apps/tags/events.py b/mayan/apps/tags/events.py index 44f5793802..8f753bbbe9 100644 --- a/mayan/apps/tags/events.py +++ b/mayan/apps/tags/events.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.events import EventTypeNamespace -namespace = EventTypeNamespace(name='tags', label=_('Tags')) +namespace = EventTypeNamespace(label=_('Tags'), name='tags') event_tag_attach = namespace.add_event_type( label=_('Tag attached to document'), name='attach' diff --git a/mayan/apps/tags/forms.py b/mayan/apps/tags/forms.py index 392de0d68f..30b60a9937 100644 --- a/mayan/apps/tags/forms.py +++ b/mayan/apps/tags/forms.py @@ -1,37 +1,22 @@ from __future__ import absolute_import, unicode_literals -import logging - from django import forms from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.models import AccessControlList +from mayan.apps.common.forms import FilteredSelectionForm from .models import Tag from .permissions import permission_tag_view from .widgets import TagFormWidget -logger = logging.getLogger(__name__) +class TagMultipleSelectionForm(FilteredSelectionForm): + class Media: + js = ('tags/js/tags_form.js',) -class TagMultipleSelectionForm(forms.Form): - def __init__(self, *args, **kwargs): - help_text = kwargs.pop('help_text', None) - permission = kwargs.pop('permission', permission_tag_view) - queryset = kwargs.pop('queryset', Tag.objects.all()) - user = kwargs.pop('user', None) - - logger.debug('user: %s', user) - super(TagMultipleSelectionForm, self).__init__(*args, **kwargs) - - queryset = AccessControlList.objects.filter_by_access( - permission=permission, queryset=queryset, user=user - ) - - self.fields['tags'] = forms.ModelMultipleChoiceField( - label=_('Tags'), help_text=help_text, - queryset=queryset, required=False, - widget=TagFormWidget( - attrs={'class': 'select2-tags'}, queryset=queryset - ) - ) + class Meta: + allow_multiple = True + field_name = 'tags' + label = _('Tags') + widget_attributes = {'class': 'select2-tags'} diff --git a/mayan/apps/tags/html_widgets.py b/mayan/apps/tags/html_widgets.py new file mode 100644 index 0000000000..94128dc441 --- /dev/null +++ b/mayan/apps/tags/html_widgets.py @@ -0,0 +1,26 @@ +from __future__ import absolute_import, unicode_literals + +from django.template.loader import render_to_string + + +class DocumentTagsWidget(object): + """ + A tag widget that displays the tags for the given document + """ + def render(self, name, value): + return render_to_string( + template_name='tags/document_tags_widget.html', + context={ + 'tags': value, + } + ) + + +class TagWidget(object): + def render(self, name, value): + return render_to_string( + template_name='tags/tag_widget.html', + context={ + 'tag': value, + } + ) diff --git a/mayan/apps/tags/icons.py b/mayan/apps/tags/icons.py index a5f55f6e96..7e84defc64 100644 --- a/mayan/apps/tags/icons.py +++ b/mayan/apps/tags/icons.py @@ -2,19 +2,22 @@ from __future__ import absolute_import, unicode_literals from mayan.apps.appearance.classes import Icon -icon_menu_tags = Icon(driver_name='fontawesome', symbol='tags') -icon_multiple_documents_tag_attach = Icon( - driver_name='fontawesome-dual', primary_symbol='tag', - secondary_symbol='arrow-right' -) -icon_multiple_documents_tag_remove = Icon( +icon_document_multiple_tag_multiple_remove = Icon( driver_name='fontawesome-dual', primary_symbol='tag', secondary_symbol='minus' ) -icon_tag_attach = Icon( +icon_document_tag_multiple_attach = Icon( driver_name='fontawesome-dual', primary_symbol='tag', secondary_symbol='arrow-right' ) +icon_document_tag_multiple_remove = Icon( + driver_name='fontawesome-dual', primary_symbol='tag', + secondary_symbol='minus' +) +icon_document_tag_multiple_remove_submit = Icon( + driver_name='fontawesome', symbol='minus' +) +icon_menu_tags = Icon(driver_name='fontawesome', symbol='tags') icon_tag_create = Icon( driver_name='fontawesome-dual', primary_symbol='tag', secondary_symbol='plus' @@ -25,8 +28,3 @@ icon_tag_delete_submit = Icon(driver_name='fontawesome', symbol='times') icon_tag_document_list = Icon(driver_name='fontawesome', symbol='tags') icon_tag_list = Icon(driver_name='fontawesome', symbol='tags') icon_tag_multiple_delete = Icon(driver_name='fontawesome', symbol='times') -icon_tag_remove = Icon( - driver_name='fontawesome-dual', primary_symbol='tag', - secondary_symbol='minus' -) -icon_tag_remove_submit = Icon(driver_name='fontawesome', symbol='minus') diff --git a/mayan/apps/tags/links.py b/mayan/apps/tags/links.py index 062a44f425..0a7a3ab980 100644 --- a/mayan/apps/tags/links.py +++ b/mayan/apps/tags/links.py @@ -6,10 +6,10 @@ from mayan.apps.documents.icons import icon_document_list from mayan.apps.navigation import Link, get_cascade_condition from .icons import ( - icon_document_multiple_tag_multiple_remove, icon_document_multiple_tag_multiple_remove, - icon_document_tag_multiple_attach, icon_tag_create, icon_tag_delete, icon_tag_edit, - icon_tag_document_list, icon_tag_list, icon_tag_multiple_delete, - icon_document_tag_multiple_remove + icon_document_multiple_tag_multiple_remove, + icon_document_tag_multiple_attach, icon_tag_create, icon_tag_delete, + icon_tag_edit, icon_tag_document_list, icon_tag_list, + icon_tag_multiple_delete, icon_document_tag_multiple_remove ) from .permissions import ( permission_tag_attach, permission_tag_create, permission_tag_delete, @@ -17,10 +17,6 @@ from .permissions import ( ) -link_document_tag_list = Link( - args='resolved_object.pk', icon_class=icon_tag_document_list, - permission=permission_tag_view, text=_('Tags'), view='tags:document_tags' -) link_document_multiple_tag_multiple_attach = Link( icon_class=icon_document_multiple_tag_multiple_remove, text=_('Attach tags'), view='tags:document_multiple_tag_multiple_attach' @@ -29,27 +25,37 @@ link_document_multiple_tag_multiple_remove = Link( icon_class=icon_document_multiple_tag_multiple_remove, text=_('Remove tag'), view='tags:document_multiple_tag_multiple_remove' ) +link_document_tag_list = Link( + icon_class=icon_tag_document_list, + kwargs={'document_id': 'resolved_object.pk'}, + permission=permission_tag_view, text=_('Tags'), + view='tags:document_tag_list' +) link_document_tag_multiple_attach = Link( - args='object.pk', icon_class=icon_document_tag_multiple_attach, - permission=permission_tag_attach, text=_('Attach tags'), - view='tags:document_tag_multiple_attach' + icon_class=icon_document_tag_multiple_attach, + kwargs={'document_id': 'object.pk'}, permission=permission_tag_attach, + text=_('Attach tags'), view='tags:document_tag_multiple_attach' ) link_document_tag_multiple_remove = Link( - args='object.id', icon_class=icon_document_tag_multiple_remove, - permission=permission_tag_remove, text=_('Remove tags'), - view='tags:document_tag_multiple_remove' + icon_class=icon_document_tag_multiple_remove, + kwargs={'document_id': 'object.pk'}, permission=permission_tag_remove, + text=_('Remove tags'), view='tags:document_tag_multiple_remove' ) link_tag_create = Link( icon_class=icon_tag_create, permission=permission_tag_create, text=_('Create new tag'), view='tags:tag_create' ) link_tag_delete = Link( - args='object.id', icon_class=icon_tag_delete, + icon_class=icon_tag_delete, kwargs={'tag_id': 'object.pk'}, permission=permission_tag_delete, tags='dangerous', text=_('Delete'), view='tags:tag_delete' ) +link_tag_document_list = Link( + icon_class=icon_document_list, kwargs={'tag_id': 'object.pk'}, + text=('Documents'), view='tags:tag_document_list' +) link_tag_edit = Link( - args='object.id', icon_class=icon_tag_edit, + icon_class=icon_tag_edit, kwargs={'tag_id': 'object.pk'}, permission=permission_tag_edit, text=_('Edit'), view='tags:tag_edit' ) link_tag_list = Link( @@ -62,7 +68,3 @@ link_tag_multiple_delete = Link( icon_class=icon_tag_multiple_delete, permission=permission_tag_delete, text=_('Delete'), view='tags:tag_multiple_delete' ) -link_tag_tagged_item_list = Link( - args='object.id', icon_class=icon_document_list, text=('Documents'), - view='tags:tag_tagged_item_list' -) diff --git a/mayan/apps/tags/methods.py b/mayan/apps/tags/methods.py index 83761fc92f..bfc0163ebc 100644 --- a/mayan/apps/tags/methods.py +++ b/mayan/apps/tags/methods.py @@ -3,10 +3,18 @@ from __future__ import unicode_literals from django.apps import apps from django.utils.translation import ugettext_lazy as _ +from .permissions import permission_tag_view -def method_get_tags(self): + +def method_get_tags(self, permission, user): + AccessControlList = apps.get_model( + app_label='acls', model_name='AccessControlList' + ) DocumentTag = apps.get_model(app_label='tags', model_name='DocumentTag') - return DocumentTag.objects.filter(documents=self) + return AccessControlList.objects.restrict_queryset( + permission=permission, + queryset=DocumentTag.objects.filter(documents=self), user=user + ) method_get_tags.help_text = _('Return a the tags attached to the document.') diff --git a/mayan/apps/tags/models.py b/mayan/apps/tags/models.py index 0e49ae2f27..5f2e6ec88b 100644 --- a/mayan/apps/tags/models.py +++ b/mayan/apps/tags/models.py @@ -15,7 +15,6 @@ from .events import ( event_tag_attach, event_tag_created, event_tag_edited, event_tag_remove ) from .managers import DocumentTagManager -from .widgets import widget_single_tag @python_2_unicode_compatible @@ -56,23 +55,22 @@ class Tag(models.Model): def get_absolute_url(self): return reverse( - viewname='tags:tag_tagged_item_list', kwargs={'tag_pk': str(self.pk)} + viewname='tags:tag_tagged_item_list', kwargs={ + 'tag_id': self.pk + } + ) + + def get_documents(self, user): + """ + Return a filtered queryset documents that have this tag attached. + """ + return AccessControlList.objects.restrict_queryset( + permission=permission_document_view, queryset=self.documents, + user=user ) def get_document_count(self, user): - """ - Return the numeric count of documents that have this tag attached. - The count if filtered by access. - """ - queryset = AccessControlList.objects.filter_by_access( - permission_document_view, user, queryset=self.documents - ) - - return queryset.count() - - def get_preview_widget(self): - return widget_single_tag(tag=self) - get_preview_widget.short_description = _('Preview') + return self.get_documents(user=user).count() def remove_from(self, document, user=None): """ diff --git a/mayan/apps/tags/routers.py b/mayan/apps/tags/routers.py new file mode 100644 index 0000000000..04f7371af6 --- /dev/null +++ b/mayan/apps/tags/routers.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +#from rest_framework import routers + +#router = routers.SimpleRouter() +#router.register(r'users', UserViewSet) +#router.register(r'accounts', AccountViewSet) +#urlpatterns = router.urls + +#router = routers.DefaultRouter() +#from mayan.apps.rest_api.api_views import router +#from mayan.apps.rest_api.urls import router + +from .api_views import TagViewSet + +router_entries = ( + {'prefix': r'tags', 'viewset': TagViewSet, 'base_name': 'tag'}, +) + +#router.register(prefix=r'tags', viewset=TagViewSet, basename='tag') diff --git a/mayan/apps/tags/search.py b/mayan/apps/tags/search.py index f16b8e75c1..d74bee54a8 100644 --- a/mayan/apps/tags/search.py +++ b/mayan/apps/tags/search.py @@ -7,9 +7,8 @@ from mayan.apps.dynamic_search.classes import SearchModel from .permissions import permission_tag_view tag_search = SearchModel( - app_label='tags', model_name='Tag', - permission=permission_tag_view, - serializer_string='mayan.apps.tags.serializers.TagSerializer' + app_label='tags', model_name='Tag', permission=permission_tag_view, + serializer_path='mayan.apps.tags.serializers.TagSerializer' ) tag_search.add_model_field( diff --git a/mayan/apps/tags/serializers.py b/mayan/apps/tags/serializers.py index 5048d10324..9f4d5c3012 100644 --- a/mayan/apps/tags/serializers.py +++ b/mayan/apps/tags/serializers.py @@ -8,34 +8,42 @@ from rest_framework.reverse import reverse from mayan.apps.acls.models import AccessControlList from mayan.apps.documents.models import Document +from mayan.apps.documents.serializers import DocumentSerializer +from mayan.apps.rest_api.relations import MultiKwargHyperlinkedIdentityField from .models import Tag from .permissions import permission_tag_attach class TagSerializer(serializers.HyperlinkedModelSerializer): - documents_url = serializers.HyperlinkedIdentityField( - lookup_field='pk', lookup_url_kwarg='tag_pk', - view_name='rest_api:tag-document-list' + attach_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='tag_id', view_name='rest_api:tag-document-attach' + ) + + documents_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='tag_id', view_name='rest_api:tag-document-list' + ) + + remove_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='tag_id', view_name='rest_api:tag-document-remove' ) - documents_count = serializers.SerializerMethodField() class Meta: extra_kwargs = { 'url': { - 'lookup_field': 'pk', 'lookup_url_kwarg': 'tag_pk', + 'lookup_url_kwarg': 'tag_id', 'view_name': 'rest_api:tag-detail' - } + }, } fields = ( - 'color', 'documents_count', 'documents_url', 'id', 'label', 'url' + 'attach_url', 'color', 'documents_url', 'label', 'id', + 'remove_url', 'url' ) model = Tag - def get_documents_count(self, instance): - return instance.documents.count() +""" class WritableTagSerializer(serializers.ModelSerializer): documents_pk_list = serializers.CharField( help_text=_( @@ -81,37 +89,303 @@ class WritableTagSerializer(serializers.ModelSerializer): ) return instance - +""" class DocumentTagSerializer(TagSerializer): - document_tag_url = serializers.SerializerMethodField( - help_text=_( - 'API URL pointing to a tag in relation to the document ' - 'attached to it. This URL is different than the canonical ' - 'tag URL.' - ) - ) - tag_pk = serializers.IntegerField( - help_text=_('Primary key of the tag to be added.'), write_only=True - ) + #document_attach_url = serializers.HyperlinkedIdentityField( + # lookup_url_kwarg='document_id', view_name='rest_api:document-tag-attach' + #) class Meta(TagSerializer.Meta): - fields = TagSerializer.Meta.fields + ('document_tag_url', 'tag_pk') - read_only_fields = TagSerializer.Meta.fields + ('document_tag_url',) + #fields = TagSerializer.Meta.fields + ('document_attach_url',) + fields = TagSerializer.Meta.fields + #fields = TagSerializer.Meta.fields + ('document_tag_url', 'tag_pk') + #fields = TagSerializer.Meta.fields + ('tag_pk',) + #read_only_fields = TagSerializer.Meta.fields + ('document_attach_url',) + read_only_fields = TagSerializer.Meta.fields + #read_only_fields = TagSerializer.Meta.fields - def get_document_tag_url(self, instance): - return reverse( - viewname='rest_api:document-tag-detail', kwargs={ - 'document_pk': self.context['document'].pk, - 'tag_pk': instance.pk - }, request=self.context['request'], format=self.context['format'] + #related_models = ('tags',) + #related_models_kwargs = { + # 'documents': { + ## #'pk_list': 'tags_pk_list', 'model': Tag, + # #'model': Tag, + # 'object_permission': {'create': permission_tag_attach}, + # #'add_method': 'add', 'add_method_kwargs': 'document' + # } + #} + + ''' + def create(self, validated_data): + """ + queryset = Tag.objects.filter(pk__in=validated_data['tags_pk_list'].split(',')) + + #permission = self.object_permission.get('create') + + #if permission: + queryset = AccessControlList.objects.restrict_queryset( + permission=permission_tag_attach, queryset=queryset, + user=self.context['request'].user ) - def create(self, validated_data): - queryset = AccessControlList.objects.filter_by_access( + for tag in queryset.all(): + tag.attach_to( + document=self.context['document'], + user=self.context['request'].user + ) + """ + queryset = AccessControlList.objects.restrict_queryset( permission=permission_tag_attach, queryset=Tag.objects.all(), user=self.context['request'].user ) tag = get_object_or_404(queryset=queryset, pk=validated_data['tag_pk']) - tag.documents.add(self.context['document']) + tag.attach_to( + document=self.context['document'], + user=self.context['request'].user + ) return tag + #return None + ''' + + def get_document_tag_url(self, instance): + return reverse( + viewname='rest_api:document-tag-detail', kwargs={ + 'document_id': self.context['document'].pk, + 'tag_id': instance.pk + }, request=self.context['request'], format=self.context['format'] + ) + + +class DocumentTagAttachSerializer(serializers.Serializer): + tags_pk_list = serializers.CharField( + help_text=_( + 'Comma separated list of tag primary keys that will be attached ' + 'to this document.' + ), write_only=True + ) + + +class TagAttachSerializer(serializers.Serializer): +#class TagAttachSerializer(TagSerializer): + documents_pk_list = serializers.CharField( + help_text=_( + 'Comma separated list of document primary keys to which this ' + 'tag will be attached.' + ), write_only=True + ) + + #class Meta(TagSerializer.Meta): + # fields = TagSerializer.Meta.fields + ('documents_pk_list',) + # read_only_fields = TagSerializer.Meta.fields + + def attach(self, instance): + queryset = AccessControlList.objects.restrict_queryset( + permission=permission_tag_attach, queryset=Document.objects.all(), + user=self.context['request'].user + ) + + for document in queryset.filter(pk__in=self.validated_data['documents_pk_list'].split(',')): + instance.attach_to(document=document, user=self.context['request'].user) + + #print '@@@@@@@', self.validated_data['document_pk_list'] + #print '@@@@@@@', instance + #print '22222', validated_data + #print '!!!', self.data['document_pk_list'] + + +class TagRemoveSerializer(serializers.Serializer): + documents_pk_list = serializers.CharField( + help_text=_( + 'Comma separated list of document primary keys from which this ' + 'tag will be removed.' + ), write_only=True + ) + + def remove(self, instance): + queryset = AccessControlList.objects.restrict_queryset( + permission=permission_tag_attach, queryset=Document.objects.all(), + user=self.context['request'].user + ) + + for document in queryset.filter(pk__in=self.validated_data['documents_pk_list'].split(',')): + instance.remove_from(document=document, user=self.context['request'].user) + + + +class RelatedModel(object): + @classmethod + def generate(cls, serializer, validated_data): + result = [] + + kwargs = getattr(serializer.Meta, 'related_model_kwargs', {}) + kwargs.update({'serializer': serializer}) + + for field_name in getattr(serializer.Meta, 'related_models', []): + kwargs.update({'field_name': field_name}) + related_field = cls(**kwargs) + related_field.pop_pk_list(validated_data=validated_data) + result.append(related_field) + + return result + + def __init__(self, field_name, serializer, pk_list_field=None, model=None, object_permission=None): + self.field_name = field_name + self._pk_list_field = pk_list_field + self.model = model + self.object_permission = object_permission + self.serializer = serializer + + def create(self, instance): + field = self.get_field(instance=instance) + field.clear() + #model = self.get_model() + + queryset = self.get_model().objects.filter(pk__in=self.pk_list.split(',')) + + permission = self.object_permission.get('create') + + if permission: + queryset = AccessControlList.objects.restrict_queryset( + permission=permission, + queryset=queryset, + user=self.serializer.context['request'].user + ) + + self.related_add() + + field.add(*queryset) + #fieldqueryset=queryset) + + #def related_add(self, queryset): + # self.get_field().add(*queryset) + + + #def _get_m2m_field(self, instance): + # getattr(instance, m2m_field_name).all() + + """ + def _add_m2m(self, instance, m2m_pk_list, permission): + m2m_field = self._get_m2m_field() + m2m_field.clear() + + queryset = AccessControlList.objects.restrict_queryset( + permission=permission, + queryset=m2m_model.objects.filter(pk__in=m2m_pk_list.split(',')), + user=self.context['request'].user + ) + + #m2m_field.add(*queryset) + self._m2m_add(m2m_field=m2m_field, queryset=queryset) + """ + + def get_model(self): + return self.model or self.get_field.model + + def get_field(self, instance): + return getattr(instance, self.field_name) + + def get_pk_list_field_name(self): + return self._pk_list_field or '{}_pk_list'.format(self.field_name) + + def pop_pk_list(self, validated_data): + self.pk_list = validated_data.pop(self.get_pk_list_field_name(), '') + + +class RelatedModelSerializerMixin(object): + #m2m_pk_list_name = 'documents_pk_list' + #m2m_field_name = 'documents' + #m2m_model = Document + + """ + class Meta: + extra_kwargs = { + 'url': { + 'lookup_field': 'pk', 'lookup_url_kwarg': 'tag_pk', + 'view_name': 'rest_api:tag-detail' + } + } + fields = ( + 'color', 'documents_count', 'documents_pk_list', 'documents_url', + 'id', 'label', 'url' + ) + model = Tag + related_models = ('documents',) + related_models_kwargs = { + 'documents': { + 'pk_list_field': 'documents_pk_list', 'model': Document, + 'permissions': {'create': permission_tag_attach} + } + } + """ + + def _get_m2m_field(self, instance): + getattr(instance, m2m_field_name).all() + + def _add_m2m(self, instance, m2m_pk_list, permission): + m2m_field = self._get_m2m_field() + m2m_field.clear() + + queryset = AccessControlList.objects.restrict_queryset( + permission=permission, + queryset=m2m_model.objects.filter(pk__in=m2m_pk_list.split(',')), + user=self.context['request'].user + ) + + #m2m_field.add(*queryset) + self._m2m_add(m2m_field=m2m_field, queryset=queryset) + + #def _m2m_add(self, m2m_field, queryset): + # m2m_field.add(*queryset) + + def _m2m_add(self, m2m_field, queryset): + for document in queryset.all(): + m2m_field.add(document=document, user=self.context['request'].user) + + def create(self, validated_data): + related_objects = RelatedModel.generate( + serializer=self, validated_data=validated_data + ) + + instance = super(RelatedModelSerializerMixin, self).create( + validated_data=validated_data + ) + + #TODO: return a container class + #TODO:related_objects.create(instance=instance) + for related_object in related_objects: + related_object.create(instance=instance) + + #if m2m_pk_list: + ## self._add_m2m( + # instance=instance, m2m_pk_list=m2m_pk_list, + # permission=permission_tag_add + # ) + + return instance + + ''' + # Extract the related field data before calling the superclass + # .create() and avoid an error due to unknown field data. + + #related_models = self.Meta.related_models + + #self.Meta.related_models + related_models_dictionary = {} + for related_model in self.Meta.related_models: + + #if self.m2m_pk_list_name: + m2m_pk_list = validated_data.pop(self.get_related_model_pk_list(), '') + + instance = super(RelatedObjectSerializerMixin, self).create( + validated_data=validated_data + ) + + if m2m_pk_list: + self._add_m2m( + instance=instance, m2m_pk_list=m2m_pk_list, + permission=permission_tag_add + ) + + return instance + ''' + diff --git a/mayan/apps/tags/static/tags/js/tags_form.js b/mayan/apps/tags/static/tags/js/tags_form.js new file mode 100644 index 0000000000..290f1dd82f --- /dev/null +++ b/mayan/apps/tags/static/tags/js/tags_form.js @@ -0,0 +1,26 @@ +'use strict'; + +var tagSelectionTemplate = function (tag, container) { + var $tag = $( + ' ' + escape(tag.text) + '' + ); + container[0].style.background = tag.element.dataset.color; + return $tag; +} + +var tagResultTemplate = function (tag) { + if (!tag.element) { return ''; } + var $tag = $( + ' ' + escape(tag.text) + '' + ); + return $tag; +} + +jQuery(document).ready(function() { + $('.select2-tags').select2({ + templateSelection: tagSelectionTemplate, + templateResult: tagResultTemplate, + width: '100%' + }); +}); + diff --git a/mayan/apps/tags/templates/tags/document_tags_widget.html b/mayan/apps/tags/templates/tags/document_tags_widget.html new file mode 100644 index 0000000000..4d0e6fe825 --- /dev/null +++ b/mayan/apps/tags/templates/tags/document_tags_widget.html @@ -0,0 +1,5 @@ +
+ {% for tag in tags %} + {% include 'tags/tag_widget.html' with tag=tag %} + {% endfor %} +
diff --git a/mayan/apps/tags/templates/tags/forms/widgets/tag_select_option.html b/mayan/apps/tags/templates/tags/forms/widgets/tag_select_option.html index 8dabad9809..5a657c2915 100644 --- a/mayan/apps/tags/templates/tags/forms/widgets/tag_select_option.html +++ b/mayan/apps/tags/templates/tags/forms/widgets/tag_select_option.html @@ -1,2 +1 @@ {% include 'django/forms/widgets/select_option.html' %} - diff --git a/mayan/apps/tags/tests/literals.py b/mayan/apps/tags/tests/literals.py index 6949b2d9dd..1c08f4765b 100644 --- a/mayan/apps/tags/tests/literals.py +++ b/mayan/apps/tags/tests/literals.py @@ -9,15 +9,15 @@ TEST_TAG_INDEX_HAS_TAG = 'HAS_TAG' TEST_TAG_INDEX_NO_TAG = 'NO_TAG' TEST_TAG_INDEX_NODE_TEMPLATE = ''' {{% for tag in document.get_tags().all() %}} -{{% if tag.label == "{}" %}} -{} + {{% if tag.label == "{label}" %}} + {has_tag} + {{% else %}} + {not_tagged} + {{% endif %}} {{% else %}} -NO_TAG -{{% endif %}} -{{% else %}} -NO_TAG + {not_tagged} {{% endfor %}} '''.format( - TEST_TAG_LABEL, TEST_TAG_INDEX_HAS_TAG, TEST_TAG_INDEX_NO_TAG, - TEST_TAG_INDEX_NO_TAG + label=TEST_TAG_LABEL, has_tag=TEST_TAG_INDEX_HAS_TAG, + not_tagged=TEST_TAG_INDEX_NO_TAG ).replace('\n', '') diff --git a/mayan/apps/tags/tests/mixins.py b/mayan/apps/tags/tests/mixins.py index d93041edb5..2b4b1dd6f4 100644 --- a/mayan/apps/tags/tests/mixins.py +++ b/mayan/apps/tags/tests/mixins.py @@ -9,39 +9,100 @@ from .literals import ( class TagTestMixin(object): - def _create_tag(self): - self.tag = Tag.objects.create( + def _create_test_tag(self): + self.test_tag = Tag.objects.create( color=TEST_TAG_COLOR, label=TEST_TAG_LABEL ) + +class TagAPITestMixin(object): def _request_api_tag_create_view(self): return self.post( viewname='rest_api:tag-list', data={ - 'label': TEST_TAG_LABEL, 'color': TEST_TAG_COLOR + 'color': TEST_TAG_COLOR, 'label': TEST_TAG_LABEL + } + ) + + def _request_api_tag_create_and_attach_view(self): + return self.post( + viewname='rest_api:tag-list', data={ + 'color': TEST_TAG_COLOR, 'label': TEST_TAG_LABEL, + 'document_id_list': self.document.pk } ) def _request_api_tag_delete_view(self): return self.delete( - viewname='rest_api:tag-detail', kwargs={'tag_pk': self.tag.pk} + viewname='rest_api:tag-detail', kwargs={'tag_id': self.test_tag.pk} ) - def _request_api_tag_edit_via_patch_view(self): + def _request_api_tag_edit_patch_view(self): return self.patch( - viewname='rest_api:tag-detail', kwargs={'tag_pk': self.tag.pk}, data={ + viewname='rest_api:tag-detail', kwargs={ + 'tag_id': self.test_tag.pk + }, data={ 'label': TEST_TAG_LABEL_EDITED, 'color': TEST_TAG_COLOR_EDITED } ) - def _request_api_tag_edit_via_put_view(self): + def _request_api_tag_edit_put_view(self): return self.put( - viewname='rest_api:tag-detail', kwargs={'tag_pk': self.tag.pk}, data={ + viewname='rest_api:tag-detail', kwargs={ + 'tag_id': self.test_tag.pk + }, data={ 'label': TEST_TAG_LABEL_EDITED, 'color': TEST_TAG_COLOR_EDITED } ) + def _request_api_tag_list_view(self): + return self.get(viewname='rest_api:tag-list') + + +class TagViewTestMixin(object): + def _request_document_tag_multiple_attach_view(self): + return self.post( + viewname='tags:document_tag_multiple_attach', + kwargs={'document_id': self.document.pk}, data={ + 'tags': self.test_tag.pk, + } + ) + + def _request_document_multiple_tag_multiple_attach_view(self): + return self.post( + viewname='tags:document_multiple_tag_multiple_attach', data={ + 'id_list': self.document.pk, 'tags': self.test_tag.pk, + } + ) + + def _request_document_tag_multiple_remove_view(self): + return self.post( + viewname='tags:document_tag_multiple_remove', + kwargs={'document_id': self.document.pk}, data={ + 'tags': self.test_tag.pk, + } + ) + + def _request_document_multiple_tag_multiple_remove_view(self): + return self.post( + viewname='tags:document_multiple_tag_multiple_remove', + data={ + 'id_list': self.document.pk, + 'tags': self.test_tag.pk, + } + ) + + def _request_document_tag_list_view(self): + return self.get( + viewname='tags:document_tag_list', + kwargs={ + 'document_id': self.document.pk, + } + ) + + # Normal tag view + def _request_tag_create_view(self): return self.post( viewname='tags:tag_create', data={ @@ -52,68 +113,19 @@ class TagTestMixin(object): def _request_tag_delete_view(self): return self.post( - viewname='tags:tag_delete', kwargs={'tag_pk': self.tag.pk} + viewname='tags:tag_delete', kwargs={'tag_id': self.test_tag.pk}, ) def _request_tag_edit_view(self): return self.post( - viewname='tags:tag_edit', kwargs={'tag_pk': self.tag.pk}, data={ + viewname='tags:tag_edit', kwargs={'tag_id': self.test_tag.pk}, + data={ 'label': TEST_TAG_LABEL_EDITED, 'color': TEST_TAG_COLOR_EDITED } ) - def _request_multiple_delete_view(self): + def _request_tag_multiple_delete_view(self): return self.post( viewname='tags:tag_multiple_delete', - data={'id_list': self.tag.pk}, - ) - - def _request_edit_tag_view(self): - return self.post( - viewname='tags:tag_edit', kwargs={'tag_pk': self.tag.pk}, data={ - 'label': TEST_TAG_LABEL_EDITED, 'color': TEST_TAG_COLOR_EDITED - } - ) - - def _request_create_tag_view(self): - return self.post( - viewname='tags:tag_create', data={ - 'label': TEST_TAG_LABEL, - 'color': TEST_TAG_COLOR - } - ) - - def _request_attach_tag_view(self): - return self.post( - viewname='tags:tag_attach', - kwargs={'document_pk': self.document.pk}, data={ - 'tags': self.tag.pk, - 'user': self.user.pk - } - ) - - def _request_multiple_attach_tag_view(self): - return self.post( - viewname='tags:multiple_documents_tag_attach', data={ - 'id_list': self.document.pk, 'tags': self.tag.pk, - 'user': self.user.pk - } - ) - - def _request_single_document_multiple_tag_remove_view(self): - return self.post( - viewname='tags:single_document_multiple_tag_remove', - kwargs={'document_pk': self.document.pk}, data={ - 'id_list': self.document.pk, - 'tags': self.tag.pk, - } - ) - - def _request_multiple_documents_selection_tag_remove_view(self): - return self.post( - viewname='tags:multiple_documents_selection_tag_remove', - data={ - 'id_list': self.document.pk, - 'tags': self.tag.pk, - } + data={'id_list': self.test_tag.pk} ) diff --git a/mayan/apps/tags/tests/test_api.py b/mayan/apps/tags/tests/test_api.py index ec55335692..4c259f2de9 100644 --- a/mayan/apps/tags/tests/test_api.py +++ b/mayan/apps/tags/tests/test_api.py @@ -21,11 +21,7 @@ from .literals import ( from .mixins import TagTestMixin -class TagAPITestCase(TagTestMixin, DocumentTestMixin, BaseAPITestCase): - def setUp(self): - super(TagAPITestCase, self).setUp() - self.login_user() - +class TagAPITestCase(TagTestMixin, BaseAPITestCase): def test_tag_create_view_no_permission(self): response = self._request_api_tag_create_view() self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) @@ -46,284 +42,301 @@ class TagAPITestCase(TagTestMixin, DocumentTestMixin, BaseAPITestCase): self.assertEqual(tag.color, TEST_TAG_COLOR) def test_tag_delete_view_no_access(self): - self._create_tag() + self._create_test_tag() response = self._request_api_tag_delete_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag in Tag.objects.all()) + self.assertTrue(self.test_tag in Tag.objects.all()) self.assertEqual(Tag.objects.all().count(), 1) def test_tag_delete_view_with_access(self): - self._create_tag() - self.grant_access(obj=self.tag, permission=permission_tag_delete) + self._create_test_tag() + self.grant_access(obj=self.test_tag, permission=permission_tag_delete) response = self._request_api_tag_delete_view() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(Tag.objects.all().count(), 0) - def test_tag_edit_via_patch_no_access(self): - self._create_tag() - response = self._request_api_tag_edit_via_patch_view() + def test_tag_edit_patch_view_no_access(self): + self._create_test_tag() + response = self._request_api_tag_edit_patch_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.tag.refresh_from_db() - self.assertEqual(self.tag.label, TEST_TAG_LABEL) - self.assertEqual(self.tag.color, TEST_TAG_COLOR) + self.test_tag.refresh_from_db() + self.assertEqual(self.test_tag.label, TEST_TAG_LABEL) + self.assertEqual(self.test_tag.color, TEST_TAG_COLOR) self.assertEqual(Tag.objects.all().count(), 1) - def test_tag_edit_via_patch_with_access(self): - self._create_tag() - self.grant_access(obj=self.tag, permission=permission_tag_edit) - response = self._request_api_tag_edit_via_patch_view() + def test_tag_edit_patch_view_with_access(self): + self._create_test_tag() + self.grant_access(obj=self.test_tag, permission=permission_tag_edit) + response = self._request_api_tag_edit_patch_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.tag.refresh_from_db() - self.assertEqual(self.tag.label, TEST_TAG_LABEL_EDITED) - self.assertEqual(self.tag.color, TEST_TAG_COLOR_EDITED) + self.test_tag.refresh_from_db() + self.assertEqual(self.test_tag.label, TEST_TAG_LABEL_EDITED) + self.assertEqual(self.test_tag.color, TEST_TAG_COLOR_EDITED) self.assertEqual(Tag.objects.all().count(), 1) - def test_tag_edit_via_put_no_access(self): - self._create_tag() - response = self._request_api_tag_edit_via_put_view() + def test_tag_edit_put_view_no_access(self): + self._create_test_tag() + response = self._request_api_tag_edit_put_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.tag.refresh_from_db() - self.assertEqual(self.tag.label, TEST_TAG_LABEL) - self.assertEqual(self.tag.color, TEST_TAG_COLOR) + self.test_tag.refresh_from_db() + self.assertEqual(self.test_tag.label, TEST_TAG_LABEL) + self.assertEqual(self.test_tag.color, TEST_TAG_COLOR) self.assertEqual(Tag.objects.all().count(), 1) - def test_tag_edit_via_put_with_access(self): - self._create_tag() - self.grant_access(obj=self.tag, permission=permission_tag_edit) - response = self._request_api_tag_edit_via_put_view() + def test_tag_edit_put_view_with_access(self): + self._create_test_tag() + self.grant_access(obj=self.test_tag, permission=permission_tag_edit) + response = self._request_api_tag_edit_put_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.tag.refresh_from_db() - self.assertEqual(self.tag.label, TEST_TAG_LABEL_EDITED) - self.assertEqual(self.tag.color, TEST_TAG_COLOR_EDITED) + self.test_tag.refresh_from_db() + self.assertEqual(self.test_tag.label, TEST_TAG_LABEL_EDITED) + self.assertEqual(self.test_tag.color, TEST_TAG_COLOR_EDITED) self.assertEqual(Tag.objects.all().count(), 1) - -class DocumentAPITestCase(TagTestMixin, DocumentTestMixin, BaseAPITestCase): - auto_upload_document = False - - def setUp(self): - super(DocumentAPITestCase, self).setUp() - self.login_user() - - def _request_api_tag_document_list_view(self): - return self.get( - viewname='rest_api:tag-document-list', - kwargs={'tag_pk': self.tag.pk} - ) - - def test_tag_document_list_view_no_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - response = self._request_api_tag_document_list_view() - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_tag_document_list_view_with_tag_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.tag, permission=permission_tag_view) - response = self._request_api_tag_document_list_view() + def test_tag_list_view_no_access(self): + self._create_test_tag() + response = self._request_api_tag_list_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 0) - def test_tag_document_list_view_with_document_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access( - obj=self.document, permission=permission_document_view - ) - response = self._request_api_tag_document_list_view() - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_tag_document_list_view_with_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.tag, permission=permission_tag_view) - self.grant_access( - obj=self.document, permission=permission_document_view - ) - response = self._request_api_tag_document_list_view() + def test_tag_list_view_with_access(self): + self._create_test_tag() + self.grant_access(obj=self.test_tag, permission=permission_tag_view) + response = self._request_api_tag_list_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.data['results'][0]['uuid'], - force_text(self.document.uuid) - ) + self.assertEqual(response.data['count'], 1) - def _request_api_document_attach_tag_view(self): + +class DocumentTagAPITestCase(TagTestMixin, DocumentTestMixin, BaseAPITestCase): + auto_upload_document = False + + def _request_api_document_tag_attach_view(self): return self.post( viewname='rest_api:document-tag-list', - kwargs={'document_pk': self.document.pk}, - data={'tag_pk': self.tag.pk} + kwargs={'document_id': self.test_document.pk}, + data={'tag_id': self.test_tag.pk} ) - def test_document_attach_tag_view_no_access(self): - self._create_tag() - self.document = self.upload_document() - response = self._request_api_document_attach_tag_view() + def test_document_tag_attach_view_no_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + response = self._request_api_document_tag_attach_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag not in self.document.tags.all()) + self.assertTrue(self.test_tag not in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) - def test_document_attach_tag_view_with_document_access(self): - self._create_tag() - self.document = self.upload_document() - self.grant_access(obj=self.document, permission=permission_tag_attach) - response = self._request_api_document_attach_tag_view() + def test_document_tag_attach_view_with_document_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.grant_access(obj=self.test_document, permission=permission_tag_attach) + response = self._request_api_document_tag_attach_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag not in self.document.tags.all()) + self.assertTrue(self.test_tag not in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) - def test_document_attach_tag_view_with_tag_access(self): - self._create_tag() - self.document = self.upload_document() - self.grant_access(obj=self.tag, permission=permission_tag_attach) - response = self._request_api_document_attach_tag_view() + def test_document_tag_attach_view_with_tag_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + response = self._request_api_document_tag_attach_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag not in self.document.tags.all()) + self.assertTrue(self.test_tag not in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) - def test_document_attach_tag_view_with_full_access(self): - self._create_tag() - self.document = self.upload_document() - self.grant_access(obj=self.document, permission=permission_tag_attach) - self.grant_access(obj=self.tag, permission=permission_tag_attach) - response = self._request_api_document_attach_tag_view() + def test_document_tag_attach_view_with_full_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.grant_access( + obj=self.test_document, permission=permission_tag_attach + ) + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + response = self._request_api_document_tag_attach_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertTrue(self.tag in self.document.tags.all()) + self.assertTrue(self.test_tag in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) def _request_api_document_tag_detail_view(self): return self.get( viewname='rest_api:document-tag-detail', kwargs={ - 'document_pk': self.document.pk, 'tag_pk': self.tag.pk + 'document_id': self.test_document.pk, 'tag_id': self.test_tag.pk } ) def test_document_tag_detail_view_no_permission(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) response = self._request_api_document_tag_detail_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_document_tag_detail_view_with_document_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) self.grant_access( - obj=self.document, permission=permission_document_view + obj=self.test_document, permission=permission_document_view ) response = self._request_api_document_tag_detail_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_document_tag_detail_view_with_tag_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.tag, permission=permission_tag_view) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_tag, permission=permission_tag_view) response = self._request_api_document_tag_detail_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_document_tag_detail_view_with_full_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.tag, permission=permission_tag_view) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_tag, permission=permission_tag_view) self.grant_access( - obj=self.document, permission=permission_tag_view + obj=self.test_document, permission=permission_tag_view ) response = self._request_api_document_tag_detail_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['label'], self.tag.label) + self.assertEqual(response.data['label'], self.test_tag.label) def _request_api_document_tag_list_view(self): return self.get( viewname='rest_api:document-tag-list', - kwargs={'document_pk': self.document.pk} + kwargs={'document_id': self.test_document.pk} ) def test_document_tag_list_view_no_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) response = self._request_api_document_tag_list_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_document_tag_list_view_with_document_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.document, permission=permission_tag_view) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_document, permission=permission_tag_view) response = self._request_api_document_tag_list_view() self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 0) def test_document_tag_list_view_with_tag_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.tag, permission=permission_tag_view) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_tag, permission=permission_tag_view) response = self._request_api_document_tag_list_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_document_tag_list_view_with_full_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.document, permission=permission_tag_view) - self.grant_access(obj=self.tag, permission=permission_tag_view) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_document, permission=permission_tag_view) + self.grant_access(obj=self.test_tag, permission=permission_tag_view) response = self._request_api_document_tag_list_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['results'][0]['label'], self.tag.label) + self.assertEqual( + response.data['results'][0]['label'], self.test_tag.label + ) def _request_api_document_tag_remove_view(self): return self.delete( viewname='rest_api:document-tag-detail', kwargs={ - 'document_pk': self.document.pk, 'tag_pk': self.tag.pk + 'document_id': self.test_document.pk, 'tag_id': self.test_tag.pk } ) def test_document_tag_remove_view_no_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) response = self._request_api_document_tag_remove_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag in self.document.tags.all()) + self.assertTrue(self.test_tag in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) def test_document_tag_remove_view_with_document_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.document, permission=permission_tag_remove) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_document, permission=permission_tag_remove) response = self._request_api_document_tag_remove_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag in self.document.tags.all()) + self.assertTrue(self.test_tag in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) def test_document_tag_remove_view_with_tag_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) - self.grant_access(obj=self.tag, permission=permission_tag_remove) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_tag, permission=permission_tag_remove) response = self._request_api_document_tag_remove_view() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertTrue(self.tag in self.document.tags.all()) + self.assertTrue(self.test_tag in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) def test_document_tag_remove_view_with_full_access(self): - self._create_tag() - self.document = self.upload_document() - self.tag.documents.add(self.document) + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) self.grant_access( - obj=self.document, permission=permission_tag_remove + obj=self.test_document, permission=permission_tag_remove ) - self.grant_access(obj=self.tag, permission=permission_tag_remove) + self.grant_access(obj=self.test_tag, permission=permission_tag_remove) response = self._request_api_document_tag_remove_view() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertFalse(self.tag in self.document.tags.all()) + self.assertFalse(self.test_tag in self.test_document.tags.all()) self.assertEqual(Tag.objects.all().count(), 1) + + +class TagDocumentAPITestCase(TagTestMixin, DocumentTestMixin, BaseAPITestCase): + auto_upload_document = False + + def _request_api_tag_document_list_view(self): + return self.get( + viewname='rest_api:tag-document-list', + kwargs={'tag_id': self.test_tag.pk} + ) + + def test_tag_document_list_view_no_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + response = self._request_api_tag_document_list_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_tag_document_list_view_with_tag_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_tag, permission=permission_tag_view) + response = self._request_api_tag_document_list_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 0) + + def test_tag_document_list_view_with_document_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access( + obj=self.test_document, permission=permission_document_view + ) + response = self._request_api_tag_document_list_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_tag_document_list_view_with_access(self): + self._create_test_tag() + self.test_document = self.upload_document() + self.test_tag.documents.add(self.test_document) + self.grant_access(obj=self.test_tag, permission=permission_tag_view) + self.grant_access( + obj=self.test_document, permission=permission_document_view + ) + response = self._request_api_tag_document_list_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data['results'][0]['uuid'], + force_text(self.test_document.uuid) + ) diff --git a/mayan/apps/tags/tests/test_events.py b/mayan/apps/tags/tests/test_events.py index 33fa2bf95d..46fcce25e8 100644 --- a/mayan/apps/tags/tests/test_events.py +++ b/mayan/apps/tags/tests/test_events.py @@ -10,7 +10,7 @@ from ..permissions import permission_tag_create, permission_tag_edit from .mixins import TagTestMixin - +#TODO: Add tests for event_tag_remove and event_tag_attach class TagEventsTestCase(TagTestMixin, GenericDocumentViewTestCase): def setUp(self): super(TagEventsTestCase, self).setUp() @@ -38,10 +38,10 @@ class TagEventsTestCase(TagTestMixin, GenericDocumentViewTestCase): self.assertEqual(event.verb, event_tag_created.id) self.assertEqual(event.target, tag) - self.assertEqual(event.actor, self.user) + self.assertEqual(event.actor, self._test_case_user) def test_tag_edit_event_no_permissions(self): - self._create_tag() + self._create_test_tag() Action.objects.all().delete() response = self._request_tag_edit_view() @@ -49,11 +49,11 @@ class TagEventsTestCase(TagTestMixin, GenericDocumentViewTestCase): self.assertEqual(Action.objects.count(), 0) def test_tag_edit_event_with_access(self): - self._create_tag() + self._create_test_tag() Action.objects.all().delete() self.grant_access( - permission=permission_tag_edit, obj=self.tag + permission=permission_tag_edit, obj=self.test_tag ) response = self._request_tag_edit_view() @@ -63,5 +63,5 @@ class TagEventsTestCase(TagTestMixin, GenericDocumentViewTestCase): event = Action.objects.first() self.assertEqual(event.verb, event_tag_edited.id) - self.assertEqual(event.target, self.tag) - self.assertEqual(event.actor, self.user) + self.assertEqual(event.target, self.test_tag) + self.assertEqual(event.actor, self._test_case_user) diff --git a/mayan/apps/tags/tests/test_indexing.py b/mayan/apps/tags/tests/test_indexing.py index 415ec251d0..ddd7925e16 100644 --- a/mayan/apps/tags/tests/test_indexing.py +++ b/mayan/apps/tags/tests/test_indexing.py @@ -11,9 +11,10 @@ from .literals import ( TEST_TAG_COLOR, TEST_TAG_LABEL, TEST_TAG_INDEX_HAS_TAG, TEST_TAG_INDEX_NO_TAG, TEST_TAG_INDEX_NODE_TEMPLATE ) +from .mixins import TagTestMixin -class TagSignalIndexingTestCase(DocumentTestMixin, BaseTestCase): +class TagSignalIndexingTestCase(TagTestMixin, DocumentTestMixin, BaseTestCase): auto_upload_document = False def test_tag_indexing(self): @@ -27,7 +28,8 @@ class TagSignalIndexingTestCase(DocumentTestMixin, BaseTestCase): link_documents=True ) - tag = Tag.objects.create(color=TEST_TAG_COLOR, label=TEST_TAG_LABEL) + self._create_test_tag() + self.document = self.upload_document() self.assertTrue( @@ -36,7 +38,7 @@ class TagSignalIndexingTestCase(DocumentTestMixin, BaseTestCase): ).documents.all() ) - tag.documents.add(self.document) + self.test_tag.documents.add(self.document) self.assertTrue( self.document in IndexInstanceNode.objects.get( @@ -44,7 +46,7 @@ class TagSignalIndexingTestCase(DocumentTestMixin, BaseTestCase): ).documents.all() ) - tag.delete() + self.test_tag.delete() self.assertTrue( self.document in IndexInstanceNode.objects.get( diff --git a/mayan/apps/tags/tests/test_links.py b/mayan/apps/tags/tests/test_links.py new file mode 100644 index 0000000000..8ef56f5dcb --- /dev/null +++ b/mayan/apps/tags/tests/test_links.py @@ -0,0 +1,41 @@ +from __future__ import unicode_literals + +from django.urls import reverse + +from mayan.apps.documents.tests import GenericDocumentViewTestCase + +from ..links import link_document_tag_list +from ..permissions import permission_tag_view + +from .mixins import TagTestMixin + + +class DocumentLinksTestCase(TagTestMixin, GenericDocumentViewTestCase): + def _request_document_tag_list_link(self): + self.add_test_view(test_object=self.document) + context = self.get_test_view() + return link_document_tag_list.resolve(context=context) + + def test_document_tag_list_no_permission(self): + self._create_test_tag() + resolved_link = self._request_document_tag_list_link() + self.assertEqual(resolved_link, None) + + def test_document_tag_list_with_full_access(self): + self._create_test_tag() + self.grant_access( + obj=self.document, permission=permission_tag_view + ) + self.grant_access( + obj=self.test_tag, permission=permission_tag_view + ) + resolved_link = self._request_document_tag_list_link() + + self.assertNotEqual(resolved_link, None) + self.assertEqual( + resolved_link.url, + reverse( + viewname='tags:document_tag_list', + kwargs={'document_id': self.document.pk} + ) + ) diff --git a/mayan/apps/tags/tests/test_views.py b/mayan/apps/tags/tests/test_views.py index 7f699357ca..bdfa59e73a 100644 --- a/mayan/apps/tags/tests/test_views.py +++ b/mayan/apps/tags/tests/test_views.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +from django.utils.encoding import force_text + from mayan.apps.common.tests import GenericViewTestCase from mayan.apps.documents.permissions import permission_document_view from mayan.apps.documents.tests import GenericDocumentViewTestCase @@ -14,21 +16,17 @@ from .literals import ( TEST_TAG_COLOR, TEST_TAG_COLOR_EDITED, TEST_TAG_LABEL, TEST_TAG_LABEL_EDITED ) -from .mixins import TagTestMixin +from .mixins import TagTestMixin, TagViewTestMixin -class TagViewTestCase(TagTestMixin, GenericViewTestCase): +class TagViewTestCase(TagViewTestMixin, TagTestMixin, GenericViewTestCase): def test_tag_create_view_no_permissions(self): - self.login_user() - response = self._request_tag_create_view() self.assertEqual(response.status_code, 403) self.assertEqual(Tag.objects.count(), 0) def test_tag_create_view_with_permissions(self): - self.login_user() - self.grant_permission(permission=permission_tag_create) response = self._request_tag_create_view() self.assertEqual(response.status_code, 302) @@ -39,18 +37,16 @@ class TagViewTestCase(TagTestMixin, GenericViewTestCase): self.assertEqual(tag.color, TEST_TAG_COLOR) def test_tag_delete_view_no_permissions(self): - self.login_user() - self._create_tag() + self._create_test_tag() response = self._request_tag_delete_view() - self.assertEqual(response.status_code, 302) + self.assertEqual(response.status_code, 404) self.assertEqual(Tag.objects.count(), 1) def test_tag_delete_view_with_access(self): - self.login_user() - self._create_tag() + self._create_test_tag() - self.grant_access(obj=self.tag, permission=permission_tag_delete) + self.grant_access(obj=self.test_tag, permission=permission_tag_delete) response = self._request_tag_delete_view() self.assertEqual(response.status_code, 302) @@ -58,190 +54,295 @@ class TagViewTestCase(TagTestMixin, GenericViewTestCase): self.assertEqual(Tag.objects.count(), 0) def test_tag_multiple_delete_view_no_permissions(self): - self.login_user() - self._create_tag() + self._create_test_tag() - response = self._request_multiple_delete_view() - self.assertEqual(response.status_code, 302) + response = self._request_tag_multiple_delete_view() + self.assertEqual(response.status_code, 404) self.assertEqual(Tag.objects.count(), 1) def test_tag_multiple_delete_view_with_access(self): - self.login_user() - self._create_tag() + self._create_test_tag() - self.grant_access(obj=self.tag, permission=permission_tag_delete) + self.grant_access(obj=self.test_tag, permission=permission_tag_delete) - response = self._request_multiple_delete_view() + response = self._request_tag_multiple_delete_view() self.assertEqual(response.status_code, 302) - self.assertEqual(Tag.objects.count(), 0) def test_tag_edit_view_no_permissions(self): - self.login_user() - self._create_tag() + self._create_test_tag() - response = self._request_edit_tag_view() + response = self._request_tag_edit_view() self.assertEqual(response.status_code, 404) - tag = Tag.objects.get(pk=self.tag.pk) + tag = Tag.objects.get(pk=self.test_tag.pk) self.assertEqual(tag.label, TEST_TAG_LABEL) self.assertEqual(tag.color, TEST_TAG_COLOR) def test_tag_edit_view_with_access(self): - self.login_user() - self._create_tag() + self._create_test_tag() - self.grant_access(obj=self.tag, permission=permission_tag_edit) + self.grant_access(obj=self.test_tag, permission=permission_tag_edit) - response = self._request_edit_tag_view() + response = self._request_tag_edit_view() self.assertEqual(response.status_code, 302) - tag = Tag.objects.get(pk=self.tag.pk) + tag = Tag.objects.get(pk=self.test_tag.pk) self.assertEqual(tag.label, TEST_TAG_LABEL_EDITED) self.assertEqual(tag.color, TEST_TAG_COLOR_EDITED) -class TagDocumentsViewTestCase(TagTestMixin, GenericDocumentViewTestCase): - def _request_document_list_view(self): - return self.get(viewname='documents:document_list') +class TagDocumentsViewTestCase(TagViewTestMixin, TagTestMixin, GenericDocumentViewTestCase): + def test_document_tag_attach_view_no_permission(self): + self._create_test_tag() - def test_document_tags_widget_no_permissions(self): - self.login_user() - self._create_tag() + response = self._request_document_tag_multiple_attach_view() + self.assertEqual(response.status_code, 404) + self.assertEqual(self.test_document.tags.count(), 0) - self.tag.documents.add(self.document) - response = self._request_document_list_view() - self.assertNotContains( - response=response, text=TEST_TAG_LABEL, status_code=200 - ) + def test_document_tag_attach_view_with_document_access(self): + self._create_test_tag() - def test_document_tags_widget_with_access(self): - self.login_user() - self._create_tag() - - self.tag.documents.add(self.document) - - self.grant_access(obj=self.tag, permission=permission_tag_view) self.grant_access( - obj=self.document, permission=permission_document_view + obj=self.test_document, permission=permission_tag_attach ) - - response = self._request_document_list_view() + response = self._request_document_tag_multiple_attach_view() self.assertContains( - response=response, text=TEST_TAG_LABEL, status_code=200 + response=response, text=force_text(self.test_document), + status_code=200 + ) + self.assertNotContains( + response=response, text=force_text(self.test_tag), status_code=200 ) - def test_document_attach_tag_view_no_permission(self): - self.login_user() - self._create_tag() + self.assertEqual(self.test_document.tags.count(), 0) - self.assertEqual(self.document.tags.count(), 0) + def test_document_tag_attach_view_with_tag_access(self): + self._create_test_tag() - self.grant_access(obj=self.tag, permission=permission_tag_attach) + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + response = self._request_document_tag_multiple_attach_view() + self.assertEqual(response.status_code, 404) - response = self._request_attach_tag_view() - # Redirect to previous URL and show warning message about having to - # select at least one object. - self.assertEqual(response.status_code, 302) - self.assertEqual(self.document.tags.count(), 0) + self.assertEqual(self.test_document.tags.count(), 0) - def test_document_attach_tag_view_with_access(self): - self.login_user() - self._create_tag() + def test_document_tag_attach_view_with_full_access(self): + self._create_test_tag() - self.assertEqual(self.document.tags.count(), 0) - - self.grant_access(obj=self.document, permission=permission_tag_attach) - self.grant_access(obj=self.tag, permission=permission_tag_attach) - # permission_tag_view is needed because the form filters the - # choices - self.grant_access(obj=self.tag, permission=permission_tag_view) - - response = self._request_attach_tag_view() + self.grant_access( + obj=self.test_document, permission=permission_tag_attach + ) + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + response = self._request_document_tag_multiple_attach_view() self.assertEqual(response.status_code, 302) self.assertQuerysetEqual( - self.document.tags.all(), (repr(self.tag),) + self.test_document.tags.all(), (repr(self.test_tag),) ) - def test_document_multiple_attach_tag_view_no_permission(self): - self.login_user() - self._create_tag() - self.grant_permission(permission=permission_tag_view) + def test_document_single_tag_attach_view_with_full_access(self): + """ + Test to make sure only the tag is attached to the selected document + """ + self._create_test_tag() + self.test_document_2 = self.upload_document() - response = self._request_multiple_attach_tag_view() - self.assertEqual(response.status_code, 200) - self.assertEqual(self.document.tags.count(), 0) - - def test_document_multiple_attach_tag_view_with_access(self): - self.login_user() - self._create_tag() - - self.grant_access(obj=self.document, permission=permission_tag_attach) - self.grant_access(obj=self.tag, permission=permission_tag_attach) - - # permission_tag_view is needed because the form filters the - # choices - self.grant_access(obj=self.tag, permission=permission_tag_view) - - response = self._request_multiple_attach_tag_view() + self.grant_access( + obj=self.test_document, permission=permission_tag_attach + ) + self.grant_access( + obj=self.test_document_2, permission=permission_tag_attach + ) + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + response = self._request_document_tag_multiple_attach_view() self.assertEqual(response.status_code, 302) self.assertQuerysetEqual( - self.document.tags.all(), (repr(self.tag),) + self.test_document.tags.all(), (repr(self.test_tag),) ) - def test_single_document_multiple_tag_remove_view_no_permissions(self): - self.login_user() - self._create_tag() + self.assertEqual(self.test_document_2.tags.count(), 0) - self.document.tags.add(self.tag) + def test_document_multiple_tag_attach_view_no_permission(self): + self._create_test_tag() - self.grant_access(obj=self.tag, permission=permission_tag_view) + response = self._request_document_multiple_tag_multiple_attach_view() + self.assertEqual(response.status_code, 404) + self.assertEqual(self.test_document.tags.count(), 0) - response = self._request_single_document_multiple_tag_remove_view() - self.assertEqual(response.status_code, 200) + def test_document_multiple_tag_attach_view_with_document_access(self): + self._create_test_tag() - self.assertQuerysetEqual(self.document.tags.all(), (repr(self.tag),)) + self.grant_access( + obj=self.test_document, permission=permission_tag_attach + ) - def test_single_document_multiple_tag_remove_view_with_access(self): - self.login_user() - self._create_tag() + response = self._request_document_multiple_tag_multiple_attach_view() - self.document.tags.add(self.tag) + self.assertContains( + response=response, text=force_text(self.test_document), + status_code=200 + ) + self.assertNotContains( + response=response, text=force_text(self.test_tag), status_code=200 + ) - self.grant_access(obj=self.document, permission=permission_tag_remove) - self.grant_access(obj=self.tag, permission=permission_tag_remove) - self.grant_access(obj=self.tag, permission=permission_tag_view) + self.assertEqual(self.test_document.tags.count(), 0) - response = self._request_single_document_multiple_tag_remove_view() + def test_document_multiple_tag_attach_view_with_tag_access(self): + self._create_test_tag() + + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + + response = self._request_document_multiple_tag_multiple_attach_view() + self.assertEqual(response.status_code, 404) + self.assertEqual(self.test_document.tags.count(), 0) + + def test_document_multiple_tag_attach_view_with_full_access(self): + self._create_test_tag() + + self.grant_access( + obj=self.test_document, permission=permission_tag_attach + ) + self.grant_access(obj=self.test_tag, permission=permission_tag_attach) + + response = self._request_document_multiple_tag_multiple_attach_view() self.assertEqual(response.status_code, 302) - self.assertEqual(self.document.tags.count(), 0) + self.assertQuerysetEqual( + self.test_document.tags.all(), (repr(self.test_tag),) + ) - def test_multiple_documents_selection_tag_remove_view_no_permissions(self): - self.login_user() - self._create_tag() + def test_document_tag_multiple_remove_view_no_permissions(self): + self._create_test_tag() - self.document.tags.add(self.tag) + self.test_document.tags.add(self.test_tag) - self.grant_access(obj=self.tag, permission=permission_tag_view) + response = self._request_document_tag_multiple_remove_view() + self.assertEqual(response.status_code, 404) - response = self._request_multiple_documents_selection_tag_remove_view() - self.assertEqual(response.status_code, 200) + self.assertQuerysetEqual( + self.test_document.tags.all(), (repr(self.test_tag),) + ) - self.assertQuerysetEqual(self.document.tags.all(), (repr(self.tag),)) + def test_document_tag_multiple_remove_view_with_document_access(self): + self._create_test_tag() - def test_multiple_documents_selection_tag_remove_view_with_access(self): - self.login_user() - self._create_tag() + self.test_document.tags.add(self.test_tag) - self.document.tags.add(self.tag) + self.grant_access( + obj=self.test_document, permission=permission_tag_remove + ) + response = self._request_document_tag_multiple_remove_view() + self.assertNotContains( + response=response, text=self.test_tag, status_code=200 + ) + self.assertContains( + response=response, text=self.test_document, status_code=200 + ) - self.grant_access(obj=self.document, permission=permission_tag_remove) - self.grant_access(obj=self.tag, permission=permission_tag_remove) - self.grant_access(obj=self.tag, permission=permission_tag_view) + self.assertEqual(self.test_document.tags.count(), 1) - response = self._request_multiple_documents_selection_tag_remove_view() + def test_document_tag_multiple_remove_view_with_tag_access(self): + self._create_test_tag() + + self.test_document.tags.add(self.test_tag) + + self.grant_access(obj=self.test_tag, permission=permission_tag_remove) + + response = self._request_document_tag_multiple_remove_view() + self.assertEqual(response.status_code, 404) + + self.assertEqual(self.test_document.tags.count(), 1) + + def test_document_tag_multiple_remove_view_with_full_access(self): + self._create_test_tag() + + self.test_document.tags.add(self.test_tag) + + self.grant_access( + obj=self.test_document, permission=permission_tag_remove + ) + self.grant_access(obj=self.test_tag, permission=permission_tag_remove) + + response = self._request_document_tag_multiple_remove_view() self.assertEqual(response.status_code, 302) - self.assertEqual(self.document.tags.count(), 0) + self.assertEqual(self.test_document.tags.count(), 0) + + def test_document_tags_list_no_permissions(self): + self._create_test_tag() + + self.test_tag.documents.add(self.test_document) + + response = self._request_document_tag_list_view() + self.assertNotContains( + response=response, text=force_text(self.test_tag), status_code=404 + ) + + def test_document_tags_list_with_document_access(self): + self._create_test_tag() + + self.test_tag.documents.add(self.test_document) + + self.grant_access( + obj=self.test_document, permission=permission_tag_view + ) + + response = self._request_document_tag_list_view() + self.assertNotContains( + response=response, text=force_text(self.test_tag), status_code=200 + ) + + def test_document_tags_list_with_tag_access(self): + self._create_test_tag() + + self.test_tag.documents.add(self.test_document) + + self.grant_access(obj=self.test_tag, permission=permission_tag_view) + + response = self._request_document_tag_list_view() + self.assertNotContains( + response=response, text=force_text(self.test_tag), status_code=404 + ) + + def test_document_tags_list_with_full_access(self): + self._create_test_tag() + + self.test_tag.documents.add(self.test_document) + + self.grant_access(obj=self.test_tag, permission=permission_tag_view) + self.grant_access( + obj=self.test_document, permission=permission_tag_view + ) + + response = self._request_document_tag_list_view() + self.assertContains( + response=response, text=force_text(self.test_tag), status_code=200 + ) + + def test_document_multiple_tag_remove_view_no_permissions(self): + self._create_test_tag() + + self.test_document.tags.add(self.test_tag) + + response = self._request_document_multiple_tag_multiple_remove_view() + self.assertEqual(response.status_code, 404) + + self.assertQuerysetEqual( + self.test_document.tags.all(), (repr(self.test_tag),) + ) + + def test_document_multiple_tag_remove_view_with_full_access(self): + self._create_test_tag() + + self.test_document.tags.add(self.test_tag) + + self.grant_access( + obj=self.test_document, permission=permission_tag_remove + ) + self.grant_access(obj=self.test_tag, permission=permission_tag_remove) + + response = self._request_document_multiple_tag_multiple_remove_view() + self.assertEqual(response.status_code, 302) + + self.assertEqual(self.test_document.tags.count(), 0) diff --git a/mayan/apps/tags/tests/test_wizard_steps.py b/mayan/apps/tags/tests/test_wizard_steps.py index edc1732ce5..9acfd0971b 100644 --- a/mayan/apps/tags/tests/test_wizard_steps.py +++ b/mayan/apps/tags/tests/test_wizard_steps.py @@ -13,12 +13,12 @@ from mayan.apps.sources.tests.literals import ( from ..models import Tag from .literals import TEST_TAG_COLOR, TEST_TAG_LABEL +from .mixins import TagTestMixin -class TaggedDocumentUploadTestCase(GenericDocumentViewTestCase): +class TaggedDocumentUploadTestCase(TagTestMixin, GenericDocumentViewTestCase): def setUp(self): super(TaggedDocumentUploadTestCase, self).setUp() - self.login_user() self.source = WebFormSource.objects.create( enabled=True, label=TEST_SOURCE_LABEL, uncompress=TEST_SOURCE_UNCOMPRESS_N @@ -34,20 +34,15 @@ class TaggedDocumentUploadTestCase(GenericDocumentViewTestCase): data={ 'document_type_id': self.document_type.pk, 'source-file': file_object, - 'tags': self.tag.pk + 'tags': self.test_tag.pk } ) - def _create_tag(self): - self.tag = Tag.objects.create( - color=TEST_TAG_COLOR, label=TEST_TAG_LABEL - ) - def test_upload_interactive_view_with_access(self): - self._create_tag() + self._create_test_tag() self.grant_access( permission=permission_document_create, obj=self.document_type ) response = self._request_upload_interactive_document_create_view() self.assertEqual(response.status_code, 302) - self.assertTrue(self.tag in Document.objects.first().tags.all()) + self.assertTrue(self.test_tag in Document.objects.first().tags.all()) diff --git a/mayan/apps/tags/urls.py b/mayan/apps/tags/urls.py index 67857d8a46..66ac0b2659 100644 --- a/mayan/apps/tags/urls.py +++ b/mayan/apps/tags/urls.py @@ -2,66 +2,75 @@ from __future__ import unicode_literals from django.conf.urls import url -from .api_views import ( - APIDocumentTagView, APIDocumentTagListView, APITagDocumentListView, - APITagListView, APITagView -) +#from .api_views import ( +# APIDocumentTagView, APIDocumentTagListView, APITagDocumentListView, +# APITagListView, APITagView +#) +from .api_views import DocumentTagViewSet, TagViewSet + from .views import ( DocumentTagListView, TagAttachActionView, TagCreateView, - TagDeleteActionView, TagEditView, TagListView, TagRemoveActionView, - TagTaggedItemListView + TagDeleteActionView, TagDocumentListView, TagEditView, TagListView, + TagRemoveActionView ) urlpatterns = [ - url(regex=r'^tags/list/$', name='tag_list', view=TagListView.as_view()), + url( + regex=r'^documents/(?P\d+)/tags/$', + name='document_tag_list', view=DocumentTagListView.as_view() + ), + url( + regex=r'^documents/(?P\d+)/tags/multiple/attach/$', + name='document_tag_multiple_attach', view=TagAttachActionView.as_view() + ), + url( + regex=r'^documents/(?P\d+)/tags/multiple/remove/$', + name='document_tag_multiple_remove', + view=TagRemoveActionView.as_view() + ), + url( + regex=r'^documents/multiple/attach/$', + name='document_multiple_tag_multiple_attach', + view=TagAttachActionView.as_view() + ), + url( + regex=r'^documents/multiple/tags/remove/$', + name='document_multiple_tag_multiple_remove', + view=TagRemoveActionView.as_view() + ), + url(regex=r'^tags/$', name='tag_list', view=TagListView.as_view()), url( regex=r'^tags/create/$', name='tag_create', view=TagCreateView.as_view() ), url( - regex=r'^tags/(?P\d+)/delete/$', name='tag_delete', + regex=r'^tags/(?P\d+)/delete/$', name='tag_delete', view=TagDeleteActionView.as_view() ), url( - regex=r'^tags/(?P\d+)/edit/$', name='tag_edit', + regex=r'^tags/(?P\d+)/edit/$', name='tag_edit', view=TagEditView.as_view() ), url( - regex=r'^tags/(?P\d+)/documents/$', - name='tag_tagged_item_list', view=TagTaggedItemListView.as_view() + regex=r'^tags/(?P\d+)/documents/$', + name='tag_document_list', view=TagDocumentListView.as_view() ), url( regex=r'^tags/multiple/delete/$', name='tag_multiple_delete', view=TagDeleteActionView.as_view() - ), - url( - regex=r'^tags/multiple/remove/document/(?P\d+)/$', - name='single_document_multiple_tag_remove', - view=TagRemoveActionView.as_view() - ), - url( - regex=r'^tags/multiple/remove/document/multiple/$', - name='multiple_documents_selection_tag_remove', - view=TagRemoveActionView.as_view() - ), - - url( - regex=r'^documents/(?P\d+)/attach/$', - name='tag_attach', view=TagAttachActionView.as_view() - ), - url( - regex=r'^documents/multiple/attach//$', - name='multiple_documents_tag_attach', - view=TagAttachActionView.as_view() - ), - - url( - regex=r'^documents/(?P\d+)/tags/$', name='document_tags', - view=DocumentTagListView.as_view(), - ), + ) ] -api_urls = [ + +api_router_entries = ( + {'prefix': r'tags', 'viewset': TagViewSet, 'basename': 'tag'}, + { + 'prefix': r'documents/(?P\d+)/tags', + 'viewset': DocumentTagViewSet, 'basename': 'document_tag' + }, +) + +""" url( regex=r'^tags/(?P\d+)/documents/$', name='tag-document-list', view=APITagDocumentListView.as_view(), @@ -79,4 +88,4 @@ api_urls = [ regex=r'^documents/(?P\d+)/tags/(?P[0-9]+)/$', name='document-tag-detail', view=APIDocumentTagView.as_view() ), -] +""" diff --git a/mayan/apps/tags/views.py b/mayan/apps/tags/views.py index 1c0e8050f4..d36149333c 100644 --- a/mayan/apps/tags/views.py +++ b/mayan/apps/tags/views.py @@ -9,19 +9,20 @@ from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _, ungettext from mayan.apps.acls.models import AccessControlList -from mayan.apps.common.views import ( +from mayan.apps.common.generics import ( MultipleObjectFormActionView, MultipleObjectConfirmActionView, SingleObjectCreateView, SingleObjectEditView, SingleObjectListView ) +from mayan.apps.common.mixins import ExternalObjectMixin from mayan.apps.documents.models import Document from mayan.apps.documents.views import DocumentListView from mayan.apps.documents.permissions import permission_document_view from .forms import TagMultipleSelectionForm from .icons import ( - icon_menu_tags, icon_tag_delete_submit, icon_tag_remove_submit + icon_menu_tags, icon_tag_delete_submit, icon_document_tag_multiple_remove_submit ) -from .links import link_tag_attach, link_tag_create +from .links import link_document_tag_multiple_attach, link_tag_create from .models import Tag from .permissions import ( permission_tag_attach, permission_tag_create, permission_tag_delete, @@ -34,7 +35,7 @@ logger = logging.getLogger(__name__) class TagAttachActionView(MultipleObjectFormActionView): form_class = TagMultipleSelectionForm model = Document - pk_url_kwarg = 'document_pk' + pk_url_kwarg = 'document_id' object_permission = permission_tag_attach success_message = _('Tag attach request performed on %(count)d document') success_message_plural = _( @@ -42,7 +43,7 @@ class TagAttachActionView(MultipleObjectFormActionView): ) def get_extra_context(self): - queryset = self.get_queryset() + queryset = self.get_object_list() result = { 'submit_label': _('Attach'), @@ -66,7 +67,7 @@ class TagAttachActionView(MultipleObjectFormActionView): return result def get_form_extra_kwargs(self): - queryset = self.get_queryset() + queryset = self.get_object_list() result = { 'help_text': _('Tags to be attached.'), 'permission': permission_tag_attach, @@ -85,44 +86,45 @@ class TagAttachActionView(MultipleObjectFormActionView): return result def get_post_action_redirect(self): - queryset = self.get_queryset() + queryset = self.get_object_list() if queryset.count() == 1: return reverse( - viewname='tags:document_tags', kwargs={ - 'document_pk': queryset.first().pk + viewname='tags:document_tag_list', kwargs={ + 'document_id': queryset.first().pk } ) else: return super(TagAttachActionView, self).get_post_action_redirect() def object_action(self, form, instance): - attached_tags = instance.get_tags() + attached_tags = instance.get_tags( + permission=permission_tag_attach, user=self.request.user + ) - for tag in form.cleaned_data['tags']: - AccessControlList.objects.check_access( - obj=tag, permissions=permission_tag_attach, - user=self.request.user - ) + tag_list = AccessControlList.objects.restrict_queryset( + permission=permission_tag_attach, queryset=form.cleaned_data['tags'], + user=self.request.user + ) + for tag in tag_list: if tag in attached_tags: messages.warning( - self.request, _( + message=_( 'Document "%(document)s" is already tagged as ' '"%(tag)s"' ) % { 'document': instance, 'tag': tag - } + }, request=self.request ) else: tag.attach_to(document=instance, user=self.request.user) messages.success( - self.request, - _( + message=_( 'Tag "%(tag)s" attached successfully to document ' '"%(document)s".' ) % { 'document': instance, 'tag': tag - } + }, request=self.request ) @@ -130,7 +132,7 @@ class TagCreateView(SingleObjectCreateView): extra_context = {'title': _('Create tag')} fields = ('label', 'color') model = Tag - post_action_redirect = reverse_lazy('tags:tag_list') + post_action_redirect = reverse_lazy(viewname='tags:tag_list') view_permission = permission_tag_create def get_save_extra_data(self): @@ -139,8 +141,8 @@ class TagCreateView(SingleObjectCreateView): class TagDeleteActionView(MultipleObjectConfirmActionView): model = Tag - pk_url_kwarg = 'tag_pk' - post_action_redirect = reverse_lazy('tags:tag_list') + pk_url_kwarg = 'tag_id' + post_action_redirect = reverse_lazy(viewname='tags:tag_list') object_permission = permission_tag_delete success_message = _('Tag delete request performed on %(count)d tag') success_message_plural = _( @@ -155,9 +157,9 @@ class TagDeleteActionView(MultipleObjectConfirmActionView): 'submit_icon_class': icon_tag_delete_submit, 'submit_label': _('Delete'), 'title': ungettext( - 'Delete the selected tag?', - 'Delete the selected tags?', - queryset.count() + singular='Delete the selected tag?', + plural='Delete the selected tags?', + number=queryset.count() ) } @@ -175,13 +177,14 @@ class TagDeleteActionView(MultipleObjectConfirmActionView): try: instance.delete() messages.success( - self.request, _('Tag "%s" deleted successfully.') % instance + message=_('Tag "%s" deleted successfully.') % instance, + request=self.request ) except Exception as exception: messages.error( - self.request, _('Error deleting tag "%(tag)s": %(error)s') % { + message=_('Error deleting tag "%(tag)s": %(error)s') % { 'tag': instance, 'error': exception - } + }, request=self.request ) @@ -189,9 +192,8 @@ class TagEditView(SingleObjectEditView): fields = ('label', 'color') model = Tag object_permission = permission_tag_edit - object_permission_raise_404 = True - pk_url_kwarg = 'tag_pk' - post_action_redirect = reverse_lazy('tags:tag_list') + pk_url_kwarg = 'tag_id' + post_action_redirect = reverse_lazy(viewname='tags:tag_list') def get_extra_context(self): return { @@ -222,22 +224,24 @@ class TagListView(SingleObjectListView): 'title': _('Tags'), } - def get_object_list(self): - return self.get_tag_queryset() - - def get_tag_queryset(self): + def get_source_queryset(self): + #return self.get_tag_queryset() return Tag.objects.all() + #def get_tag_queryset(self): + # return Tag.objects.all() -class TagTaggedItemListView(DocumentListView): - def get_tag(self): - return get_object_or_404(klass=Tag, pk=self.kwargs['tag_pk']) + +class TagDocumentListView(ExternalObjectMixin, DocumentListView): + external_object_class = Tag + external_object_permission = permission_tag_view + external_object_pk_url_kwarg = 'tag_id' def get_document_queryset(self): - return self.get_tag().documents.all() + return self.get_tag().get_documents(user=self.request.user).all() def get_extra_context(self): - context = super(TagTaggedItemListView, self).get_extra_context() + context = super(TagDocumentListView, self).get_extra_context() context.update( { 'column_class': 'col-xs-12 col-sm-6 col-md-4 col-lg-3', @@ -247,57 +251,56 @@ class TagTaggedItemListView(DocumentListView): ) return context + def get_tag(self): + return self.get_external_object() -class DocumentTagListView(TagListView): - def dispatch(self, request, *args, **kwargs): - self.document = get_object_or_404( - klass=Document, pk=self.kwargs['document_pk'] - ) - AccessControlList.objects.check_access( - permissions=permission_document_view, user=request.user, - obj=self.document - ) - - return super(DocumentTagListView, self).dispatch( - request, *args, **kwargs - ) +class DocumentTagListView(ExternalObjectMixin, TagListView): + external_object_class = Document + external_object_permission = permission_tag_view + external_object_pk_url_kwarg = 'document_id' def get_extra_context(self): context = super(DocumentTagListView, self).get_extra_context() context.update( { 'hide_link': True, - 'no_results_main_link': link_tag_attach.resolve( + 'no_results_main_link': link_document_tag_multiple_attach.resolve( context=RequestContext( - self.request, {'object': self.document} + self.request, {'object': self.get_external_object()} ) ), 'no_results_title': _('Document has no tags attached'), - 'object': self.document, - 'title': _('Tags for document: %s') % self.document, + 'object': self.get_external_object(), + 'title': _( + 'Tags for document: %s' + ) % self.get_external_object(), } ) return context - def get_tag_queryset(self): - return self.document.get_tags().all() + #def get_tag_queryset(self): + def get_source_queryset(self): + return self.get_external_object().get_tags( + permission=permission_tag_view, user=self.request.user + ).all() class TagRemoveActionView(MultipleObjectFormActionView): form_class = TagMultipleSelectionForm model = Document object_permission = permission_tag_remove + pk_url_kwarg = 'document_id' success_message = _('Tag remove request performed on %(count)d document') success_message_plural = _( 'Tag remove request performed on %(count)d documents' ) def get_extra_context(self): - queryset = self.get_queryset() + queryset = self.get_object_list() result = { - 'submit_icon_class': icon_tag_remove_submit, + 'submit_icon_class': icon_document_tag_multiple_remove_submit, 'submit_label': _('Remove'), 'title': ungettext( singular='Remove tags to %(count)d document', @@ -321,7 +324,7 @@ class TagRemoveActionView(MultipleObjectFormActionView): return result def get_form_extra_kwargs(self): - queryset = self.get_queryset() + queryset = self.get_object_list() result = { 'help_text': _('Tags to be removed.'), 'permission': permission_tag_remove, @@ -338,41 +341,43 @@ class TagRemoveActionView(MultipleObjectFormActionView): return result def get_post_action_redirect(self): - queryset = self.get_queryset() + queryset = self.get_object_list() if queryset.count() == 1: return reverse( - viewname='tags:document_tags', kwargs={ - 'document_pk': queryset.first().pk + viewname='tags:document_tag_list', kwargs={ + 'document_id': queryset.first().pk } ) else: return super(TagRemoveActionView, self).get_post_action_redirect() def object_action(self, form, instance): - attached_tags = instance.get_tags() + attached_tags = instance.get_tags( + permission=permission_tag_remove, user=self.request.user + ) - for tag in form.cleaned_data['tags']: - AccessControlList.objects.check_access( - obj=tag, permissions=permission_tag_remove, - user=self.request.user - ) + tag_list = AccessControlList.objects.restrict_queryset( + permission=permission_tag_remove, + queryset=form.cleaned_data['tags'], + user=self.request.user + ) + for tag in tag_list: if tag not in attached_tags: messages.warning( - self.request, _( + message=_( 'Document "%(document)s" wasn\'t tagged as "%(tag)s' ) % { 'document': instance, 'tag': tag - } + }, request=self.request ) else: tag.remove_from(document=instance, user=self.request.user) messages.success( - self.request, - _( + message=_( 'Tag "%(tag)s" removed successfully from document ' '"%(document)s".' ) % { 'document': instance, 'tag': tag - } + }, request=self.request ) diff --git a/mayan/apps/tags/widgets.py b/mayan/apps/tags/widgets.py index 4dc95af64e..2157429dd1 100644 --- a/mayan/apps/tags/widgets.py +++ b/mayan/apps/tags/widgets.py @@ -18,39 +18,11 @@ class TagFormWidget(forms.SelectMultiple): def create_option(self, name, value, label, selected, index, subindex=None, attrs=None): result = super(TagFormWidget, self).create_option( - name=name, value=value, label='{}'.format(conditional_escape(label)), - selected=selected, index=index, subindex=subindex, attrs=attrs + attrs=attrs, index=index, + label='{}'.format(conditional_escape(label)), name=name, + selected=selected, subindex=subindex, value=value ) result['attrs']['data-color'] = self.queryset.get(pk=value).color return result - - -def widget_document_tags(document, user): - """ - A tag widget that displays the tags for the given document - """ - AccessControlList = apps.get_model( - app_label='acls', model_name='AccessControlList' - ) - - result = ['
'] - - tags = AccessControlList.objects.filter_by_access( - permission_tag_view, user, queryset=document.get_tags().all() - ) - - for tag in tags: - result.append(widget_single_tag(tag)) - - result.append('
') - - if tags: - return mark_safe(''.join(result)) - else: - return '' - - -def widget_single_tag(tag): - return render_to_string('tags/tag_widget.html', {'tag': tag}) diff --git a/mayan/apps/tags/wizard_steps.py b/mayan/apps/tags/wizard_steps.py index 84dd5c6055..5abac4bb90 100644 --- a/mayan/apps/tags/wizard_steps.py +++ b/mayan/apps/tags/wizard_steps.py @@ -22,17 +22,10 @@ class WizardStepTags(WizardStep): Tag = apps.get_model(app_label='tags', model_name='Tag') return Tag.objects.exists() - @classmethod - def get_form_kwargs(self, wizard): - return { - 'help_text': _('Tags to be attached.'), - 'user': wizard.request.user - } - @classmethod def done(cls, wizard): result = {} - cleaned_data = wizard.get_cleaned_data_for_step(cls.name) + cleaned_data = wizard.get_cleaned_data_for_step(step=cls.name) if cleaned_data: result['tags'] = [ force_text(tag.pk) for tag in cleaned_data['tags'] @@ -40,13 +33,20 @@ class WizardStepTags(WizardStep): return result + @classmethod + def get_form_kwargs(self, wizard): + return { + 'help_text': _('Tags to be attached.'), + 'user': wizard.request.user + } + @classmethod def step_post_upload_process(cls, document, querystring=None): - furl_instance = furl(querystring) + furl_instance = furl(args=querystring) Tag = apps.get_model(app_label='tags', model_name='Tag') for tag in Tag.objects.filter(pk__in=furl_instance.args.getlist('tags')): tag.documents.add(document) -WizardStep.register(WizardStepTags) +WizardStep.register(step=WizardStepTags) diff --git a/mayan/apps/tags/workflow_actions.py b/mayan/apps/tags/workflow_actions.py index b04aeef0c5..12018f98cd 100644 --- a/mayan/apps/tags/workflow_actions.py +++ b/mayan/apps/tags/workflow_actions.py @@ -38,8 +38,9 @@ class AttachTagAction(WorkflowAction): user = request.user logger.debug('user: %s', user) - queryset = AccessControlList.objects.filter_by_access( - self.permission, user, queryset=Tag.objects.all() + queryset = AccessControlList.objects.restrict_queryset( + permission=self.permission, queryset=Tag.objects.all(), + user=user ) self.fields['tags']['kwargs']['queryset'] = queryset