diff --git a/mayan/apps/documents/api_views.py b/mayan/apps/documents/api_views.py index 8851f9da1f..a604ad76c6 100644 --- a/mayan/apps/documents/api_views.py +++ b/mayan/apps/documents/api_views.py @@ -5,6 +5,7 @@ import logging from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404 +from filetransfers.api import serve_file from rest_framework import generics, status from rest_framework.response import Response @@ -18,19 +19,19 @@ from .models import ( ) from .permissions import ( permission_document_create, permission_document_delete, - permission_document_edit, permission_document_new_version, - permission_document_properties_edit, permission_document_restore, - permission_document_trash, permission_document_version_revert, - permission_document_view, permission_document_type_create, - permission_document_type_delete, permission_document_type_edit, - permission_document_type_view + permission_document_download, permission_document_edit, + permission_document_new_version, permission_document_properties_edit, + permission_document_restore, permission_document_trash, + permission_document_version_revert, permission_document_view, + permission_document_type_create, permission_document_type_delete, + permission_document_type_edit, permission_document_type_view ) from .serializers import ( DeletedDocumentSerializer, DocumentPageImageSerializer, - DocumentPageSerializer, DocumentSerializer, DocumentTypeSerializer, - DocumentVersionSerializer, DocumentVersionRevertSerializer, - NewDocumentSerializer, NewDocumentVersionSerializer, - RecentDocumentSerializer + DocumentPageSerializer, DocumentSerializer, + DocumentTypeSerializer, DocumentVersionSerializer, + DocumentVersionRevertSerializer, NewDocumentSerializer, + NewDocumentVersionSerializer, RecentDocumentSerializer ) logger = logging.getLogger(__name__) @@ -86,6 +87,37 @@ class APIDeletedDocumentRestoreView(generics.GenericAPIView): return Response(status=status.HTTP_200_OK) +class APIDocumentDownloadView(generics.RetrieveAPIView): + """ + Download the latest version of a document. + --- + GET: + omit_serializer: true + parameters: + - name: pk + paramType: path + type: number + """ + + mayan_object_permissions = { + 'GET': (permission_document_download,) + } + permission_classes = (MayanPermission,) + queryset = Document.objects.all() + + def get_serializer_class(self): + return None + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + return serve_file( + request, + instance.latest_version.file, + save_as='"%s"' % instance.label, + content_type=instance.latest_version.mimetype if instance.latest_version.mimetype else 'application/octet-stream' + ) + + class APIDocumentListView(generics.ListCreateAPIView): """ Returns a list of all the documents. @@ -114,6 +146,37 @@ class APIDocumentListView(generics.ListCreateAPIView): return super(APIDocumentListView, self).post(*args, **kwargs) +class APIDocumentVersionDownloadView(generics.RetrieveAPIView): + """ + Download a document version. + --- + GET: + omit_serializer: true + parameters: + - name: pk + paramType: path + type: number + """ + + mayan_object_permissions = { + 'GET': (permission_document_download,) + } + permission_classes = (MayanPermission,) + queryset = DocumentVersion.objects.all() + + def get_serializer_class(self): + return None + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + return serve_file( + request, + instance.file, + save_as='"%s"' % instance.document.label, + content_type=instance.mimetype if instance.mimetype else 'application/octet-stream' + ) + + class APIDocumentView(generics.RetrieveUpdateDestroyAPIView): """ Returns the selected document details. diff --git a/mayan/apps/documents/tests/literals.py b/mayan/apps/documents/tests/literals.py index 449005d7b7..d5e70ca242 100644 --- a/mayan/apps/documents/tests/literals.py +++ b/mayan/apps/documents/tests/literals.py @@ -36,6 +36,7 @@ TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME = 'I18N_title_áéíóúüñÑ.png.z TEST_NON_ASCII_DOCUMENT_FILENAME = 'I18N_title_áéíóúüñÑ.png' TEST_OFFICE_DOCUMENT = 'simple_2_page_document.doc' TEST_SMALL_DOCUMENT_FILENAME = 'title_page.png' +TEST_SMALL_DOCUMENT_CHECKSUM = 'efa10e6cc21f83078aaa94d5cbe51de67b51af706143bafc7fd6d4c02124879a' # File paths TEST_COMPRESSED_DOCUMENT_PATH = os.path.join( diff --git a/mayan/apps/documents/tests/test_api.py b/mayan/apps/documents/tests/test_api.py index 9fa1bc02f5..35708d45ff 100644 --- a/mayan/apps/documents/tests/test_api.py +++ b/mayan/apps/documents/tests/test_api.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals +import io import time from json import loads @@ -17,9 +18,9 @@ from rest_framework.test import APITestCase from .literals import ( TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE, - TEST_SMALL_DOCUMENT_PATH, + TEST_SMALL_DOCUMENT_CHECKSUM, TEST_SMALL_DOCUMENT_PATH, ) -from ..models import Document, DocumentType +from ..models import Document, DocumentType, HASH_FUNCTION class DocumentTypeAPITestCase(APITestCase): @@ -231,5 +232,46 @@ class DocumentAPITestCase(APITestCase): self.assertEqual(document_version, document.latest_version) + def test_document_download(self): + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + document = self.document_type.new_document( + file_object=File(file_object), + ) + + response = self.client.get( + reverse( + 'rest_api:document-download', args=(document.pk,) + ) + ) + buf = io.BytesIO() + buf.write(response.content) + + self.assertEqual( + HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM + ) + + del(buf) + + def test_document_version_download(self): + with open(TEST_SMALL_DOCUMENT_PATH) as file_object: + document = self.document_type.new_document( + file_object=File(file_object), + ) + + response = self.client.get( + reverse( + 'rest_api:documentversion-download', + args=(document.latest_version.pk,) + ) + ) + buf = io.BytesIO() + buf.write(response.content) + + self.assertEqual( + HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM + ) + + del(buf) + #def test_document_set_document_type(self): # pass diff --git a/mayan/apps/documents/urls.py b/mayan/apps/documents/urls.py index 0d78448cd5..dc24101422 100644 --- a/mayan/apps/documents/urls.py +++ b/mayan/apps/documents/urls.py @@ -4,7 +4,8 @@ from django.conf.urls import patterns, url from .api_views import ( APIDeletedDocumentListView, APIDeletedDocumentRestoreView, - APIDeletedDocumentView, APIDocumentView, APIDocumentListView, + APIDeletedDocumentView, APIDocumentDownloadView, APIDocumentView, + APIDocumentListView, APIDocumentVersionDownloadView, APIDocumentPageImageView, APIDocumentPageView, APIDocumentTypeDocumentListView, APIDocumentTypeListView, APIDocumentTypeView, APIDocumentVersionsListView, @@ -263,6 +264,10 @@ api_urls = patterns( r'^documents/(?P[0-9]+)/versions/$', APIDocumentVersionsListView.as_view(), name='document-version-list' ), + url( + r'^documents/(?P[0-9]+)/download/$', + APIDocumentDownloadView.as_view(), name='document-download' + ), url( r'^document_version/(?P[0-9]+)/$', APIDocumentVersionView.as_view(), name='documentversion-detail' @@ -271,6 +276,10 @@ api_urls = patterns( r'^document_version/(?P[0-9]+)/revert/$', APIDocumentVersionRevertView.as_view(), name='documentversion-revert' ), + url( + r'^document_version/(?P[0-9]+)/download/$', + APIDocumentVersionDownloadView.as_view(), name='documentversion-download' + ), url( r'^document_page/(?P[0-9]+)/$', APIDocumentPageView.as_view(), name='documentpage-detail'