# common/private_files/views.py import os from django.apps import apps from django.http import FileResponse, Http404, HttpResponseBadRequest from django.contrib.auth.decorators import login_required from django.core import signing from django.core.signing import BadSignature, SignatureExpired from django.utils.encoding import smart_str from django.utils.translation import gettext as _ from django.conf import settings from .registry import get_guard _TOKEN_MAX_AGE = getattr(settings, "PRIVATE_FILES_TOKEN_MAX_AGE", 14400) @login_required def private_file_download(request): """ Accès générique: app=&model=&pk=&field=&token= """ app_label = request.GET.get("app") model_name = request.GET.get("model") pk = request.GET.get("pk") field_name = request.GET.get("field") token = request.GET.get("token") if not all([app_label, model_name, pk, field_name]): return HttpResponseBadRequest("Missing params") # Vérifie le token signé (facultatif si tu ne veux pas signer) if token: try: signing.loads(token, salt="private-files", max_age=_TOKEN_MAX_AGE) except (BadSignature, SignatureExpired): raise Http404(_("Lien expiré ou invalide")) # Récupère le modèle et l’objet try: model = apps.get_model(app_label, model_name) except LookupError: raise Http404(_("Modèle inconnu")) obj = model._default_manager.select_related().filter(pk=pk).first() if not obj: raise Http404(_("Objet introuvable")) # Récupère le guard guard = get_guard(app_label, model_name, field_name) if guard is None: # Par défaut, refuse si pas de guard enregistré (sécurité par défaut) raise Http404(_("Fichier non exposé")) # Vérif d’accès if not guard(obj, request.user): from django.core.exceptions import PermissionDenied raise PermissionDenied(_("Accès refusé")) # Ouvre le FileField f = getattr(obj, field_name, None) if not f or not getattr(f, "storage", None) or not f.name or not f.storage.exists(f.name): raise Http404(_("Fichier introuvable")) resp = FileResponse(f.open("rb")) resp["Content-Disposition"] = f'inline; filename="{smart_str(os.path.basename(f.name))}"' resp['X-Frame-Options'] = 'ALLOWALL' # Permet l’affichage dans un iframe return resp