Coverage for src / mesh / views / views_file.py: 39%
70 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-09 13:14 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-07-09 13:14 +0000
1import os
2import re
3from abc import ABC, abstractmethod
5from django.contrib.auth.mixins import LoginRequiredMixin
6from django.http import Http404, HttpRequest, JsonResponse
7from django.utils.translation import gettext as _
8from django.views.generic import View
9from django_sendfile import sendfile
10from ptf.url_utils import decode_from_url
12from mesh.models.orm.editorial_models import EditorialDecisionFile
13from mesh.models.orm.file_models import BaseFileWrapperModel
14from mesh.models.orm.review_models import ReviewAdditionalFile
15from mesh.models.orm.submission_models import (
16 SubmissionAdditionalFile,
17)
18from mesh.views.views_base import MeshObjectMixin
21class MeshGetFile(ABC, LoginRequiredMixin, MeshObjectMixin, View):
22 @abstractmethod
23 def get_object(self, queryset=None) -> BaseFileWrapperModel: ...
25 def get(self, request: HttpRequest, *args, **kwargs):
26 instance = self.get_object()
27 # Make sure the file exists
28 if not os.path.exists(instance.file.path):
29 raise FileNotFoundError()
31 response = sendfile(request, instance.file.path)
33 # Set the original name of the served file for display to the
34 # download client
35 if not hasattr(response, "headers"):
36 return response
38 resp_headers = response.headers
39 content_disposition = resp_headers.get("Content-Disposition")
40 if not content_disposition:
41 return response
43 filename_match = re.search(r"(?P<filename>filename=\".+?\")", content_disposition)
44 if not filename_match:
45 return response
47 filename = filename_match.group("filename")
48 resp_headers["Content-Disposition"] = content_disposition.replace(
49 filename, f'filename="{instance.name}"'
50 )
51 return response
54class VersionMainFileView(MeshGetFile):
55 def get_object(self, queryset=None):
56 version = self.get_version(queryset)
57 return version.main_file
60class VersionAdditionalFileView(MeshGetFile):
61 def get_object(self, queryset=None):
62 version = self.get_version(queryset)
63 additional_files = version.additional_files
65 file_name = decode_from_url(self.kwargs.get("file_identifier"))
67 try:
68 return next(file for file in additional_files if file.file.name == file_name)
69 except StopIteration:
70 raise Http404(
71 _("No %(verbose_name)s found matching the query")
72 % {"verbose_name": SubmissionAdditionalFile._meta.verbose_name}
73 )
76class ReviewAdditionalFileView(MeshGetFile):
77 def get_object(self, queryset=None):
78 review = self.get_review(queryset)
79 additional_files = review.additional_files.all()
81 file_name = decode_from_url(self.kwargs.get("file_identifier"))
83 try:
84 return next(file for file in additional_files if file.file.name == file_name)
85 except StopIteration:
86 raise Http404(
87 _("No %(verbose_name)s found matching the query")
88 % {"verbose_name": ReviewAdditionalFile._meta.verbose_name}
89 )
92class EditorialDecisionFileView(MeshGetFile):
93 def get_object(self, queryset=None):
94 decision = self.get_decision(queryset)
95 additional_files = decision.additional_files.all()
97 file_name = decode_from_url(self.kwargs.get("file_identifier"))
99 try:
100 return next(file for file in additional_files if file.file.name == file_name)
101 except StopIteration:
102 raise Http404(
103 _("No %(verbose_name)s found matching the query")
104 % {"verbose_name": EditorialDecisionFile._meta.verbose_name}
105 )
108class ReviewFileAccessAPIView(ReviewAdditionalFileView):
109 http_method_names = ["post"]
111 def post(self, request, *args, **kwargs):
112 file = self.get_object()
113 file.author_access = not file.author_access
114 file.save()
116 return JsonResponse({})