diff --git a/opentech/apply/funds/urls.py b/opentech/apply/funds/urls.py index dd75bcd4a84bc64ab50dff8c52f85ed9ae0a0155..386993422d70d095bd1c10dc336fd9c0616fdc74 100644 --- a/opentech/apply/funds/urls.py +++ b/opentech/apply/funds/urls.py @@ -12,7 +12,7 @@ from .views import ( SubmissionOverviewView, SubmissionSealedView, SubmissionDeleteView, - SubmissionPrivateMediaRedirectView, + SubmissionPrivateMediaView, ) from .api_views import ( CommentEdit, @@ -39,7 +39,7 @@ submission_urls = ([ path('all/', SubmissionListView.as_view(), name="list"), path( 'documents/submission/<int:submission_id>/<uuid:field_id>/<str:file_name>/', - SubmissionPrivateMediaRedirectView.as_view(), name='private_media_redirect' + SubmissionPrivateMediaView.as_view(), name='serve_private_media' ), path('<int:pk>/', include([ path('', SubmissionDetailView.as_view(), name="detail"), diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py index 0f9d8bab2fdf721d377eb9939fce29be5d0b0490..07fe086f5fd2fb25cd0daed66f8005554d7510ef 100644 --- a/opentech/apply/funds/views.py +++ b/opentech/apply/funds/views.py @@ -1,4 +1,6 @@ +import mimetypes from copy import copy +from wsgiref.util import FileWrapper from django.conf import settings from django.contrib.auth.decorators import login_required, permission_required @@ -8,13 +10,13 @@ from django.contrib import messages from django.core.exceptions import PermissionDenied from django.core.files.storage import get_storage_class from django.db.models import Count, F, Q -from django.http import HttpResponseRedirect, Http404 +from django.http import HttpResponseRedirect, Http404, StreamingHttpResponse from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.utils.decorators import method_decorator from django.utils.text import mark_safe from django.utils.translation import ugettext_lazy as _ -from django.views.generic import DetailView, FormView, ListView, UpdateView, DeleteView, RedirectView +from django.views.generic import DetailView, FormView, ListView, UpdateView, DeleteView, View from django_filters.views import FilterView from django_tables2.views import SingleTableMixin @@ -844,15 +846,32 @@ class SubmissionDeleteView(DeleteView): return response -class SubmissionPrivateMediaRedirectView(UserPassesTestMixin, RedirectView): +class SubmissionPrivateMediaView(UserPassesTestMixin, View): - def get_redirect_url(self, *args, **kwargs): + def get(self, *args, **kwargs): submission_id = kwargs['submission_id'] field_id = kwargs['field_id'] file_name = kwargs['file_name'] file_name_with_path = f'submission/{submission_id}/{field_id}/{file_name}' - return submission_storage.url(file_name_with_path) + submission_file = submission_storage.open(file_name_with_path) + wrapper = FileWrapper(submission_file) + encoding_map = { + 'bzip2': 'application/x-bzip', + 'gzip': 'application/gzip', + 'xz': 'application/x-xz', + } + content_type, encoding = mimetypes.guess_type(file_name) + # Encoding isn't set to prevent browsers from automatically uncompressing files. + content_type = encoding_map.get(encoding, content_type) + content_type = content_type or 'application/octet-stream' + # From Django 2.1, we can use FileResponse instead of StreamingHttpResponse + response = StreamingHttpResponse(wrapper, content_type=content_type) + + response['Content-Disposition'] = f'filename={file_name}' + response['Content-Length'] = submission_file.size + + return response def test_func(self): submission_id = self.kwargs['submission_id'] diff --git a/opentech/storage_backends.py b/opentech/storage_backends.py index 3a347b4cbf1c2194e2e77dc7fbac240935eb6e62..e7f08555901397cda8232239a2bed3d01b289432 100644 --- a/opentech/storage_backends.py +++ b/opentech/storage_backends.py @@ -36,7 +36,7 @@ class PrivateMediaStorage(S3Boto3Storage): try: name_parts = name.split('/') return reverse( - 'apply:submissions:private_media_redirect', kwargs={ + 'apply:submissions:serve_private_media', kwargs={ 'submission_id': name_parts[1], 'field_id': name_parts[2], 'file_name': name_parts[3] }