- MetadataFieldFile.__str__: return original_filename from metadata so
template links show the user-supplied name instead of the UUID path
- MetadataFileField.pre_save: capture original_filename from
file_before.name (FieldFile attribute set to the upload name by
FileDescriptor) instead of file_before.file.name (TemporaryUploadedFile
temp-file path), which was producing wrong filenames
- serve_file: add settings.DEBUG branch that streams bytes via
FileResponse so the dev server works; production path unchanged
(X-Accel-Redirect)
- serve_image: same DEBUG fallback via FileResponse
- norma/normajuridica_detail.html: replace hardcoded /media/{{field}}
href with {{p.anexo_arquivo.url}} so annexed-file links work now
that /media/ is nginx-internal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Launches the management command in the background immediately after
migrations so FileMetadata rows are populated for pre-existing files
without delaying pod readiness. A comment marks the line for removal
once the fleet is fully backfilled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IMAGE_FIELDS allowlist and serve_image view: cover CasaLegislativa.logotipo,
Partido.logo_partido, Parlamentar.fotografia, Dispositivo.imagem. The view
validates the (app, model, field) triple, fetches the instance, and issues
X-Accel-Redirect to nginx — same mechanism as serve_file but without
FileMetadata involvement (images carry no versioning or access-control
requirement).
- nginx: /media/CACHE/ added as a public exception before the internal
/media/ block so sorl-thumbnail cached thumbnails (fotografia via
{% cropped_thumbnail %}) remain accessible to browsers without going through
Django.
- get_logotipo_url helper in sapl/utils.py: returns the semantic
/imagens/base/casalegislativa/<pk>/logotipo/ URL; avoids circular imports
since sapl/base/views.py imports from sapl/relatorios/views.py.
- LogotipoView updated to redirect to the semantic URL instead of the raw
/media/ path.
- parliament_info context processor: adds logotipo_url to every template
context so base.html and 404.html can render the logo without MEDIA_URL
concatenation.
- 5 HTML templates updated: {% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}
replaced with {% if logotipo_url %}{{ logotipo_url }}.
- relatorios/views.py (4 sites): logotipo_url added to header_context dicts
passed to header_ata.html.
- painel/views.py: brasao computed via get_logotipo_url instead of
casa.logotipo.url (which returns the now-internal /media/ path).
- ImageThumbnailFileInput.get_context: computes semantic_url from IMAGE_FIELDS
when the instance has a PK; image_thumbnail.html uses it as the src fallback
so the edit-form preview remains visible after /media/ became internal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MetadataFileField.generate_filename: substitutes a UUID for the original
filename in the upload_to path so new files land at a stable, unguessable
storage path (e.g. sapl/public/norma/2025/9395/<uuid>.pdf). For
replacements the existing UUID is reused; OverwriteStorage replaces bytes
in-place and the public /documentos/<uuid>/ URL never changes. A fresh UUID
is stashed on the instance (_pending_uuid_<field>) and wired into the new
FileMetadata row in pre_save so FileMetadata.uuid always matches the path.
- pre_save Case 2 fix: skip explicit default_storage.delete() when old and new
storage paths are identical (UUID-based replacement) to avoid deleting the
freshly written file that OverwriteStorage already placed at that path.
- MetadataFileFieldSerializer: overrides to_representation only (inherits
DRF FileField for writes) to emit /documentos/<uuid>/ for API responses
instead of the semantic alias .url() returns. SaplSerializerMixin wires it
in via build_standard_field while preserving all normal field kwargs.
- nginx sapl.conf: adds `internal` and `etag on` to /media/ so clients can no
longer fetch files directly; only Django's X-Accel-Redirect reaches the
location.
- settings.py: adds ConditionalGetMiddleware after CommonMiddleware to set
ETag and Last-Modified on Django responses and short-circuit with 304.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates FileMetadata rows for all existing uploaded files that have a non-empty
file field but no _metadata FK yet. Idempotent and safe to interrupt.
Features:
--dry-run report without writing
--batch-size rows per batch (default 500)
--rate-limit max rows/s for NFS-constrained deployments
--skip-hash stat-only (skip SHA-256 for very large instances)
--app / --model restrict to a single model
For each qualifying row it:
1. reads storage_name from the DB field value (relative path, unchanged)
2. derives original_filename from the path's basename
3. stat()s the file for file_size_bytes (fast, ~0.3 ms/file)
4. optionally SHA-256 hashes it for content_hash (throughput-bound)
5. creates a FileMetadata row and sets the _metadata FK
Missing files are warned and counted but do not abort the run, which is
expected on dev DBs and documents RFC §7 "broken reference detection".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the URL decoupling layer described in the RFC:
sapl/base/views.py
serve_file(request, file_uuid)
Resolves UUID → FileMetadata → storage_name; issues X-Accel-Redirect
to nginx with dual-param Content-Disposition (ASCII + UTF-8 encoded).
Currently passes all files unconditionally; the permission-check hook
is in place for when DocumentoAdministrativo.nivel_restricao is wired.
serve_model_file(request, app_label, model_name, pk, field_name)
Semantic alias handler: validates (app, model, field) against an explicit
SERVE_FILE_FIELDS allowlist, fetches the parent instance, resolves the
_metadata FK, then delegates to serve_file. Allowlist prevents the
generic URL from accidentally exposing arbitrary model fields.
sapl/base/fields.py
MetadataFieldFile(FieldFile) — overrides .url:
• saved instance → /<app>/<model>/<pk>/<field>/download (semantic alias)
• unsaved instance → /documentos/<uuid>/ (canonical fallback)
• no metadata row → raw storage URL (pre-backfill fallback, nothing breaks)
MetadataFileField.attr_class = MetadataFieldFile
sapl/urls.py
path('documentos/<uuid:file_uuid>/', serve_file, name='serve_file')
path('<app>/<model>/<pk>/<field>/download', serve_model_file, ...)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the foundational infrastructure for the file-serving redesign described
in docs/rfc/files-metafields.md.
What changed:
sapl/base/models.py
- Add FileMetadata model (db_table=base_file_metadata): stable uuid,
storage_name, original_filename, file_size_bytes, content_hash, version,
backfilled_at. The uuid is the stable public identifier that survives
file replacements; storage_name is the storage-backend key (relative path
or S3 object key).
sapl/base/fields.py (new)
- MetadataFileField: FileField subclass that injects a companion FK
(<fieldname>_metadata → base.FileMetadata) via contribute_to_class and
manages the FileMetadata row lifecycle in pre_save (create on first upload,
update-in-place on replacement keeping uuid stable, nullify+delete on
clear, no-op on re-save).
sapl/utils.py
- fabrica_validador_de_tipos_de_arquivo: also set __qualname__ = nome so
Django 2.2's migration serializer (which checks __qualname__ for '<locals>')
can resolve the function as sapl.utils.<name>.
14 models across 5 apps swapped FileField → MetadataFileField:
norma: NormaJuridica.texto_integral, AnexoNormaJuridica.anexo_arquivo
materia: MateriaLegislativa.texto_original, DocumentoAcessorio.arquivo,
Proposicao.texto_original
sessao: SessaoPlenaria.upload_{pauta,ata,anexo}, AbstractOrador.upload_anexo
(→ Orador, OradorExpediente, OradorOrdemDia), JustificativaAusencia
comissoes: Reuniao.upload_{pauta,ata,anexo}, DocumentoAcessorio.arquivo
audiencia: AudienciaPublica.upload_{pauta,ata,anexo},
AnexoAudienciaPublica.arquivo
protocoloadm: DocumentoAdministrativo.texto_integral,
DocumentoAcessorioAdministrativo.arquivo
6 migrations adding <fieldname>_metadata FK columns (AddField, pure SQL;
no existing rows touched).
sapl/api/views_base.py
- Remove FileMetadata from the auto-built API set after build_class to
prevent UUID enumeration and storage_name leakage (RFC §11.3).
Not yet implemented (follow-up commits):
- serve_file / serve_model_file views (RFC §6.4, §9)
- MetadataFileField.url() returning semantic aliases (RFC §9)
- MetadataOverwriteStorage with UUID-based paths (RFC §6.3)
- backfill_file_metadata management command (RFC §8)
- SaplSerializerMixin integration for API canonical URLs (RFC §10)
- nginx /media/ internal + ConditionalGetMiddleware (RFC §6.4)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refact: cria método get_proximo_numero
* feat: impl numeração automática em cadastros via API
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/api/serializers.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/api/views_materia.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update sapl/materia/models.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Add transaction atomic no endpoint create
* add validação de tipo se tipo não é objeto do model TipoMateriaLegislativa
* refact: aplica solicitações de reviewer e cria testes
* fix: altera numero_preferido para numero_candidato
* fix: remove espaços entre classes
* fix: corrige uso de transaction e ausencia dele
* fix: corrige testes devido mudança de norme de variável
* fix: altera seleção para select_for_update
* fix: mudança de nome remanescente para numero_candidato
* fix: retorna decorator na view function recuperar_materia
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: impl HEADER LastModifiedDecorator na api
* fix: ajuste no frontend para evitar cache automático devido a LastModified sem tratamento adequado
* rebuild frontend
* fix: corrige last_modified_func para retorna sob retrieve
* refact: impl LastModified diretamente nos models que possuem campos específicos
* Seleciona tipo de votação para múltiplas matérias ao incluir na Ordem do Dia ou Expediente
* Ajustes solicitados na seleção de múltiplas Matérias para Ordem/Expediente
* Update adicionar_varias_materias_expediente.html
* Fix read-only mount on k8s
* Fix recibo proposição e adiciona rate limiter em matéria e norma
* Update forms.py
Alteração do nome do campo todos, conforme solicitação.
* Update adicionar_varias_materias_expediente.html
Ajuste no template por alteração do nome do campo "todos".
* Fix recibo proposição e adiciona rate limiter em matéria e norma
---------
Co-authored-by: root <root@info38.camaranh>
Co-authored-by: Edward Oliveira <edwardr@senado.gov.br>
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
* Fix recibo proposição e adiciona rate limiter em matéria e norma
* Conserta bug na pesquisa do Relatório de Votações Nominais
Na pesquisa, ao selecionar filtro por Tipo de Matéria, Número e/ou Ano da Matéria, o sistema retorna erro 500.
* Fix recibo proposição e adiciona rate limiter em matéria e norma
---------
Co-authored-by: Edward Oliveira <edwardr@senado.gov.br>
* feat: impl filtro m2m com lookup '__in' para buscas com multiplos ids
* fix: remove alteração na criação de rotas da api
A classe DrfautoapiRouter removida nesse commit bem como a alteração no classmethod router é uma aleração necessária para que a api rode no django 5.2. Por outro lado, esta alteração quebra os links gerados pela template tag url e exigiria refatoração das mesmas. Esta alteração não é necessária para o propósito do PR.
* Implementa Relatório de Votações Nominais
* Alterações solicitadas - Relatório de Votações Nominais
* Apply suggestions from code review
Todas as sugestões de alteração acatadas.
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>
* Update views.py
Conforme observação sobre o retorno da QuerySet, escolha da opção 2 - colocar o qs dentro dos if's. Também houve a alteração na view, utilizando diretamente a classe genérica MultiFormatOutputMixin. Com a refatoração efetuada, não foi necessário definir uma especificação da mesma.
---------
Co-authored-by: root <root@info38.camaranh>
Co-authored-by: Edward <9326037+edwardoliveira@users.noreply.github.com>