Aller au contenu

Les images et médias dans Forge (forge-mvc-images)

Ce document explique ce que fait l'opt-in forge-mvc-images, ce qu'il expose, et comment on s'en sert.

forge-mvc-images est l'unique propriétaire de tout l'image dans Forge : le traitement (Pillow) et la couche médias applicative (associer des images à des entités, galerie, couverture).

Il s'appuie sur forge-mvc-files pour l'écriture disque et le service de fichiers.

1. Rôle du module

L'opt-in couvre deux niveaux complémentaires :

  • traitement d'image : vérifier qu'un upload est une vraie image, l'écrire, générer des variantes (miniature, medium) ;
  • couche médias applicative : relier une image à une entité (un article, un élève), lister une galerie, désigner une couverture.

La vérification du contenu est une sécurité : on confirme que les octets sont bien une image avant toute écriture (garde anti-bombe de décompression), car le type MIME annoncé est falsifiable.

2. Vue d'ensemble rapide

Élément Valeur
Paquet forge-mvc-images
Module forge_mvc_images
Catégorie Médias et fichiers (ADR-055)
Couche opt-in (brique optionnelle)
Dépend de forge-mvc, forge-mvc-files, Pillow
Traitement save_image_upload, save_image, generate_image_variants, verify_image_content
Couche médias create_media_record, attach_media_to_entity, list_media_for_entity, get_media_gallery, get_cover_media
Objet renvoyé MediaRecord (association fichier média / entité)
Variantes IMAGE_VARIANT_SIZES (miniature, medium)
Formats autorisés ALLOWED_IMAGE_EXTENSIONS, ALLOWED_IMAGE_MIME_TYPES
Décision d'architecture ADR-018 (remplace forge-mvc-media)
Installation pip install --pre forge-mvc-images

3. Schémas UML

Les deux schémas suivants montrent deux vues complémentaires de l'opt-in.

Le diagramme de classe montre les deux couches et leurs dépendances.

Le diagramme de séquence montre l'enregistrement d'une image puis l'affichage d'une galerie.

3.1 Diagramme de classe

Le diagramme de classe montre que le traitement s'appuie sur forge-mvc-files et Pillow, et que la couche médias persiste des associations via un exécuteur injecté.

classDiagram
    direction LR

    class processing {
        <<module>>
        +save_image_upload(file, category, variants) SavedUpload
        +save_image(file, entity_name, entity_id, ...) MediaRecord
        +generate_image_variants(path, root) dict
        +verify_image_content(data) None
    }

    class media {
        <<module>>
        +create_media_record(entity_name, entity_id, path, ...) int
        +attach_media_to_entity(saved_upload, entity_name, entity_id, ...) int
        +list_media_for_entity(entity_name, entity_id, role) list
        +get_media_gallery(...) list
        +get_cover_media(...) dict
    }

    class MediaRecord {
        <<dataclass>>
        +str filename
        +str path
        +str category
    }

    class files {
        <<opt-in>>
        +save_upload()
        +serve_media_file()
    }

    class Pillow {
        <<dependance>>
    }

    processing --> files : écrit via
    processing --> Pillow : vérifie / redimensionne
    processing --> MediaRecord : renvoie
    media --> files : chemins / service
    processing ..> media : alimente

À retenir :

  • la couche traitement produit des fichiers (original + variantes) ;
  • la couche médias relie ces fichiers à des entités ;
  • Pillow sert à vérifier et redimensionner, pas le cœur ;
  • l'écriture et le service passent par forge-mvc-files.

3.2 Diagramme de séquence

Le diagramme de séquence montre un upload d'image relié à une entité, puis l'affichage de sa galerie.

