From 7b42c8e9ae4797454ed90a1ffea2e170be5ccc31 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Fri, 7 Aug 2015 01:07:41 -0400 Subject: [PATCH] Initial commit for tag API refactor. --- mayan/apps/tags/api_views.py | 144 +++++++++++++++++++++------------ mayan/apps/tags/serializers.py | 61 ++++++++++++-- mayan/apps/tags/test_api.py | 126 +++++++++++++++++++++++++++++ 3 files changed, 274 insertions(+), 57 deletions(-) create mode 100644 mayan/apps/tags/test_api.py diff --git a/mayan/apps/tags/api_views.py b/mayan/apps/tags/api_views.py index fd5ffd2fd0..073d8b96db 100644 --- a/mayan/apps/tags/api_views.py +++ b/mayan/apps/tags/api_views.py @@ -9,54 +9,81 @@ from rest_framework.response import Response from acls.models import AccessControlList from documents.models import Document from documents.permissions import permission_document_view +from documents.serializers import DocumentSerializer from permissions import Permission from rest_api.filters import MayanObjectPermissionsFilter from rest_api.permissions import MayanPermission from .models import Tag from .permissions import ( - permission_tag_attach, permission_tag_remove, permission_tag_view + permission_tag_attach, permission_tag_create, permission_tag_delete, + permission_tag_edit, permission_tag_remove, permission_tag_view +) +from .serializers import ( + DocumentTagSerializer, NewDocumentTagSerializer, TagSerializer ) -from .serializers import TagSerializer class APITagView(generics.RetrieveUpdateDestroyAPIView): - serializer_class = TagSerializer + filter_backends = (MayanObjectPermissionsFilter,) + mayan_object_permissions = { + 'DELETE': (permission_tag_delete,), + 'GET': (permission_tag_view,), + 'PATCH': (permission_tag_edit,), + 'PUT': (permission_tag_edit,) + } queryset = Tag.objects.all() - - permission_classes = (MayanPermission,) - mayan_object_permissions = {'GET': [permission_tag_view]} + serializer_class = TagSerializer def delete(self, *args, **kwargs): - """Delete the selected tag.""" + """ + Delete the selected tag. + """ + return super(APITagView, self).delete(*args, **kwargs) def get(self, *args, **kwargs): - """Return the details of the selected tag.""" + """ + Return the details of the selected tag. + """ + return super(APITagView, self).get(*args, **kwargs) def patch(self, *args, **kwargs): - """Edit the selected tag.""" + """ + Edit the selected tag. + """ + return super(APITagView, self).patch(*args, **kwargs) def put(self, *args, **kwargs): - """Edit the selected tag.""" + """ + Edit the selected tag. + """ + return super(APITagView, self).put(*args, **kwargs) class APITagListView(generics.ListCreateAPIView): - serializer_class = TagSerializer - queryset = Tag.objects.all() - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': [permission_tag_view]} + mayan_object_permissions = {'GET': (permission_tag_view,)} + mayan_view_permissions = {'POST': (permission_tag_create,)} + permission_classes = (MayanPermission,) + queryset = Tag.objects.all() + serializer_class = TagSerializer def get(self, *args, **kwargs): - """Returns a list of all the tags.""" + """ + Returns a list of all the tags. + """ + return super(APITagListView, self).get(*args, **kwargs) def post(self, *args, **kwargs): - """Create a new tag.""" + """ + Create a new tag. + """ + return super(APITagListView, self).post(*args, **kwargs) @@ -66,61 +93,91 @@ class APITagDocumentListView(generics.ListAPIView): """ filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': [permission_document_view]} - - def get_serializer_class(self): - from documents.serializers import DocumentSerializer - return DocumentSerializer + mayan_object_permissions = {'GET': (permission_document_view,)} + serializer_class = DocumentSerializer def get_queryset(self): tag = get_object_or_404(Tag, pk=self.kwargs['pk']) try: Permission.check_permissions( - self.request.user, [permission_tag_view] + self.request.user, (permission_tag_view,) ) except PermissionDenied: AccessControlList.objects.check_access( permission_tag_view, self.request.user, tag ) - queryset = tag.documents.all() - return queryset + return tag.documents.all() - -class APIDocumentTagListView(generics.ListAPIView): +### +class APIDocumentTagListView(generics.ListCreateAPIView): """ Returns a list of all the tags attached to a document. """ - serializer_class = TagSerializer - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': [permission_tag_view]} + mayan_object_permissions = {'GET': (permission_tag_view,)} + + def get_document(self): + return get_object_or_404(Document, pk=self.kwargs['pk']) def get_queryset(self): - document = get_object_or_404(Document, pk=self.kwargs['pk']) + document = self.get_document() try: Permission.check_permissions( - self.request.user, [permission_document_view] + self.request.user, (permission_document_view,) ) except PermissionDenied: AccessControlList.objects.check_access( permission_document_view, self.request.user, document ) - queryset = document.tags.all() - return queryset + return document.tags.all() + + def get_serializer_class(self): + if self.request.method == 'GET': + return DocumentTagSerializer + elif self.request.method == 'POST': + return NewDocumentTagSerializer + + def perform_create(self, serializer): + serializer.save(document=self.get_document()) + + def post(self, request, *args, **kwargs): + """ + Attach a tag to a document. + """ + + return super(APIDocumentTagListView, self).post(request, *args, **kwargs) -class APIDocumentTagView(views.APIView): +class APIDocumentTagView(generics.RetrieveDestroyAPIView): + serialize_class = DocumentTagSerializer + + def get_folder(self): + return get_object_or_404(Document, pk=self.kwargs['document_pk']) + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'document': self.get_document(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + ''' def delete(self, request, *args, **kwargs): """ Remove a tag from a document. """ document = get_object_or_404(Document, pk=self.kwargs['document_pk']) + try: - Permission.check_permissions(request.user, [permission_tag_remove]) + Permission.check_permissions(request.user, (permission_tag_remove,)) except PermissionDenied: AccessControlList.objects.check_access( permission_tag_remove, request.user, document @@ -130,19 +187,4 @@ class APIDocumentTagView(views.APIView): tag.documents.remove(document) return Response(status=status.HTTP_204_NO_CONTENT) - def post(self, request, *args, **kwargs): - """ - Attach a tag to a document. - """ - - document = get_object_or_404(Document, pk=self.kwargs['document_pk']) - try: - Permission.check_permissions(request.user, [permission_tag_attach]) - except PermissionDenied: - AccessControlList.objects.check_access( - permission_tag_attach, request.user, document - ) - - tag = get_object_or_404(Tag, pk=self.kwargs['pk']) - tag.documents.add(document) - return Response(status=status.HTTP_201_CREATED) + ''' diff --git a/mayan/apps/tags/serializers.py b/mayan/apps/tags/serializers.py index 1f992c962c..1e978779c4 100644 --- a/mayan/apps/tags/serializers.py +++ b/mayan/apps/tags/serializers.py @@ -1,16 +1,65 @@ -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals + +from django.core.exceptions import PermissionDenied +from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from rest_framework.exceptions import ValidationError +from rest_framework.reverse import reverse + +from acls.models import AccessControlList +from permissions import Permission from .models import Tag +from .permissions import permission_tag_attach -class TagSerializer(serializers.ModelSerializer): - documents = serializers.SerializerMethodField('get_documents_count') +class TagSerializer(serializers.HyperlinkedModelSerializer): + documents = serializers.HyperlinkedIdentityField(view_name='rest_api:tag-document-list') + documents_count = serializers.SerializerMethodField() class Meta: - fields = ('id', 'label', 'color', 'documents') + extra_kwargs = { + 'url': {'view_name': 'rest_api:tag-detail'}, + } + fields = ('color', 'documents', 'documents_count', 'id', 'label', 'url') model = Tag - def get_documents_count(self, obj): - return obj.documents.count() + def get_documents_count(self, instance): + return instance.documents.count() + + +class NewDocumentTagSerializer(serializers.Serializer): + tag = serializers.IntegerField(help_text=_('Primary key of the tag to be added.')) + + def create(self, validated_data): + try: + tag = Tag.objects.get(pk=validated_data['tag']) + + try: + Permission.check_permissions(self.context['request'].user, (permission_tag_attach,)) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_tag_attach, self.context['request'], document + ) + + tag.documents.add(validated_data['document']) + except Exception as exception: + raise ValidationError(exception) + + return {'tag': tag.pk} + + +class DocumentTagSerializer(TagSerializer): + remove = serializers.SerializerMethodField() + + def get_remove(self, instance): + return reverse( + 'rest_api:document-tag', args=( + self.context['document'].pk, instance.pk + ), request=self.context['request'], format=self.context['format'] + ) + + class Meta(TagSerializer.Meta): + fields = TagSerializer.Meta.fields + ('remove',) + read_only_fields = TagSerializer.Meta.fields diff --git a/mayan/apps/tags/test_api.py b/mayan/apps/tags/test_api.py new file mode 100644 index 0000000000..2cec8fd2ad --- /dev/null +++ b/mayan/apps/tags/test_api.py @@ -0,0 +1,126 @@ +from __future__ import unicode_literals + +from json import loads + +from django.contrib.auth.models import User +from django.core.files import File +from django.core.urlresolvers import reverse + +from rest_framework import status +from rest_framework.test import APITestCase + +from documents.models import DocumentType +from documents.test_models import ( + TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, + TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE, + TEST_SMALL_DOCUMENT_FILENAME, TEST_SMALL_DOCUMENT_PATH, +) + +from .models import Tag + +TEST_TAG_LABEL = 'test tag' +TEST_TAG_LABEL_ALTERNATE = 'test tag alternate' +TEST_TAG_COLOR = '#001122' +TEST_TAG_COLOR_ALTERNATE = '#221100' + + +class TagAPITestCase(APITestCase): + """ + Test the tag API endpoints + """ + + def setUp(self): + self.admin_user = User.objects.create_superuser( + username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, + password=TEST_ADMIN_PASSWORD + ) + + self.client.force_authenticate(user=self.admin_user) + + def tearDown(self): + self.admin_user.delete() + + def test_tag_create(self): + self.client.post( + reverse('rest_api:tag-list'), { + 'label': TEST_TAG_LABEL, 'color': TEST_TAG_COLOR + } + ) + + tag = Tag.objects.first() + + self.assertEqual(Tag.objects.count(), 1) + self.assertEqual(tag.label, TEST_TAG_LABEL) + self.assertEqual(tag.color, TEST_TAG_COLOR) + + def test_tag_delete(self): + tag = Tag.objects.create(color=TEST_TAG_COLOR, label=TEST_TAG_LABEL) + + self.client.delete(reverse('rest_api:tag-detail', args=(tag.pk,))) + + self.assertEqual(Tag.objects.count(), 0) + + def test_tag_edit(self): + tag = Tag.objects.create(color=TEST_TAG_COLOR, label=TEST_TAG_LABEL) + + self.client.put( + reverse('rest_api:tag-detail', args=(tag.pk,)), + { + 'label': TEST_TAG_LABEL_ALTERNATE, + 'color': TEST_TAG_COLOR_ALTERNATE + } + ) + + tag = Tag.objects.first() + + self.assertEqual(tag.label, TEST_TAG_LABEL_ALTERNATE) + self.assertEqual(tag.color, TEST_TAG_COLOR_ALTERNATE) + + def test_tag_add_document(self): + tag = Tag.objects.create(color=TEST_TAG_COLOR, label=TEST_TAG_LABEL) + + document_type = DocumentType.objects.create( + label=TEST_DOCUMENT_TYPE + ) + + ocr_settings = document_type.ocr_settings + ocr_settings.auto_ocr = False + ocr_settings.save() + + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + document = document_type.new_document( + file_object=File(file_object), + ) + + self.client.post( + reverse('rest_api:document-tag-list', args=(document.pk,)), + {'tag': tag.pk} + ) + + self.assertEqual(tag.documents.count(), 1) + + """ + def test_tag_remove_document(self): + tag = Tag.objects.create(color=TEST_TAG_COLOR, label=TEST_TAG_LABEL) + + document_type = DocumentType.objects.create( + label=TEST_DOCUMENT_TYPE + ) + + ocr_settings = document_type.ocr_settings + ocr_settings.auto_ocr = False + ocr_settings.save() + + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + document = document_type.new_document( + file_object=File(file_object), + ) + + tag.documents.add(document) + + self.client.delete( + reverse('rest_api:tag-document', args=(tag.pk, document.pk)), + ) + + self.assertEqual(tag.documents.count(), 0) + """