blueprints.services.uploads — validación compartida de subida de imágenes
Descripción
blueprints.services.uploads es el módulo de validación compartida para subida de imágenes en blueprints, introducido en la cola B de la auditoría del dominio (task #286, s300, PR#493) para unificar reglas que antes vivían duplicadas y desalineadas en dos sitios: el endpoint de fondo de plano (/bg) y Auto-Plan.
Antes de este cambio, /bg (update_blueprint_bg) aceptaba cualquier fichero sin comprobar extensión, tipo real ni tamaño, mientras Auto-Plan sí validaba. Ahora ambos caminos llaman a las mismas funciones.
Módulo
blueprints/services/uploads.py
No es una clase: son funciones sueltas (74 LOC).
Funciones públicas
| Función | Propósito | Entrada | Salida |
|---|---|---|---|
image_max_bytes | Techo de tamaño configurable (settings.BLUEPRINT_IMAGE_MAX_BYTES, default 25 MB) | — | int |
validate_image_upload | Valida extensión + tamaño ANTES de escribir nada | upload (fichero subido) | extensión en minúsculas (str) — o ValueError |
org_scoped_filename | Genera la ruta relativa <org_id>/<prefix>_<slug>_<ts>.<ext> | org_id, prefix, nombre original, ext | str (ruta relativa) |
write_upload | Escribe el fichero en MEDIA_ROOT/uploads/blueprints/<rel_path>, creando la carpeta de la org | upload, rel_path | str (ruta absoluta) |
ensure_real_image | Comprueba que el contenido es una imagen real y no una bomba de descompresión (reutiliza AutoPlanService.get_image_scaling); si falla, BORRA el fichero recién escrito | ruta absoluta | None — o ValueError |
Flujo de uso (dos consumidores)
1. validate_image_upload(file) # extensión + tamaño, sin tocar disco
2. org_scoped_filename(org.id, ...) # nombre saneado, carpeta por org
3. write_upload(file, filename) # escribe a disco
4. ensure_real_image(abs_path) # imagen real + anti-bomba; borra si falla
Consumidores:
blueprints/api/blueprints.py→update_blueprint_bg(endpointPOST /api/blueprints/{bp_id}/bg, fondo de plano)blueprints/api/autoplan.py→magic_import_blueprint(endpointPOST /api/blueprints/autoplan/import, Auto-Plan)
Por qué existe
La auditoría del dominio blueprints (cola B, task #286) encontró que /bg no validaba nada — cualquier fichero se escribía a disco sin comprobar tipo, tamaño ni contenido real — mientras Auto-Plan sí lo hacía, con su propia copia de las reglas. ensure_real_image reutiliza la guarda anti decompression-bomb que ya existía en AutoPlanService.get_image_scaling (ver [[entity—blueprints—service—autoplan]]), y borra el fichero si la comprobación falla para no dejar basura huérfana en disco.
Dependencias
django.conf.settings—MEDIA_ROOT,BLUEPRINT_IMAGE_MAX_BYTESdjango.utils.text.slugify— nombre de fichero saneadoblueprints.services.autoplan.AutoPlanService.get_image_scaling— import diferido dentro deensure_real_image(evita import circular)
Notas de arquitectura
- Import diferido:
ensure_real_imageimportaAutoPlanServicedentro de la función, no a nivel de módulo, para evitar el ciclouploads.py → autoplan.py → uploads.py(Auto-Plan también usauploads.py). - Orden de validación: extensión/tamaño ANTES de escribir (barato, rechaza sin tocar disco); imagen real/anti-bomba DESPUÉS de escribir (necesita el fichero en disco para abrirlo con Pillow) — si falla, se borra.
- Carpeta por organización (hereda el fix de task #252):
org_scoped_filenameevita que dos organizaciones con el mismo nombre de fichero se pisen, y acota el borrado al espacio de la propia org.
Véase también
- [[entity—blueprints—service—autoplan]]
- [[entity—blueprints—endpoint—autoplan-import]]
- [[entity—blueprints—service—run-autoplan-import]]
- [[entity—blueprints—model—blueprint]]
- [[feature—blueprints—autoplan-async]]
- [[decision—20260403—multi-tenancy-rls]]