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

1import os 

2import re 

3from abc import ABC, abstractmethod 

4 

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 

11 

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 

19 

20 

21class MeshGetFile(ABC, LoginRequiredMixin, MeshObjectMixin, View): 

22 @abstractmethod 

23 def get_object(self, queryset=None) -> BaseFileWrapperModel: ... 

24 

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() 

30 

31 response = sendfile(request, instance.file.path) 

32 

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 

37 

38 resp_headers = response.headers 

39 content_disposition = resp_headers.get("Content-Disposition") 

40 if not content_disposition: 

41 return response 

42 

43 filename_match = re.search(r"(?P<filename>filename=\".+?\")", content_disposition) 

44 if not filename_match: 

45 return response 

46 

47 filename = filename_match.group("filename") 

48 resp_headers["Content-Disposition"] = content_disposition.replace( 

49 filename, f'filename="{instance.name}"' 

50 ) 

51 return response 

52 

53 

54class VersionMainFileView(MeshGetFile): 

55 def get_object(self, queryset=None): 

56 version = self.get_version(queryset) 

57 return version.main_file 

58 

59 

60class VersionAdditionalFileView(MeshGetFile): 

61 def get_object(self, queryset=None): 

62 version = self.get_version(queryset) 

63 additional_files = version.additional_files 

64 

65 file_name = decode_from_url(self.kwargs.get("file_identifier")) 

66 

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 ) 

74 

75 

76class ReviewAdditionalFileView(MeshGetFile): 

77 def get_object(self, queryset=None): 

78 review = self.get_review(queryset) 

79 additional_files = review.additional_files.all() 

80 

81 file_name = decode_from_url(self.kwargs.get("file_identifier")) 

82 

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 ) 

90 

91 

92class EditorialDecisionFileView(MeshGetFile): 

93 def get_object(self, queryset=None): 

94 decision = self.get_decision(queryset) 

95 additional_files = decision.additional_files.all() 

96 

97 file_name = decode_from_url(self.kwargs.get("file_identifier")) 

98 

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 ) 

106 

107 

108class ReviewFileAccessAPIView(ReviewAdditionalFileView): 

109 http_method_names = ["post"] 

110 

111 def post(self, request, *args, **kwargs): 

112 file = self.get_object() 

113 file.author_access = not file.author_access 

114 file.save() 

115 

116 return JsonResponse({})