sequenceDiagram
    actor Navigateur
    participant Ctrl as Contrôleur
    participant Img as forge_mvc_images
    participant Files as forge_mvc_files
    participant DB as Exécuteur BDD

    Navigateur->>Ctrl: POST image (multipart)
    Ctrl->>Img: save_image_upload(file)
    Img->>Img: verify_image_content (anti-bombe)
    Img->>Files: save_upload (écrit l'original)
    Img->>Img: generate_image_variants (miniature, medium)
    Img-->>Ctrl: SavedUpload
    Ctrl->>Img: attach_media_to_entity(saved, "article", 7)
    Img->>DB: insère l'association
    Navigateur->>Ctrl: GET page article
    Ctrl->>Img: get_media_gallery("article", 7)
    Img->>DB: lit les médias liés
    Img-->>Ctrl: galerie (URLs via media_url)

À retenir :

  • le contenu est vérifié avant l'écriture disque ;
  • les variantes sont générées à l'enregistrement ;
  • l'association média / entité est persistée par la couche médias ;
  • la galerie et la couverture se lisent par entité.

4. API publique

Traitement d'image

Élément Signature Rôle
save_image_upload save_image_upload(file, category="images", *, variants=True) -> SavedUpload vérifie, écrit, génère les variantes
save_image save_image(file, *, category="images", entity_name=None, entity_id=None, usage="main", position=0, is_main=True) -> MediaRecord enregistre une image liée à une entité
generate_image_variants generate_image_variants(path, *, root=None) -> dict[str, str] génère miniature et medium
verify_image_content verify_image_content(data) -> None garde anti-bombe, lève si non-image
constantes ALLOWED_IMAGE_EXTENSIONS, ALLOWED_IMAGE_MIME_TYPES, IMAGE_VARIANT_SIZES formats et tailles

Couche médias applicative

Élément Signature Rôle
create_media_record create_media_record(entity_name, entity_id, path, ..., db=None) -> int enregistre une association média / entité
attach_media_to_entity attach_media_to_entity(saved_upload, entity_name, entity_id, ..., db=None) -> int relie un SavedUpload à une entité
list_media_for_entity list_media_for_entity(entity_name, entity_id, role=None, ...) -> list liste les médias d'une entité
get_media_gallery get_media_gallery(...) -> list[dict] galerie d'une entité (avec URLs)
get_cover_media get_cover_media(...) -> dict \| None média de couverture
media_url media_url(path) -> str URL publique d'un média
autres update_media_alt_text, update_media_position, delete_media, get_media_record gestion fine

5. Contextes d'utilisation

Besoin Élément
Recevoir une image en sécurité save_image_upload(file)
Vérifier des octets image verify_image_content(data)
Générer miniature et medium generate_image_variants(path)
Relier une image à une entité attach_media_to_entity(...)
Afficher une galerie get_media_gallery(...)
Choisir une couverture get_cover_media(...)
Construire l'URL d'un média media_url(path)

6. Exemples d'utilisation

6.1 Recevoir une image et la relier à une entité

from forge_mvc_images import save_image_upload, attach_media_to_entity


def add_photo(request):
    saved = save_image_upload(request.file("photo"))
    attach_media_to_entity(saved, "article", 7, role="gallery")
    return Response.text("Photo ajoutée.")

save_image_upload vérifie le contenu, écrit l'original et génère les variantes.

6.2 Afficher la galerie d'une entité

from forge_mvc_images import get_media_gallery, get_cover_media

gallery = get_media_gallery("article", 7)
cover = get_cover_media("article", 7)

Aide-mémoire

Deux couches, un même flux :

  • traitement : save_image_upload (vérifie, écrit, variantes) ;
  • médias : attach_media_to_entity, get_media_gallery, get_cover_media.

7. Sécurité, variantes et dépendances

verify_image_content confirme que les octets sont une vraie image avant toute écriture, et protège contre les bombes de décompression.

Les variantes (miniature, medium) sont définies par IMAGE_VARIANT_SIZES ; l'original est conservé tel quel.

Le MIME annoncé ne suffit pas

Un fichier non-image peut se présenter avec une extension et un Content-Type d'image.

save_image_upload (et save_image) appellent verify_image_content avant d'écrire : ne court-circuitez pas cette étape.

Pillow vit dans l'opt-in

Pillow est une dépendance de forge-mvc-images, retirée du cœur (ADR-018).

Le cœur ne sait pas traiter d'images ; tout l'image vit ici.

S'appuie sur forge-mvc-files

L'écriture disque, le service (HTTP Range) et l'anti-traversal viennent de forge-mvc-files.

forge-mvc-images ajoute le traitement (Pillow) et la couche applicative.

Voir aussi