Add document tags API views

These views allow accesing the tags list of a document as
well as attaching or removing tags in bulk.

The URLs for tag list, attach and remove are added to the
DocumentSerializer.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-02-15 04:20:41 -04:00
parent bb6a827f28
commit cae7b8f8c5
4 changed files with 150 additions and 378 deletions

View File

@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
from drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, serializers, status, routers, viewsets
from rest_framework.decorators import action, detail_route
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
@@ -12,7 +12,9 @@ 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.viewsets import MayanAPIModelViewSet
from mayan.apps.rest_api.viewsets import (
MayanAPIGenericViewSet, MayanAPIModelViewSet
)
from .models import Tag
from .permissions import (
@@ -20,14 +22,12 @@ from .permissions import (
permission_tag_edit, permission_tag_remove, permission_tag_view
)
from .serializers import (
DocumentTagAttachSerializer, DocumentTagSerializer,
TagDocumentAttachRemoveSerializer,
DocumentTagAttachRemoveSerializer, TagDocumentAttachRemoveSerializer,
TagSerializer,
)
class TagViewSet(MayanAPIModelViewSet):
class TagAPIViewSet(MayanAPIModelViewSet):
lookup_url_kwarg='tag_id'
object_permission_map = {
'destroy': permission_tag_delete,
@@ -53,7 +53,7 @@ class TagViewSet(MayanAPIModelViewSet):
instance = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.attach(instance=instance)
serializer.documents_attach(instance=instance)
headers = self.get_success_headers(data=serializer.data)
return Response(
serializer.data, status=status.HTTP_200_OK, headers=headers
@@ -86,284 +86,69 @@ class TagViewSet(MayanAPIModelViewSet):
instance = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.attach(instance=instance)
serializer.documents_remove(instance=instance)
headers = self.get_success_headers(data=serializer.data)
return Response(
serializer.data, status=status.HTTP_200_OK, headers=headers
)
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
class DocumentTagAPIViewSet(MayanAPIGenericViewSet):
lookup_url_kwarg='document_id'
object_permission_map = {
'tag_attach': permission_tag_attach,
'tag_list': permission_tag_view,
'tag_remove': permission_tag_remove,
}
serializer_class = DocumentTagSerializer
queryset = Document.objects.all()
@action(
#detail=True, lookup_field='pk', lookup_url_kwarg='document_id',
detail=True, lookup_url_kwarg='document_id',
methods=('post',), serializer_class=DocumentTagAttachSerializer,
url_name='tag-attach', url_path='attach'
detail=False, lookup_url_kwarg='document_id', methods=('post',),
serializer_class=DocumentTagAttachRemoveSerializer,
url_name='tag-attach', url_path='tags/attach'
)
def attach(self, request, *args, **kwargs):
return Response({})
'''
serializer = DocumentSerializer(
instance=self.get_object().documents.all(), many=True,
context={'request': request}
def tag_attach(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.tags_attach(instance=instance)
headers = self.get_success_headers(data=serializer.data)
return Response(
serializer.data, status=status.HTTP_200_OK, headers=headers
)
@action(
detail=False, lookup_url_kwarg='document_id',
serializer_class=TagSerializer, url_name='tag-list',
url_path='tags'
)
def tag_list(self, request, *args, **kwargs):
queryset = self.get_object().get_tags(
permission=permission_tag_view, user=self.request.user
)
page = self.paginate_queryset(queryset)
serializer = self.get_serializer(
queryset, many=True, context={'request': request}
)
if page is not None:
return self.get_paginated_response(serializer.data)
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.
"""
object_permission = {'GET': permission_tag_view}
queryset = Tag.objects.all()
serializer_class = TagSerializer
view_permission = {'POST': permission_tag_create}
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.
"""
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 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
object_permission = {'GET': permission_document_view}
serializer_class = TagDocumentSerializer
def get_queryset(self):
return self.get_tag().get_documents(user=self.request.user).all()
def get_tag(self):
return self.get_external_object()
##
'''
'''
class APITagView(RetrieveDestroyAPIView):
"""
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'
serializer_class = DocumentTagSerializer
def get_document(self):
return self.get_external_object()
def get_external_object_permission(self):
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().tags.all()
def get_serializer_context(self):
context = super(DocumentSourceMixin, self).get_serializer_context()
if self.kwargs:
context.update(
{
'document': self.get_document(),
}
)
return context
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.
"""
context = super(APIDocumentTagListView, self).get_serializer_context()
if self.kwargs:
context.update(
{
'document': self.get_document(),
}
)
return context
"""
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'
lookup_url_kwarg = 'tag_pk'
mayan_object_permission = {
'GET': permission_tag_view,
'DELETE': permission_tag_remove
}
#serializer_class = DocumentTagSerializer
#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_queryset(self):
if self.request.method == 'DELETE':
permission = permission_tag_remove
else:
permission = permission_tag_view
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.
"""
context = super(APIDocumentTagView, self).get_serializer_context()
if self.kwargs:
context.update(
{
'document': self.get_document(),
}
)
return context
"""
def perform_destroy(self, instance):
# try:
from mayan.apps.acls.models import AccessControlList
from rest_framework.generics import get_object_or_404
queryset = AccessControlList.objects.restrict_queryset(
permission=permission_tag_remove, queryset=Tag.objects.all(),
user=self.request.user
@action(
detail=False, lookup_field='pk', lookup_url_kwarg='document_id',
methods=('post',), serializer_class=DocumentTagAttachRemoveSerializer,
url_name='tag-remove', url_path='tags/remove'
)
def tag_remove(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.tags_remove(instance=instance)
headers = self.get_success_headers(data=serializer.data)
return Response(
serializer.data, status=status.HTTP_200_OK, headers=headers
)
instance = get_object_or_404(queryset=queryset, pk=instance.pk)
#instance.attach_to(
# document=self.context['document'],
# user=self.context['request'].user
#)
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)
'''

View File

@@ -9,7 +9,7 @@ from mayan.apps.acls.links import link_acl_list
from mayan.apps.acls.permissions import permission_acl_edit, permission_acl_view
from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_list_facet, menu_object, menu_main,
menu_multi_item, menu_sidebar
menu_multi_item, menu_secondary
)
from mayan.apps.common.classes import ModelAttribute, ModelField
from mayan.apps.documents.search import document_page_search, document_search
@@ -19,6 +19,8 @@ from mayan.apps.events.links import (
)
from mayan.apps.events.permissions import permission_events_view
from mayan.apps.navigation import SourceColumn
from mayan.apps.rest_api.fields import HyperlinkField
from mayan.apps.rest_api.serializers import LazyExtraFieldsSerializerMixin
from .events import (
event_tag_attach, event_tag_created, event_tag_edited, event_tag_remove
@@ -33,7 +35,10 @@ from .links import (
link_tag_list, link_tag_multiple_delete
)
from .menus import menu_tags
from .methods import method_get_tags
from .methods import (
method_document_tags_attach, method_document_get_tags,
method_document_tags_remove
)
from .permissions import (
permission_tag_attach, permission_tag_delete, permission_tag_edit,
permission_tag_remove, permission_tag_view
@@ -66,7 +71,9 @@ class TagsApp(MayanAppConfig):
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)
Document.add_to_class(name='get_tags', value=method_document_get_tags)
Document.add_to_class(name='tags_attach', value=method_document_tags_attach)
Document.add_to_class(name='tags_remove', value=method_document_tags_remove)
ModelEventType.register(
model=Tag, event_types=(
@@ -167,7 +174,7 @@ class TagsApp(MayanAppConfig):
link_tag_edit, link_tag_delete
), sources=(Tag,)
)
menu_sidebar.bind_links(
menu_secondary.bind_links(
links=(
link_document_tag_multiple_attach, link_document_tag_multiple_remove
), sources=(
@@ -194,3 +201,30 @@ class TagsApp(MayanAppConfig):
dispatch_uid='tags_handler_tag_pre_delete',
receiver=handler_tag_pre_delete, sender=Tag
)
LazyExtraFieldsSerializerMixin.add_field(
dotted_path='mayan.apps.documents.serializers.DocumentSerializer',
field_name='tag_attach_url',
field=HyperlinkField(
lookup_url_kwarg='document_id',
view_name='rest_api:document-tag-attach'
)
)
LazyExtraFieldsSerializerMixin.add_field(
dotted_path='mayan.apps.documents.serializers.DocumentSerializer',
field_name='tag_list_url',
field=HyperlinkField(
lookup_url_kwarg='document_id',
view_name='rest_api:document-tag-list'
)
)
LazyExtraFieldsSerializerMixin.add_field(
dotted_path='mayan.apps.documents.serializers.DocumentSerializer',
field_name='tag_remove_url',
field=HyperlinkField(
lookup_url_kwarg='document_id',
view_name='rest_api:document-tag-remove'
)
)

View File

@@ -16,16 +16,42 @@ from .models import Tag
from .permissions import permission_tag_attach
class DocumentTagAttachRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer):
tag_id_list = serializers.CharField(
help_text=_(
'Comma separated list of tag primary keys to be attached or '
'removed.'
), required=False, write_only=True
)
class Meta:
external_object_list_model = Tag
external_object_list_permission = permission_tag_attach
external_object_list_pk_list_field = 'tag_id_list'
def tags_attach(self, instance):
instance.tags_attach(
queryset=self.get_external_object_list(),
_user=self.context['request'].user
)
def tags_remove(self, instance):
instance.tags_remove(
queryset=self.get_external_object_list(),
_user=self.context['request'].user
)
class TagSerializer(serializers.HyperlinkedModelSerializer):
attach_url = serializers.HyperlinkedIdentityField(
document_attach_url = serializers.HyperlinkedIdentityField(
lookup_url_kwarg='tag_id', view_name='rest_api:tag-document-attach'
)
documents_url = serializers.HyperlinkedIdentityField(
document_list_url = serializers.HyperlinkedIdentityField(
lookup_url_kwarg='tag_id', view_name='rest_api:tag-document-list'
)
remove_url = serializers.HyperlinkedIdentityField(
document_remove_url = serializers.HyperlinkedIdentityField(
lookup_url_kwarg='tag_id', view_name='rest_api:tag-document-remove'
)
@@ -37,94 +63,14 @@ class TagSerializer(serializers.HyperlinkedModelSerializer):
},
}
fields = (
'attach_url', 'color', 'documents_url', 'label', 'id',
'remove_url', 'url'
'color', 'document_attach_url', 'document_list_url',
'document_remove_url','label', 'id', 'url'
)
model = Tag
class DocumentTagSerializer(TagSerializer):
#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_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
#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
)
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.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 TagDocumentAttachRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer):
document_id = serializers.CharField(
help_text=_(
'Primary key of document to which this tag will be attached or '
'removed.'
), required=False, write_only=True
)
documents_id_list = serializers.CharField(
document_id_list = serializers.CharField(
help_text=_(
'Comma separated list of document primary keys to which this '
'tag will be attached or removed.'
@@ -134,15 +80,16 @@ class TagDocumentAttachRemoveSerializer(ExternalObjectListSerializerMixin, seria
class Meta:
external_object_list_model = Document
external_object_list_permission = permission_tag_attach
external_object_list_pk_field = 'document_id'
external_object_list_pk_list_field = 'document_id_list'
def attach(self, instance):
queryset = self.get_external_object_list()
for document in queryset:
instance.attach_to(document=document, user=self.context['request'].user)
def document_attach(self, instance):
instance.documents_attach(
queryset=self.get_external_object_list(),
_user=self.context['request'].user
)
def remove(self, instance):
queryset = self.get_external_object_list()
for document in queryset:
instance.attach_from(document=document, user=self.context['request'].user)
def document_remove(self, instance):
instance.documents_remove(
queryset=self.get_external_object_list(),
_user=self.context['request'].user
)

View File

@@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.conf.urls import url
from .api_views import DocumentTagViewSet, TagViewSet
from .api_views import DocumentTagAPIViewSet, TagAPIViewSet
from .views import (
DocumentTagListView, TagAttachActionView, TagCreateView,
@@ -58,9 +58,15 @@ urlpatterns = [
]
api_router_entries = (
{'prefix': r'tags', 'viewset': TagViewSet, 'basename': 'tag'},
{'prefix': r'tags', 'viewset': TagAPIViewSet, 'basename': 'tag'},
{
'prefix': r'documents/(?P<document_id>\d+)/tags',
'viewset': DocumentTagViewSet, 'basename': 'document-tag'
},
'prefix': r'documents/(?P<document_id>\d+)',
'viewset': DocumentTagAPIViewSet, 'basename': 'document'
}
)
# {
# 'prefix': r'permission_namespaces/(?P<permission_namespace_name>[^/.]+)/permissions',
# 'viewset': PermissionViewSet, 'basename': 'permission'
# },