diff --git a/sapl/base/fields.py b/sapl/base/fields.py index 6b9812ed0..cd1f57ad8 100644 --- a/sapl/base/fields.py +++ b/sapl/base/fields.py @@ -20,6 +20,18 @@ class MetadataFieldFile(FieldFile): must use (see RFC §10). """ + def __str__(self): + """Return the original filename for display (not the UUID storage path).""" + if not self: + return '' + meta_attr = f'{self.field.name}_metadata' + meta = getattr(self.instance, meta_attr, None) + if meta and meta.original_filename: + return meta.original_filename + # Fallback: basename of storage path (may be UUID for newly uploaded files + # whose metadata row hasn't been committed yet). + return Path(self.name).name if self.name else '' + @property def url(self): if not self: @@ -161,8 +173,11 @@ class MetadataFileField(models.FileField): is_clearing = not file_before and meta_before is not None # Capture browser-supplied filename before storage renames it to the UUID path. - if has_new_upload and hasattr(file_before, 'file'): - original_filename = Path(file_before.file.name).name + # file_before.name is set to the original upload name by FileDescriptor.__get__ + # when it wraps the UploadedFile — more reliable than file_before.file.name + # which for TemporaryUploadedFile is the NamedTemporaryFile path. + if has_new_upload: + original_filename = Path(file_before.name).name if file_before.name else '' else: original_filename = '' diff --git a/sapl/base/views.py b/sapl/base/views.py index ab9024515..d0f014b35 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -1575,9 +1575,11 @@ def pesquisa_textual(request): # File-serving views (RFC §6.4, §9) # --------------------------------------------------------------------------- +import os as _os # noqa: E402 + from urllib.parse import quote # noqa: E402 — kept near usage site -from django.http import HttpResponse # noqa: E402 +from django.http import FileResponse, HttpResponse # noqa: E402 SERVE_FILE_FIELDS = frozenset({ ('materia', 'materialegislativa', 'texto_original'), @@ -1623,7 +1625,18 @@ def serve_file(request, file_uuid): # When DocumentoAdministrativo.restrito / nivel_restricao is wired, # insert the per-file check here (RFC §6.4). - # Build the nginx internal redirect path. + display_name = meta.original_filename or Path(meta.storage_name).name + + if settings.DEBUG: + # runserver has no nginx: serve the bytes directly from the filesystem. + file_path = _os.path.join(settings.MEDIA_ROOT, meta.storage_name) + try: + fh = open(file_path, 'rb') + except OSError: + raise Http404 + return FileResponse(fh, as_attachment=False, filename=display_name) + + # Production: delegate byte transfer to nginx via X-Accel-Redirect. # storage_name is relative to MEDIA_ROOT (e.g. "sapl/public/norma/…/file.pdf"). internal_path = f'/media/{meta.storage_name}' @@ -1631,8 +1644,8 @@ def serve_file(request, file_uuid): response['X-Accel-Redirect'] = internal_path # RFC 6266 — dual filename parameter: ASCII fallback + UTF-8 encoded. - filename_ascii = meta.original_filename.encode('ascii', 'replace').decode() - filename_encoded = quote(meta.original_filename, safe='') + filename_ascii = display_name.encode('ascii', 'replace').decode() + filename_encoded = quote(display_name, safe='') response['Content-Disposition'] = ( f'inline; filename="{filename_ascii}"' f"; filename*=UTF-8''{filename_encoded}" @@ -1699,6 +1712,15 @@ def serve_image(request, app_label, model_name, pk, field_name): if not field_file: raise Http404 + if settings.DEBUG: + import os as _os + file_path = _os.path.join(settings.MEDIA_ROOT, field_file.name) + try: + fh = open(file_path, 'rb') + except OSError: + raise Http404 + return FileResponse(fh) + response = HttpResponse() response['X-Accel-Redirect'] = f'/media/{field_file.name}' return response diff --git a/sapl/templates/norma/normajuridica_detail.html b/sapl/templates/norma/normajuridica_detail.html index 03636266c..710c1956d 100644 --- a/sapl/templates/norma/normajuridica_detail.html +++ b/sapl/templates/norma/normajuridica_detail.html @@ -78,8 +78,8 @@ {% if object.get_anexos_norma_juridica|length > 0 %} {% for p in object.get_anexos_norma_juridica %}
- - {{ p.anexo_arquivo | to_str | split:"/" | get_last_item_from_list:-1 }} + + {{ p.anexo_arquivo }}
{% endfor %}