Move extension preservetation code to the model. Improve document,
version and API views to include proper mimetype and encoding. Update respective tests. GitLab issue #415. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import logging
|
|||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.encoding import force_text
|
|
||||||
|
|
||||||
from django_downloadview import DownloadMixin, VirtualFile
|
from django_downloadview import DownloadMixin, VirtualFile
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics, status
|
||||||
@@ -109,10 +108,16 @@ class APIDocumentDownloadView(DownloadMixin, generics.RetrieveAPIView):
|
|||||||
permission_classes = (MayanPermission,)
|
permission_classes = (MayanPermission,)
|
||||||
queryset = Document.objects.all()
|
queryset = Document.objects.all()
|
||||||
|
|
||||||
|
def get_encoding(self):
|
||||||
|
return self.get_object().latest_version.encoding
|
||||||
|
|
||||||
def get_file(self):
|
def get_file(self):
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
return VirtualFile(instance.latest_version.file, name=instance.label)
|
return VirtualFile(instance.latest_version.file, name=instance.label)
|
||||||
|
|
||||||
|
def get_mimetype(self):
|
||||||
|
return self.get_object().latest_version.mimetype
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -186,6 +191,9 @@ class APIDocumentVersionDownloadView(DownloadMixin, generics.RetrieveAPIView):
|
|||||||
- name: pk
|
- name: pk
|
||||||
paramType: path
|
paramType: path
|
||||||
type: number
|
type: number
|
||||||
|
- name: preserve_extension
|
||||||
|
paramType: query
|
||||||
|
type: boolean
|
||||||
"""
|
"""
|
||||||
lookup_url_kwarg = 'version_pk'
|
lookup_url_kwarg = 'version_pk'
|
||||||
|
|
||||||
@@ -197,9 +205,27 @@ class APIDocumentVersionDownloadView(DownloadMixin, generics.RetrieveAPIView):
|
|||||||
)
|
)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
def get_encoding(self):
|
||||||
|
return self.get_object().encoding
|
||||||
|
|
||||||
def get_file(self):
|
def get_file(self):
|
||||||
|
preserve_extension = self.request.GET.get(
|
||||||
|
'preserve_extension', self.request.POST.get(
|
||||||
|
'preserve_extension', False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
preserve_extension = preserve_extension == 'true' or preserve_extension == 'True'
|
||||||
|
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
return VirtualFile(instance.file, name=force_text(instance))
|
return VirtualFile(
|
||||||
|
instance.file, name=instance.get_rendered_string(
|
||||||
|
preserve_extension=preserve_extension
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_mimetype(self):
|
||||||
|
return self.get_object().mimetype
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -409,7 +410,13 @@ class DocumentVersion(models.Model):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('documents:document_version_view', args=(self.pk,))
|
return reverse('documents:document_version_view', args=(self.pk,))
|
||||||
|
|
||||||
def get_rendered_string(self):
|
def get_rendered_string(self, preserve_extension=False):
|
||||||
|
if preserve_extension:
|
||||||
|
filename, extension = os.path.splitext(self.document.label)
|
||||||
|
return '{} ({}){}'.format(
|
||||||
|
filename, self.get_rendered_timestamp(), extension
|
||||||
|
)
|
||||||
|
else:
|
||||||
return Template(
|
return Template(
|
||||||
'{{ instance.document }} - {{ instance.timestamp }}'
|
'{{ instance.document }} - {{ instance.timestamp }}'
|
||||||
).render(context=Context({'instance': self}))
|
).render(context=Context({'instance': self}))
|
||||||
|
|||||||
@@ -119,12 +119,16 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.document_type.delete()
|
self.document_type.delete()
|
||||||
super(DocumentAPITestCase, self).tearDown()
|
super(DocumentAPITestCase, self).tearDown()
|
||||||
|
|
||||||
def _upload_document(self):
|
def _create_document(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
self.document = self.document_type.new_document(
|
self.document = self.document_type.new_document(
|
||||||
file_object=file_object,
|
file_object=file_object,
|
||||||
|
label=TEST_SMALL_DOCUMENT_FILENAME
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# For compatibility
|
||||||
|
return self.document
|
||||||
|
|
||||||
def test_document_upload(self):
|
def test_document_upload(self):
|
||||||
with open(TEST_DOCUMENT_PATH) as file_descriptor:
|
with open(TEST_DOCUMENT_PATH) as file_descriptor:
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@@ -160,10 +164,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(document.page_count, 47)
|
self.assertEqual(document.page_count, 47)
|
||||||
|
|
||||||
def test_document_new_version_upload(self):
|
def test_document_new_version_upload(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
document = self._create_document()
|
||||||
document = self.document_type.new_document(
|
|
||||||
file_object=file_object,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Artifical delay since MySQL doesn't store microsecond data in
|
# Artifical delay since MySQL doesn't store microsecond data in
|
||||||
# timestamps. Version timestamp is used to determine which version
|
# timestamps. Version timestamp is used to determine which version
|
||||||
@@ -193,10 +194,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(document.page_count, 47)
|
self.assertEqual(document.page_count, 47)
|
||||||
|
|
||||||
def test_document_version_revert(self):
|
def test_document_version_revert(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
document = self._create_document()
|
||||||
document = self.document_type.new_document(
|
|
||||||
file_object=file_object,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Needed by MySQL as milliseconds value is not store in timestamp field
|
# Needed by MySQL as milliseconds value is not store in timestamp field
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@@ -220,10 +218,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(document.versions.first(), document.latest_version)
|
self.assertEqual(document.versions.first(), document.latest_version)
|
||||||
|
|
||||||
def test_document_version_list(self):
|
def test_document_version_list(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
document = self._create_document()
|
||||||
document = self.document_type.new_document(
|
|
||||||
file_object=file_object,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Needed by MySQL as milliseconds value is not store in timestamp field
|
# Needed by MySQL as milliseconds value is not store in timestamp field
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@@ -248,10 +243,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_document_download(self):
|
def test_document_download(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
document = self._create_document()
|
||||||
document = self.document_type.new_document(
|
|
||||||
file_object=file_object,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse(
|
reverse(
|
||||||
@@ -267,10 +259,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_document_version_download(self):
|
def test_document_version_download(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
document = self._create_document()
|
||||||
document = self.document_type.new_document(
|
|
||||||
file_object=file_object,
|
|
||||||
)
|
|
||||||
|
|
||||||
latest_version = document.latest_version
|
latest_version = document.latest_version
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
@@ -283,14 +272,33 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
with latest_version.open() as file_object:
|
with latest_version.open() as file_object:
|
||||||
assert_download_response(
|
assert_download_response(
|
||||||
self, response, content=file_object.read(),
|
self, response, content=file_object.read(),
|
||||||
basename='{} - {}'.format(
|
basename=force_text(latest_version),
|
||||||
TEST_SMALL_DOCUMENT_FILENAME,
|
mime_type='{}; charset=utf-8'.format(document.file_mimetype)
|
||||||
latest_version.timestamp
|
)
|
||||||
), mime_type='application/octet-stream; charset=utf-8'
|
|
||||||
|
def test_document_version_download_preserve_extension(self):
|
||||||
|
document = self._create_document()
|
||||||
|
|
||||||
|
latest_version = document.latest_version
|
||||||
|
response = self.client.get(
|
||||||
|
reverse(
|
||||||
|
'rest_api:documentversion-download',
|
||||||
|
args=(document.pk, latest_version.pk,)
|
||||||
|
), data={'preserve_extension': True}
|
||||||
|
)
|
||||||
|
|
||||||
|
with latest_version.open() as file_object:
|
||||||
|
assert_download_response(
|
||||||
|
self, response, content=file_object.read(),
|
||||||
|
basename=latest_version.get_rendered_string(
|
||||||
|
preserve_extension=True
|
||||||
|
), mime_type='{}; charset=utf-8'.format(
|
||||||
|
document.file_mimetype
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_document_version_edit_via_patch(self):
|
def test_document_version_edit_via_patch(self):
|
||||||
self._upload_document()
|
self._create_document()
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse(
|
reverse(
|
||||||
'rest_api:documentversion-detail',
|
'rest_api:documentversion-detail',
|
||||||
@@ -307,7 +315,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_document_version_edit_via_put(self):
|
def test_document_version_edit_via_put(self):
|
||||||
self._upload_document()
|
self._create_document()
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
reverse(
|
reverse(
|
||||||
'rest_api:documentversion-detail',
|
'rest_api:documentversion-detail',
|
||||||
@@ -324,7 +332,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_document_comment_edit_via_patch(self):
|
def test_document_comment_edit_via_patch(self):
|
||||||
self._upload_document()
|
self._create_document()
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse(
|
reverse(
|
||||||
'rest_api:document-detail',
|
'rest_api:document-detail',
|
||||||
@@ -340,7 +348,7 @@ class DocumentAPITestCase(BaseAPITestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_document_comment_edit_via_put(self):
|
def test_document_comment_edit_via_put(self):
|
||||||
self._upload_document()
|
self._create_document()
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
reverse(
|
reverse(
|
||||||
'rest_api:document-detail',
|
'rest_api:document-detail',
|
||||||
@@ -377,7 +385,7 @@ class TrashedDocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.document_type.delete()
|
self.document_type.delete()
|
||||||
super(TrashedDocumentAPITestCase, self).tearDown()
|
super(TrashedDocumentAPITestCase, self).tearDown()
|
||||||
|
|
||||||
def _upload_document(self):
|
def _create_document(self):
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
document = self.document_type.new_document(
|
document = self.document_type.new_document(
|
||||||
file_object=file_object,
|
file_object=file_object,
|
||||||
@@ -386,7 +394,7 @@ class TrashedDocumentAPITestCase(BaseAPITestCase):
|
|||||||
return document
|
return document
|
||||||
|
|
||||||
def test_document_move_to_trash(self):
|
def test_document_move_to_trash(self):
|
||||||
document = self._upload_document()
|
document = self._create_document()
|
||||||
|
|
||||||
self.client.delete(
|
self.client.delete(
|
||||||
reverse('rest_api:document-detail', args=(document.pk,))
|
reverse('rest_api:document-detail', args=(document.pk,))
|
||||||
@@ -396,7 +404,7 @@ class TrashedDocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(Document.trash.count(), 1)
|
self.assertEqual(Document.trash.count(), 1)
|
||||||
|
|
||||||
def test_trashed_document_delete_from_trash(self):
|
def test_trashed_document_delete_from_trash(self):
|
||||||
document = self._upload_document()
|
document = self._create_document()
|
||||||
document.delete()
|
document.delete()
|
||||||
|
|
||||||
self.assertEqual(Document.objects.count(), 0)
|
self.assertEqual(Document.objects.count(), 0)
|
||||||
@@ -409,7 +417,7 @@ class TrashedDocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(Document.trash.count(), 0)
|
self.assertEqual(Document.trash.count(), 0)
|
||||||
|
|
||||||
def test_trashed_document_detail_view(self):
|
def test_trashed_document_detail_view(self):
|
||||||
document = self._upload_document()
|
document = self._create_document()
|
||||||
document.delete()
|
document.delete()
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
@@ -419,7 +427,7 @@ class TrashedDocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(response.data['uuid'], force_text(document.uuid))
|
self.assertEqual(response.data['uuid'], force_text(document.uuid))
|
||||||
|
|
||||||
def test_trashed_document_list_view(self):
|
def test_trashed_document_list_view(self):
|
||||||
document = self._upload_document()
|
document = self._create_document()
|
||||||
document.delete()
|
document.delete()
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
@@ -429,7 +437,7 @@ class TrashedDocumentAPITestCase(BaseAPITestCase):
|
|||||||
self.assertEqual(response.data['results'][0]['uuid'], force_text(document.uuid))
|
self.assertEqual(response.data['results'][0]['uuid'], force_text(document.uuid))
|
||||||
|
|
||||||
def test_trashed_document_restore(self):
|
def test_trashed_document_restore(self):
|
||||||
document = self._upload_document()
|
document = self._create_document()
|
||||||
document.delete()
|
document.delete()
|
||||||
|
|
||||||
self.client.post(
|
self.client.post(
|
||||||
|
|||||||
@@ -286,38 +286,66 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
|||||||
mime_type=self.document.file_mimetype
|
mime_type=self.document.file_mimetype
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_document_version_download_view_no_permission(self):
|
def _request_document_version_download(self, data=None):
|
||||||
response = self.get(
|
data = data or {}
|
||||||
|
return self.get(
|
||||||
'documents:document_version_download', args=(
|
'documents:document_version_download', args=(
|
||||||
self.document.latest_version.pk,
|
self.document.latest_version.pk,
|
||||||
|
), data=data
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
def test_document_version_download_view_no_permission(self):
|
||||||
|
response = self._request_document_version_download()
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_document_version_download_view_with_permission(self):
|
def test_document_version_download_view_with_permission(self):
|
||||||
# Set the expected_content_type for
|
# Set the expected_content_type for
|
||||||
# common.tests.mixins.ContentTypeCheckMixin
|
# common.tests.mixins.ContentTypeCheckMixin
|
||||||
self.expected_content_type = 'application/octet-stream; charset=utf-8'
|
self.expected_content_type = '{}; charset=utf-8'.format(
|
||||||
|
self.document.latest_version.mimetype
|
||||||
|
)
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.document, permission=permission_document_download
|
obj=self.document, permission=permission_document_download
|
||||||
)
|
)
|
||||||
response = self.get(
|
response = self._request_document_version_download()
|
||||||
'documents:document_version_download', args=(
|
|
||||||
self.document.latest_version.pk,
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
with self.document.open() as file_object:
|
||||||
|
self.assert_download_response(
|
||||||
|
response, content=file_object.read(),
|
||||||
|
basename=force_text(self.document.latest_version),
|
||||||
|
mime_type='{}; charset=utf-8'.format(
|
||||||
|
self.document.latest_version.mimetype
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_document_version_download_preserve_extension_view_with_permission(self):
|
||||||
|
# Set the expected_content_type for
|
||||||
|
# common.tests.mixins.ContentTypeCheckMixin
|
||||||
|
self.expected_content_type = '{}; charset=utf-8'.format(
|
||||||
|
self.document.latest_version.mimetype
|
||||||
|
)
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.document, permission=permission_document_download
|
||||||
|
)
|
||||||
|
response = self._request_document_version_download(
|
||||||
|
data={'preserve_extension': True}
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
with self.document.open() as file_object:
|
with self.document.open() as file_object:
|
||||||
self.assert_download_response(
|
self.assert_download_response(
|
||||||
response, content=file_object.read(),
|
response, content=file_object.read(),
|
||||||
basename='{} - {}'.format(
|
basename=self.document.latest_version.get_rendered_string(
|
||||||
TEST_SMALL_DOCUMENT_FILENAME,
|
preserve_extension=True
|
||||||
self.document.latest_version.timestamp
|
), mime_type='{}; charset=utf-8'.format(
|
||||||
), mime_type='application/octet-stream; charset=utf-8'
|
self.document.latest_version.mimetype
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_document_update_page_count_view_no_permission(self):
|
def test_document_update_page_count_view_no_permission(self):
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from acls.models import AccessControlList
|
from acls.models import AccessControlList
|
||||||
@@ -121,6 +119,9 @@ class DocumentVersionDownloadView(DocumentDownloadView):
|
|||||||
def get_item_file(item):
|
def get_item_file(item):
|
||||||
return item.file
|
return item.file
|
||||||
|
|
||||||
|
def get_encoding(self):
|
||||||
|
return self.get_object().encoding
|
||||||
|
|
||||||
def get_item_label(self, item):
|
def get_item_label(self, item):
|
||||||
preserve_extension = self.request.GET.get(
|
preserve_extension = self.request.GET.get(
|
||||||
'preserve_extension', self.request.POST.get(
|
'preserve_extension', self.request.POST.get(
|
||||||
@@ -128,13 +129,12 @@ class DocumentVersionDownloadView(DocumentDownloadView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if preserve_extension:
|
preserve_extension = preserve_extension == 'true' or preserve_extension == 'True'
|
||||||
filename, extension = os.path.splitext(item.document.label)
|
|
||||||
return '{} ({}){}'.format(
|
return item.get_rendered_string(preserve_extension=preserve_extension)
|
||||||
filename, item.get_rendered_timestamp(), extension
|
|
||||||
)
|
def get_mimetype(self):
|
||||||
else:
|
return self.get_object().mimetype
|
||||||
return force_text(item)
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentVersionView(SingleObjectDetailView):
|
class DocumentVersionView(SingleObjectDetailView):
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from django.core.exceptions import PermissionDenied
|
|||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.translation import ugettext_lazy as _, ungettext
|
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user