diff --git a/hypha/apply/funds/views/__init__.py b/hypha/apply/funds/views/__init__.py index 16b86225cf..ac80b7bdbe 100644 --- a/hypha/apply/funds/views/__init__.py +++ b/hypha/apply/funds/views/__init__.py @@ -1,5 +1,6 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import UserPassesTestMixin +from django.http import Http404 from django.shortcuts import get_object_or_404, render from django.utils.decorators import method_decorator from django_filters.views import FilterView @@ -56,6 +57,13 @@ def get_media(self, *args, **kwargs): path_to_file = generate_private_file_path( self.submission.pk, field_id, file_name ) + try: + if not self.storage.exists(path_to_file): + raise Http404 + except Http404: + raise + except Exception as e: + raise Http404 from e return self.storage.open(path_to_file) def test_func(self): diff --git a/hypha/apply/funds/views/submission_edit.py b/hypha/apply/funds/views/submission_edit.py index 241b40c18e..e7a6a45485 100644 --- a/hypha/apply/funds/views/submission_edit.py +++ b/hypha/apply/funds/views/submission_edit.py @@ -248,13 +248,23 @@ def get_form_kwargs(self): def get_placeholder_file(self, initial_file): if not isinstance(initial_file, list): return PlaceholderUploadedFile( - initial_file.filename, size=initial_file.size, file_id=initial_file.name + initial_file.filename, + size=self._safe_file_size(initial_file), + file_id=initial_file.name, ) return [ - PlaceholderUploadedFile(f.filename, size=f.size, file_id=f.name) + PlaceholderUploadedFile( + f.filename, size=self._safe_file_size(f), file_id=f.name + ) for f in initial_file ] + def _safe_file_size(self, stream_file): + try: + return stream_file.storage.size(stream_file.name) + except Exception: + return 0 + def save_draft_and_refresh_page(self, form) -> HttpResponseRedirect: self.object.create_revision(draft=True, by=self.request.user) form.delete_temporary_files() diff --git a/hypha/apply/stream_forms/files.py b/hypha/apply/stream_forms/files.py index b68bd65a89..7214866252 100644 --- a/hypha/apply/stream_forms/files.py +++ b/hypha/apply/stream_forms/files.py @@ -82,15 +82,27 @@ def size(self): return self.file.size return self.storage.size(self.name) + def _check_exists(self): + """Fetch modification time once; caches both existence and the date.""" + if not hasattr(self, "_exists_cache"): + try: + self._modification_time_cache = self.storage.get_modified_time( + self.name + ).date() + self._exists_cache = True + except Exception: + self._modification_time_cache = "–" + self._exists_cache = False + @property - def modification_time(self): - # Wrap in a try for local developments where files might not always exist. - try: - modified_time = self.storage.get_modified_time(self.name).date() - except FileNotFoundError: - modified_time = "–" + def exists(self): + self._check_exists() + return self._exists_cache - return modified_time + @property + def modification_time(self): + self._check_exists() + return self._modification_time_cache def serialize(self): return { diff --git a/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html b/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html index d53fdc6dad..e8c0b271f4 100644 --- a/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html +++ b/hypha/apply/stream_forms/templates/stream_forms/includes/file_field.html @@ -1,21 +1,39 @@ {% load i18n heroicons apply_tags %} - - - {% heroicon_mini "document-text" class="inline align-text-bottom" aria_hidden=true %} - {{ file.filename|truncatechars_middle:45 }} - - {% trans "uploaded" %} - - {{ file.modification_time|date:"SHORT_DATE_FORMAT" }} +{% if file.exists %} + + + {% heroicon_mini "document-text" class="inline align-text-bottom" aria_hidden=true %} + {{ file.filename|truncatechars_middle:45 }} + + {% trans "uploaded" %} + + {{ file.modification_time|date:"SHORT_DATE_FORMAT" }} + - - - {% heroicon_mini "arrow-down" class="w-5 h-5" stoke_width=2 aria_hidden=true %} - - + + {% heroicon_mini "arrow-down" class="w-5 h-5" stoke_width=2 aria_hidden=true %} + + +{% else %} +
+ + {% heroicon_mini "document-text" class="inline align-text-bottom" aria_hidden=true %} + {{ file.filename|truncatechars_middle:45 }} + + + {% trans "Missing file" %} + + + + {% heroicon_mini "exclamation-circle" class="w-5 h-5 text-error" aria_hidden=true %} + +
+{% endif %}