From d08bba645202fd3c28480fd4bb58934d861b97a6 Mon Sep 17 00:00:00 2001 From: Stephan Jarisch Date: Thu, 5 Sep 2024 16:16:39 +0200 Subject: [PATCH] SIPWU-1189: Fix 500 Error if file does not exist on server --- drf_attachments/admin.py | 10 ++++++++++ drf_attachments/models/models.py | 12 ++++-------- drf_attachments/rest/views.py | 27 +++++++++++++++++++-------- drf_attachments/storage.py | 5 +++-- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/drf_attachments/admin.py b/drf_attachments/admin.py index a88b201..a1b409f 100644 --- a/drf_attachments/admin.py +++ b/drf_attachments/admin.py @@ -64,6 +64,16 @@ class AttachmentAdmin(admin.ModelAdmin, AttachmentAdminMixin): "creation_date", ) + def get_object(self, request, object_id, from_field=None): + obj = super().get_object(request, object_id, from_field) + + # handling missing file (e.g. file may have been deleted on the server) + if not obj.file.storage.exists(obj.file.name): + self.message_user(request, "File not found", level="ERROR") + obj.file = None + + return obj + @staticmethod def content_object(obj): entity = obj.content_object diff --git a/drf_attachments/models/models.py b/drf_attachments/models/models.py index 95ceed6..436e915 100644 --- a/drf_attachments/models/models.py +++ b/drf_attachments/models/models.py @@ -104,11 +104,7 @@ def __str__(self): @property def is_image(self): - return ( - "mime_type" in self.meta - and self.meta["mime_type"] - and self.meta["mime_type"].startswith("image") - ) + return self.get_mime_type().startswith("image") @property def default_context(self): @@ -126,13 +122,13 @@ def is_modified(self): return self.creation_date != self.last_modification_date def get_extension(self): - return self.meta.get("extension") + return self.meta.get("extension", "unkown") def get_size(self): - return self.meta.get("size") + return self.meta.get("size", 0) def get_mime_type(self): - return self.meta.get("mime_type") + return self.meta.get("mime_type", "unkown") def save(self, *args, **kwargs): # set computed values for direct and API access diff --git a/drf_attachments/rest/views.py b/drf_attachments/rest/views.py index 57e8526..3deb2cc 100644 --- a/drf_attachments/rest/views.py +++ b/drf_attachments/rest/views.py @@ -1,5 +1,9 @@ -from django.http import FileResponse +from django.http import FileResponse, Http404 from django_filters.rest_framework import DjangoFilterBackend +from drf_attachments.storage import AttachmentFileStorage +from drf_attachments.models.models import Attachment +from drf_attachments.rest.renderers import FileDownloadRenderer +from drf_attachments.rest.serializers import AttachmentSerializer from rest_framework import viewsets from rest_framework.decorators import action, parser_classes from rest_framework.filters import SearchFilter @@ -8,10 +12,6 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import JSONRenderer -from drf_attachments.models.models import Attachment -from drf_attachments.rest.renderers import FileDownloadRenderer -from drf_attachments.rest.serializers import AttachmentSerializer - __all__ = [ "AttachmentViewSet", ] @@ -41,10 +41,16 @@ def get_storage_path(self): attachment = self.get_object() meta = getattr(attachment.content_object, "AttachmentMeta", None) storage_location = getattr(meta, "storage_location", None) - if storage_location: - return f"{storage_location}/{attachment.file.name}" + + # Get custom storage location + if meta and storage_location: + storage = AttachmentFileStorage(location=meta.storage_location) else: - return attachment.file.path + # Default storage value from FileField "file" + storage = attachment.file.storage + + # Return the file path using the appropriate storage system + return storage.path(attachment.file.name) @action( detail=True, @@ -62,6 +68,11 @@ def download(self, request, format=None, *args, **kwargs): else: download_file_name = f"attachment_{attachment.pk}{extension}" + # Check if file exists via storage due to custom storage locations + # without triggering SuspiciousFileOperation + if not attachment.file.storage.exists(attachment.file.name): + raise Http404() + return FileResponse( open(storage_path, "rb"), as_attachment=True, diff --git a/drf_attachments/storage.py b/drf_attachments/storage.py index 6783d69..43ca370 100644 --- a/drf_attachments/storage.py +++ b/drf_attachments/storage.py @@ -19,8 +19,9 @@ class AttachmentFileStorage(FileSystemStorage): Attachments are served with a dedicated API route instead """ - def __init__(self): - super().__init__(location=settings.PRIVATE_ROOT) + def __init__(self, *args, **kwargs): + kwargs.setdefault("location", settings.PRIVATE_ROOT) + super().__init__(*args, **kwargs) def url(self, name): from drf_attachments.models import Attachment