Refactor and implement download code natively

- Use modified port of Django 2.2 FileResponse.
- Remove Django DownloadView library.

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-12-12 19:39:44 -04:00
parent 826f7fddf2
commit a7b31fc171
24 changed files with 355 additions and 278 deletions
+11 -19
View File
@@ -6,12 +6,12 @@ from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.cache import cache_control, patch_cache_control
from django_downloadview import DownloadMixin, VirtualFile
from rest_framework import status
from rest_framework.response import Response
from mayan.apps.acls.models import AccessControlList
from mayan.apps.rest_api import generics
from mayan.apps.common.generics import DownloadMixin
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from .models import (
@@ -113,15 +113,11 @@ class APIDocumentDownloadView(DownloadMixin, generics.RetrieveAPIView):
}
queryset = Document.objects.all()
def get_encoding(self):
return self.get_object().latest_version.encoding
def get_download_file_object(self):
return self.get_object().open()
def get_file(self):
instance = self.get_object()
return VirtualFile(instance.latest_version.file, name=instance.label)
def get_mimetype(self):
return self.get_object().latest_version.mimetype
def get_download_filename(self):
return self.get_object().label
def get_serializer(self, *args, **kwargs):
return None
@@ -349,10 +345,11 @@ class APIDocumentVersionDownloadView(DownloadMixin, generics.RetrieveAPIView):
)
return document
def get_encoding(self):
return self.get_object().encoding
def get_download_file_object(self):
instance = self.get_object()
return instance.open()
def get_file(self):
def get_download_filename(self):
preserve_extension = self.request.GET.get(
'preserve_extension', self.request.POST.get(
'preserve_extension', False
@@ -362,15 +359,10 @@ class APIDocumentVersionDownloadView(DownloadMixin, generics.RetrieveAPIView):
preserve_extension = preserve_extension == 'true' or preserve_extension == 'True'
instance = self.get_object()
return VirtualFile(
instance.file, name=instance.get_rendered_string(
preserve_extension=preserve_extension
)
return instance.get_rendered_string(
preserve_extension=preserve_extension
)
def get_mimetype(self):
return self.get_object().mimetype
def get_serializer(self, *args, **kwargs):
return None
+3 -1
View File
@@ -45,7 +45,9 @@ class DocumentDownloadForm(forms.Form):
super(DocumentDownloadForm, self).__init__(*args, **kwargs)
if self.queryset.count() > 1:
self.fields['compressed'].initial = True
self.fields['compressed'].widget.attrs.update({'disabled': True})
self.fields['compressed'].widget.attrs.update(
{'disabled': 'disabled'}
)
class DocumentForm(forms.ModelForm):
+8 -1
View File
@@ -204,13 +204,20 @@ class DocumentViewTestMixin(object):
}
)
def _request_document_download_form_view(self):
def _request_document_download_form_get_view(self):
return self.get(
viewname='documents:document_download_form', kwargs={
'pk': self.test_document.pk
}
)
def _request_document_download_form_post_view(self):
return self.post(
viewname='documents:document_download_form', kwargs={
'pk': self.test_document.pk
}
)
def _request_document_download_view(self):
return self.get(
viewname='documents:document_download', kwargs={
+12 -19
View File
@@ -4,7 +4,6 @@ import time
from django.utils.encoding import force_text
from django_downloadview import assert_download_response
from rest_framework import status
from mayan.apps.rest_api.tests.base import BaseAPITestCase
@@ -213,12 +212,10 @@ class DocumentAPIViewTestCase(
self.assertEqual(response.status_code, status.HTTP_200_OK)
with self.test_document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type='{}; charset=utf-8'.format(
self.test_document.file_mimetype
)
self.assert_download_response(
response=response, content=file_object.read(),
filename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type=self.test_document.file_mimetype
)
def test_document_api_upload_view_no_permission(self):
@@ -418,12 +415,10 @@ class DocumentVersionAPIViewTestCase(
self.assertEqual(response.status_code, status.HTTP_200_OK)
with self.test_document.latest_version.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename=force_text(self.test_document.latest_version),
mime_type='{}; charset=utf-8'.format(
self.test_document.file_mimetype
)
self.assert_download_response(
response=response, content=file_object.read(),
filename=force_text(self.test_document.latest_version),
mime_type=self.test_document.file_mimetype
)
def test_document_version_api_download_preserve_extension_view(self):
@@ -440,13 +435,11 @@ class DocumentVersionAPIViewTestCase(
)
with self.test_document.latest_version.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename=self.test_document.latest_version.get_rendered_string(
self.assert_download_response(
response=response, content=file_object.read(),
filename=self.test_document.latest_version.get_rendered_string(
preserve_extension=True
), mime_type='{}; charset=utf-8'.format(
self.test_document.file_mimetype
)
), mime_type=self.test_document.file_mimetype
)
def test_document_version_api_list_view_no_permission(self):
@@ -166,33 +166,40 @@ class DocumentViewTestCase(
Document.objects.first().document_type, document_type_2
)
def test_document_download_form_view_no_permission(self):
response = self._request_document_download_form_view()
self.assertNotContains(
response=response, text=self.test_document.label, status_code=200
)
def test_document_download_form_get_view_no_permission(self):
response = self._request_document_download_form_get_view()
self.assertEqual(response.status_code, 404)
def test_document_download_form_view_with_access(self):
def test_document_download_form_get_view_with_access(self):
self.grant_access(
obj=self.test_document, permission=permission_document_download
)
response = self._request_document_download_form_view()
response = self._request_document_download_form_get_view()
self.assertContains(
response=response, text=self.test_document.label, status_code=200
)
def test_document_download_form_post_view_no_permission(self):
response = self._request_document_download_form_post_view()
self.assertEqual(response.status_code, 404)
def test_document_download_form_post_view_with_access(self):
self.grant_access(
obj=self.test_document, permission=permission_document_download
)
response = self._request_document_download_form_post_view()
self.assertEqual(response.status_code, 302)
def test_document_download_view_no_permission(self):
response = self._request_document_download_view()
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 404)
def test_document_download_view_with_permission(self):
# Set the expected_content_types for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_types = (
'{}; charset=utf-8'.format(
self.test_document.file_mimetype
),
self.test_document.file_mimetype,
)
self.grant_access(
@@ -205,21 +212,19 @@ class DocumentViewTestCase(
with self.test_document.open() as file_object:
self.assert_download_response(
response=response, content=file_object.read(),
basename=TEST_SMALL_DOCUMENT_FILENAME,
filename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type=self.test_document.file_mimetype
)
def test_document_multiple_download_view_no_permission(self):
response = self._request_document_multiple_download_view()
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 404)
def test_document_multiple_download_view_with_permission(self):
# Set the expected_content_types for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_types = (
'{}; charset=utf-8'.format(
self.test_document.file_mimetype
),
self.test_document.file_mimetype,
)
self.grant_access(
obj=self.test_document, permission=permission_document_download
@@ -231,21 +236,19 @@ class DocumentViewTestCase(
with self.test_document.open() as file_object:
self.assert_download_response(
response=response, content=file_object.read(),
basename=TEST_SMALL_DOCUMENT_FILENAME,
filename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type=self.test_document.file_mimetype
)
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, 404)
def test_document_version_download_view_with_permission(self):
# Set the expected_content_types for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_types = (
'{}; charset=utf-8'.format(
self.test_document.latest_version.mimetype
),
self.test_document.latest_version.mimetype,
)
self.grant_access(
@@ -258,19 +261,15 @@ class DocumentViewTestCase(
with self.test_document.open() as file_object:
self.assert_download_response(
response=response, content=file_object.read(),
basename=force_text(self.test_document.latest_version),
mime_type='{}; charset=utf-8'.format(
self.test_document.latest_version.mimetype
)
filename=force_text(self.test_document.latest_version),
mime_type=self.test_document.latest_version.mimetype
)
def test_document_version_download_preserve_extension_view_with_permission(self):
# Set the expected_content_types for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_types = (
'{}; charset=utf-8'.format(
self.test_document.latest_version.mimetype
),
self.test_document.latest_version.mimetype,
)
self.grant_access(
@@ -285,11 +284,9 @@ class DocumentViewTestCase(
with self.test_document.open() as file_object:
self.assert_download_response(
response=response, content=file_object.read(),
basename=self.test_document.latest_version.get_rendered_string(
filename=self.test_document.latest_version.get_rendered_string(
preserve_extension=True
), mime_type='{}; charset=utf-8'.format(
self.test_document.latest_version.mimetype
)
), mime_type=self.test_document.latest_version.mimetype
)
def test_document_update_page_count_view_no_permission(self):
+4 -5
View File
@@ -1,7 +1,6 @@
from __future__ import unicode_literals
from actstream.models import Action
from django_downloadview import assert_download_response
from ..events import (
event_document_download, event_document_trashed, event_document_view
@@ -45,11 +44,11 @@ class DocumentEventsTestCase(
def test_document_download_event_no_permission(self):
response = self._request_test_document_download_view()
self.assertEqual(response.status_code, 403)
self.assertEqual(response.status_code, 404)
self.assertEqual(list(Action.objects.any(obj=self.test_document)), [])
def test_document_download_event_with_access(self):
self.expected_content_types = ('image/png; charset=utf-8',)
self.expected_content_types = ('image/png',)
self.grant_access(
obj=self.test_document, permission=permission_document_download
@@ -59,8 +58,8 @@ class DocumentEventsTestCase(
# Download the file to close the file descriptor
with self.test_document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
self.assert_download_response(
response=response, content=file_object.read(),
mime_type=self.test_document.file_mimetype
)
+5
View File
@@ -272,6 +272,11 @@ urlpatterns_document_versions = [
view=DocumentVersionDownloadView.as_view(),
name='document_version_download'
),
url(
regex=r'^documents/versions/multiple/download/$',
view=DocumentVersionDownloadView.as_view(),
name='document_multiple_version_download'
),
url(
regex=r'^documents/versions/(?P<pk>\d+)/revert/$',
view=DocumentVersionRevertView.as_view(),
@@ -14,8 +14,7 @@ from ..events import event_document_view
from ..forms import DocumentVersionDownloadForm, DocumentVersionPreviewForm
from ..models import Document, DocumentVersion
from ..permissions import (
permission_document_download, permission_document_version_revert,
permission_document_version_view
permission_document_version_revert, permission_document_version_view
)
from .document_views import DocumentDownloadFormView, DocumentDownloadView
@@ -31,11 +30,11 @@ logger = logging.getLogger(__name__)
class DocumentVersionDownloadFormView(DocumentDownloadFormView):
form_class = DocumentVersionDownloadForm
model = DocumentVersion
multiple_download_view = None
pk_url_kwarg = 'pk'
querystring_form_fields = (
'compressed', 'zip_filename', 'preserve_extension'
)
single_download_view = 'documents:document_version_download'
viewname = 'documents:document_multiple_version_download'
def get_extra_context(self):
result = super(
@@ -48,31 +47,12 @@ class DocumentVersionDownloadFormView(DocumentDownloadFormView):
return result
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
if not id_list:
id_list = self.kwargs['pk']
return self.model.objects.filter(
pk__in=id_list.split(',')
)
class DocumentVersionDownloadView(DocumentDownloadView):
model = DocumentVersion
object_permission = permission_document_download
pk_url_kwarg = 'pk'
@staticmethod
def get_item_file(item):
return item.file
def get_encoding(self):
return self.get_object().encoding
def get_item_label(self, item):
def get_item_filename(self, item):
preserve_extension = self.request.GET.get(
'preserve_extension', self.request.POST.get(
'preserve_extension', False
@@ -83,9 +63,6 @@ class DocumentVersionDownloadView(DocumentDownloadView):
return item.get_rendered_string(preserve_extension=preserve_extension)
def get_mimetype(self):
return self.get_object().mimetype
class DocumentVersionListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
+49 -93
View File
@@ -2,22 +2,23 @@ from __future__ import absolute_import, unicode_literals
import logging
from furl import furl
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.http import urlencode
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _, ungettext
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.compressed_files import ZipArchive
from mayan.apps.common.generics import (
FormView, MultipleObjectConfirmActionView, MultipleObjectFormActionView,
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectEditView,
SingleObjectListView
FormView, MultipleObjectConfirmActionView, MultipleObjectDownloadView,
MultipleObjectFormActionView, SingleObjectDetailView,
SingleObjectEditView, SingleObjectListView
)
from mayan.apps.converter.layers import layer_saved_transformations
from mayan.apps.converter.permissions import (
@@ -154,55 +155,36 @@ class DocumentDocumentTypeEditView(MultipleObjectFormActionView):
)
class DocumentDownloadFormView(FormView):
class DocumentDownloadFormView(MultipleObjectFormActionView):
form_class = DocumentDownloadForm
model = Document
multiple_download_view = 'documents:document_multiple_download'
object_permission = permission_document_download
pk_url_kwarg = 'pk'
querystring_form_fields = ('compressed', 'zip_filename')
single_download_view = 'documents:document_download'
viewname = 'documents:document_multiple_download'
def form_valid(self, form):
querystring_dictionary = {}
# Turn a queryset into a comma separated list of primary keys
id_list = ','.join(
[
force_text(pk) for pk in self.get_object_list().values_list('pk', flat=True)
]
)
# Construct URL with querystring to pass on to the next view
url = furl(
args={
'id_list': id_list
}, path=reverse(viewname=self.viewname)
)
# Pass the form field data as URL querystring to the next view
for field in self.querystring_form_fields:
data = form.cleaned_data[field]
if data:
querystring_dictionary[field] = data
url.args[field] = data
querystring_dictionary.update(
{
'id_list': ','.join(
map(str, self.queryset.values_list('pk', flat=True))
)
}
)
querystring = urlencode(querystring_dictionary, doseq=True)
if self.queryset.count() > 1:
url = reverse(self.multiple_download_view)
else:
url = reverse(
viewname=self.single_download_view, kwargs={
'pk': self.queryset.first().pk
}
)
return HttpResponseRedirect(
redirect_to='{}?{}'.format(url, querystring)
)
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
if not id_list:
id_list = self.kwargs['pk']
return self.model.objects.filter(
pk__in=id_list.split(',')
)
return HttpResponseRedirect(redirect_to=url.tostr())
def get_extra_context(self):
subtemplates_list = [
@@ -210,7 +192,6 @@ class DocumentDownloadFormView(FormView):
'name': 'appearance/generic_list_items_subtemplate.html',
'context': {
'object_list': self.queryset,
'hide_link': True,
'hide_links': True,
'hide_multi_item_actions': True,
}
@@ -230,21 +211,14 @@ class DocumentDownloadFormView(FormView):
def get_form_kwargs(self):
kwargs = super(DocumentDownloadFormView, self).get_form_kwargs()
self.queryset = self.get_queryset()
self.queryset = self.get_object_list()
kwargs.update({'queryset': self.queryset})
return kwargs
def get_queryset(self):
return AccessControlList.objects.restrict_queryset(
permission=permission_document_download,
queryset=self.get_document_queryset(), user=self.request.user
)
class DocumentDownloadView(SingleObjectDownloadView):
class DocumentDownloadView(MultipleObjectDownloadView):
model = Document
# Set to None to disable the .get_object call
object_permission = None
object_permission = permission_document_download
@staticmethod
def commit_event(item, request):
@@ -259,39 +233,23 @@ class DocumentDownloadView(SingleObjectDownloadView):
target=item.document
)
@staticmethod
def get_item_file(item):
return item.open()
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
if not id_list:
id_list = self.kwargs['pk']
queryset = self.model.objects.filter(pk__in=id_list.split(','))
return AccessControlList.objects.restrict_queryset(
permission=permission_document_download, queryset=queryset,
user=self.request.user
)
def get_file(self):
queryset = self.get_document_queryset()
zip_filename = self.request.GET.get(
def get_archive_filename(self):
return self.request.GET.get(
'zip_filename', DEFAULT_ZIP_FILENAME
)
def get_download_file_object(self):
queryset = self.get_object_list()
zip_filename = self.get_archive_filename()
if self.request.GET.get('compressed') == 'True' or queryset.count() > 1:
compressed_file = ZipArchive()
compressed_file.create()
for item in queryset:
with DocumentDownloadView.get_item_file(item=item) as file_object:
with item.open() as file_object:
compressed_file.add_file(
file_object=file_object,
filename=self.get_item_label(item=item)
filename=self.get_item_filename(item=item)
)
DocumentDownloadView.commit_event(
item=item, request=self.request
@@ -299,24 +257,22 @@ class DocumentDownloadView(SingleObjectDownloadView):
compressed_file.close()
return DocumentDownloadView.VirtualFile(
compressed_file.as_file(zip_filename), name=zip_filename
)
return compressed_file.as_file(zip_filename)
else:
item = queryset.first()
if item:
DocumentDownloadView.commit_event(
item=item, request=self.request
)
else:
raise PermissionDenied
return DocumentDownloadView.VirtualFile(
DocumentDownloadView.get_item_file(item=item),
name=self.get_item_label(item=item)
DocumentDownloadView.commit_event(
item=item, request=self.request
)
return item.open()
def get_item_label(self, item):
def get_download_filename(self):
queryset = self.get_object_list()
if self.request.GET.get('compressed') == 'True' or queryset.count() > 1:
return self.get_archive_filename()
else:
return self.get_item_filename(item=queryset.first())
def get_item_filename(self, item):
return item.label