Source code for ferrosoft.apps.ferrobase.views.media
# Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
from pathlib import Path
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, FileResponse
from django.utils.http import content_disposition_header
from servestatic.utils import AsyncFile, AsyncFileIterator
from ferrosoft.apps.ferrobase.models import StoredFile, LocalUser
from ferrosoft.config import require_settings
FORCE_DOWNLOAD_PARAM = "dl"
"""Query parameter (``?dl=1``) that forces a media file to download as an attachment."""
[docs]
class CustomFileResponse(FileResponse):
"""FileResponse subclass that sets ``Content-Disposition`` from an async iterator."""
def _set_streaming_content(self, value):
"""Apply ``content_disposition_header`` before deferring to the base class."""
if content_disposition := content_disposition_header(
self.as_attachment, self.filename
):
self.headers["Content-Disposition"] = content_disposition
self.file_to_stream = None
return super()._set_streaming_content(value)
[docs]
@login_required
def restrict_media(request, path):
"""Authenticated media-serving endpoint with per-file access control.
Looks up the :class:`StoredFile` for ``path`` via
:meth:`StoredFile.get_authenticated_file` (returns ``None`` when the
current user has no access). The file is streamed asynchronously via
:class:`servestatic.utils.AsyncFile`; ``?dl=1`` forces a download.
"""
file = StoredFile.get_authenticated_file(path, LocalUser.load(request.user))
if file is None:
return HttpResponse(status=401)
settings = require_settings("MEDIA_ROOT")
full_path = Path(settings["MEDIA_ROOT"]) / path
kwargs = {"content_type": file.media_type}
as_attachment = request.GET.get(FORCE_DOWNLOAD_PARAM) == "1"
if as_attachment:
kwargs.update({"as_attachment": True, "filename": file.file_name})
file_iter = AsyncFileIterator(AsyncFile(full_path, mode="rb"))
return CustomFileResponse(file_iter, **kwargs)