From 26b6a981808985d3e0ea988d314787f6d5819df3 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 6 Aug 2015 20:01:30 -0400 Subject: [PATCH] Refactor folder app API, add folder API tests. --- mayan/apps/folders/api_views.py | 195 ++++++++++++++++++------------ mayan/apps/folders/serializers.py | 57 ++++++++- mayan/apps/folders/test_api.py | 77 +++++++++++- mayan/apps/folders/urls.py | 2 +- 4 files changed, 246 insertions(+), 85 deletions(-) diff --git a/mayan/apps/folders/api_views.py b/mayan/apps/folders/api_views.py index 1f1b4f6a83..d8c644c5d5 100644 --- a/mayan/apps/folders/api_views.py +++ b/mayan/apps/folders/api_views.py @@ -7,8 +7,9 @@ from rest_framework import generics, status, views from rest_framework.response import Response from acls.models import AccessControlList -from documents.models import Document +from documents.models import Document, DocumentType 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 @@ -19,7 +20,35 @@ from .permissions import ( permission_folder_delete, permission_folder_edit, permission_folder_remove_document, permission_folder_view ) -from .serializers import FolderSerializer +from .serializers import ( + FolderDocumentSerializer, FolderSerializer, NewFolderDocumentSerializer, + NewFolderSerializer +) + + +class APIDocumentFolderListView(generics.ListAPIView): + """ + Returns a list of all the folders to which a document belongs. + """ + + serializer_class = FolderSerializer + + filter_backends = (MayanObjectPermissionsFilter,) + mayan_object_permissions = {'GET': (permission_folder_view,)} + + def get_queryset(self): + document = get_object_or_404(Document, pk=self.kwargs['pk']) + try: + Permission.check_permissions( + self.request.user, [permission_document_view] + ) + except PermissionDenied: + AccessControlList.objects.check_access( + permission_document_view, self.request.user, document + ) + + queryset = document.folders.all() + return queryset class APIFolderListView(generics.ListCreateAPIView): @@ -28,7 +57,12 @@ class APIFolderListView(generics.ListCreateAPIView): mayan_view_permissions = {'POST': (permission_folder_create,)} permission_classes = (MayanPermission,) queryset = Folder.objects.all() - serializer_class = FolderSerializer + + def get_serializer_class(self): + if self.request.method == 'GET': + return FolderSerializer + elif self.request.method == 'POST': + return NewFolderSerializer def get(self, *args, **kwargs): """ @@ -42,26 +76,6 @@ class APIFolderListView(generics.ListCreateAPIView): """ return super(APIFolderListView, self).post(*args, **kwargs) - ''' - def create(self, request, *args, **kwargs): - serializer = self.get_serializer( - data=request.DATA, files=request.FILES - ) - - if serializer.is_valid(): - serializer.object.user = request.user - self.pre_save(serializer.object) - self.object = serializer.save(force_insert=True) - self.post_save(self.object, created=True) - headers = self.get_success_headers(serializer.data) - return Response( - serializer.data, status=status.HTTP_201_CREATED, - headers=headers - ) - - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - ''' - class APIFolderView(generics.RetrieveUpdateDestroyAPIView): serializer_class = FolderSerializer @@ -100,94 +114,117 @@ class APIFolderView(generics.RetrieveUpdateDestroyAPIView): return super(APIFolderView, self).put(*args, **kwargs) -class APIFolderDocumentListView(generics.ListAPIView): +class APIFolderDocumentListView(generics.ListCreateAPIView): """ Returns a list of all the documents contained in a particular folder. """ filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_document_view,)} + mayan_object_permissions = { + 'GET': (permission_folder_view,), + 'POST': (permission_folder_add_document,) + } def get_serializer_class(self): - from documents.serializers import DocumentSerializer - return DocumentSerializer + if self.request.method == 'GET': + return FolderDocumentSerializer + elif self.request.method == 'POST': + return NewFolderDocumentSerializer + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'folder': self.get_folder(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + def get_folder(self): + return get_object_or_404(Folder, pk=self.kwargs['pk']) def get_queryset(self): - folder = get_object_or_404(Folder, pk=self.kwargs['pk']) + folder = self.get_folder() + + documents = folder.documents.all() + try: Permission.check_permissions( - self.request.user, [permission_folder_view] + self.request.user, (permission_document_view,) ) except PermissionDenied: - AccessControlList.objects.check_access( - permission_folder_view, self.request.user, folder + documents = AccessControlList.objects.filter_by_access( + permission_document_view, self.request.user, documents ) - return folder.documents.all() + return documents + + def perform_create(self, serializer): + serializer.save(folder=self.get_folder()) - # TODO: move this method as post of APIFolderDocumentListView def post(self, request, *args, **kwargs): """ Add a document to the selected folder. """ - - folder = get_object_or_404(Folder, pk=self.kwargs['pk']) - try: - Permission.check_permissions( - request.user, (permission_folder_add_document,) - ) - except PermissionDenied: - AccessControlList.objects.check_access( - permission_folder_add_document, request.user, folder - ) - - document = get_object_or_404(Document, pk=self.kwargs['document_pk']) - folder.documents.add(document) - return Response(status=status.HTTP_201_CREATED) + return super(APIFolderDocumentListView, self).post(request, *args, **kwargs) -class APIDocumentFolderListView(generics.ListAPIView): - """ - Returns a list of all the folders to which a document belongs. - """ - - serializer_class = FolderSerializer - +class APIFolderDocumentView(generics.RetrieveDestroyAPIView): filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_folder_view,)} - - def get_queryset(self): - document = get_object_or_404(Document, pk=self.kwargs['pk']) - try: - Permission.check_permissions( - self.request.user, [permission_document_view] - ) - except PermissionDenied: - AccessControlList.objects.check_access( - permission_document_view, self.request.user, document - ) - - queryset = document.folders.all() - return queryset - - -class APIFolderDocumentView(views.APIView): + mayan_object_permissions = { + 'GET': (permission_folder_view,), + 'DELETE': (permission_folder_remove_document,) + } + mayan_permission_attribute_check = 'folder' + serializer_class = FolderDocumentSerializer def delete(self, request, *args, **kwargs): """ Remove a document from the selected folder. """ - folder = get_object_or_404(Folder, pk=self.kwargs['pk']) + return super(APIFolderDocumentView, self).delete(request, *args, **kwargs) + + def get(self, *args, **kwargs): + """ + Returns the details of the selected folder document. + """ + + return super(APIFolderDocumentView, self).get(*args, **kwargs) + + def get_folder(self): + return get_object_or_404(Folder, pk=self.kwargs['folder_pk']) + + def get_queryset(self): + return self.get_folder().documents.all() + + def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ + return { + 'folder': self.get_folder(), + 'format': self.format_kwarg, + 'request': self.request, + 'view': self + } + + def perform_destroy(self, instance): + self.get_folder().documents.remove(instance) + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + try: Permission.check_permissions( - request.user, (permission_folder_remove_document,) + self.request.user, (permission_document_view,) ) except PermissionDenied: - AccessControlList.objects.check_access( - permission_folder_remove_document, request.user, folder + documents = AccessControlList.objects.check_access( + permission_document_view, self.request.user, documents ) - document = get_object_or_404(Document, pk=self.kwargs['document_pk']) - folder.documents.remove(document) - return Response(status=status.HTTP_204_NO_CONTENT) + serializer = self.get_serializer(instance) + return Response(serializer.data) diff --git a/mayan/apps/folders/serializers.py b/mayan/apps/folders/serializers.py index 616b21731a..e206c3f91c 100644 --- a/mayan/apps/folders/serializers.py +++ b/mayan/apps/folders/serializers.py @@ -1,19 +1,70 @@ from __future__ import unicode_literals from rest_framework import serializers +from rest_framework.exceptions import ValidationError +from rest_framework.reverse import reverse +from django.utils.translation import ugettext_lazy as _ + +from documents.models import Document +from documents.serializers import DocumentSerializer from user_management.serializers import UserSerializer from .models import Folder -class FolderSerializer(serializers.ModelSerializer): - documents = serializers.SerializerMethodField('get_documents_count') +class FolderSerializer(serializers.HyperlinkedModelSerializer): + documents = serializers.HyperlinkedIdentityField(view_name='rest_api:folder-document-list') + documents_count = serializers.SerializerMethodField() user = UserSerializer(read_only=True) class Meta: - fields = ('datetime_created', 'documents', 'id', 'label', 'user') + extra_kwargs = { + 'url': {'view_name': 'rest_api:folder-detail'}, + 'user': {'view_name': 'rest_api:user-detail'} + } + fields = ('datetime_created', 'documents', 'documents_count', 'id', 'label', 'url', 'user') model = Folder def get_documents_count(self, obj): return obj.documents.count() + + +class NewFolderSerializer(serializers.Serializer): + label = serializers.CharField() + + def create(self, validated_data): + try: + data = validated_data.copy() + data.update({'user': self.context['request'].user}) + return Folder.objects.create(**data) + except Exception as exception: + raise ValidationError(exception) + + +class FolderDocumentSerializer(DocumentSerializer): + remove = serializers.SerializerMethodField() + + def get_remove(self, instance): + return reverse( + 'rest_api:folder-document', args=( + self.context['folder'].pk, instance.pk + ), request=self.context['request'], format=self.context['format'] + ) + + class Meta(DocumentSerializer.Meta): + fields = DocumentSerializer.Meta.fields + ('remove',) + read_only_fields = DocumentSerializer.Meta.fields + + +class NewFolderDocumentSerializer(serializers.Serializer): + document = serializers.IntegerField(help_text=_('Primary key of the document to be added.')) + + def create(self, validated_data): + try: + document = Document.objects.get(pk=validated_data['document']) + validated_data['folder'].documents.add(document) + except Exception as exception: + raise ValidationError(exception) + + return {'document': document.pk} diff --git a/mayan/apps/folders/test_api.py b/mayan/apps/folders/test_api.py index 34ac165774..1b23b82d4c 100644 --- a/mayan/apps/folders/test_api.py +++ b/mayan/apps/folders/test_api.py @@ -9,6 +9,7 @@ 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, @@ -34,14 +35,86 @@ class FolderAPITestCase(APITestCase): self.client.force_authenticate(user=self.admin_user) - def testDown(self): + def tearDown(self): self.admin_user.delete() def test_folder_create(self): - self.client.post(reverse('rest_api:folder-list'), {'label': TEST_FOLDER_LABEL}) + self.client.post( + reverse('rest_api:folder-list'), {'label': TEST_FOLDER_LABEL} + ) folder = Folder.objects.first() self.assertEqual(Folder.objects.count(), 1) self.assertEqual(folder.label, TEST_FOLDER_LABEL) self.assertEqual(folder.user, self.admin_user) + + def test_folder_delete(self): + folder = Folder.objects.create( + label=TEST_FOLDER_LABEL, user=self.admin_user + ) + + self.client.delete(reverse('rest_api:folder-detail', args=(folder.pk,))) + + self.assertEqual(Folder.objects.count(), 0) + + def test_folder_edit(self): + folder = Folder.objects.create( + label=TEST_FOLDER_LABEL, user=self.admin_user + ) + + self.client.put( + reverse('rest_api:folder-detail', args=(folder.pk,)), + {'label': TEST_FOLDER_LABEL + ' edited'} + ) + + folder = Folder.objects.first() + + self.assertEqual(folder.label, TEST_FOLDER_LABEL + ' edited') + + def test_folder_add_document(self): + folder = Folder.objects.create(label=TEST_FOLDER_LABEL, user=self.admin_user) + + 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:folder-document-list', args=(folder.pk,)), + {'document': document.pk} + ) + + self.assertEqual(folder.documents.count(), 1) + + def test_folder_remove_document(self): + folder = Folder.objects.create(label=TEST_FOLDER_LABEL, user=self.admin_user) + + 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), + ) + + folder.documents.add(document) + + self.client.delete( + reverse('rest_api:folder-document', args=(folder.pk, document.pk)), + ) + + self.assertEqual(folder.documents.count(), 0) diff --git a/mayan/apps/folders/urls.py b/mayan/apps/folders/urls.py index c76d168e69..6e7055a501 100644 --- a/mayan/apps/folders/urls.py +++ b/mayan/apps/folders/urls.py @@ -43,7 +43,7 @@ urlpatterns = patterns( api_urls = patterns( '', url( - r'^folders/(?P[0-9]+)/documents/(?P[0-9]+)/$', + r'^folders/(?P[0-9]+)/documents/(?P[0-9]+)/$', APIFolderDocumentView.as_view(), name='folder-document' ), url(