feat: stockage des médias en PostgreSQL (alternative au S3)#458
feat: stockage des médias en PostgreSQL (alternative au S3)#458chaibax wants to merge 11 commits intonumerique-gouv:mainfrom
Conversation
Add a new `db_storage` app that provides a custom Django Storage backend storing files directly in PostgreSQL. This offers a persistent storage alternative for PaaS deployments (Scalingo, Heroku, etc.) where the filesystem is ephemeral and S3 is not available. Activated via SF_USE_DB_STORAGE=1. Priority: S3 > DB Storage > FileSystem. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds `migrate_s3_to_db` management command that: - Downloads all files from S3 bucket and stores them as StoredFile entries - Updates hardcoded S3 URLs in Wagtail Revision content_json - Scans URLField/CharField and RichTextField on all models for S3 URLs - Supports --dry-run, --skip-files, and --skip-urls options Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive documentation for the PostgreSQL media storage feature including activation guide, S3 migration steps, and architecture overview. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix line length violations (>119 chars) in test_migrate_s3.py by extracting S3 env dict to module constant. Remove unused imports and fix black formatting in migrate_s3_to_db.py. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| "forms", | ||
| "wagtail_honeypot", | ||
| "dashboard", | ||
| "db_storage", |
There was a problem hiding this comment.
Ce serait mieux je pense d'ajouter l'app uniquement si SF_USE_DB_STORAGE est à True
There was a problem hiding this comment.
Corrigé dans 5afda59 : l'app est maintenant ajoutée conditionnellement via if SF_USE_DB_STORAGE: INSTALLED_APPS.insert(-1, "db_storage"), comme c'est fait pour SF_USE_WHITENOISE.
| path("sitemap.xml", sitemap, name="xml_sitemap"), | ||
| path(settings.WAGTAILADMIN_PATH, include(wagtailadmin_urls)), | ||
| path("documents/", include(wagtaildocs_urls)), | ||
| path("db-storage/", include("db_storage.urls")), |
There was a problem hiding this comment.
Pareil ici, que l'ajout de l'URL soit dépendant de la variable d'environnement
There was a problem hiding this comment.
Corrigé dans le même commit : la route est conditionnée par if settings.SF_USE_DB_STORAGE, comme PROCONNECT_ACTIVATED pour les URLs oidc.
L'app db_storage et sa route /db-storage/ ne sont ajoutées que si SF_USE_DB_STORAGE=True, suivant le même pattern que SF_USE_WHITENOISE et PROCONNECT_ACTIVATED. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), | ||
| ), | ||
| ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ||
| ] + static(settings.MEDIA_URL, document_root=getattr(settings, "MEDIA_ROOT", "")) |
There was a problem hiding this comment.
A priori, pas besoin de changer ici car MEDIA_ROOT n'est utile que dans le cas d'utilisation d'un file system storage et il est déjà défini dans la conditionnelle à son utilisation dans config/settings.py
There was a problem hiding this comment.
Merci, j'ai enlevé
- Add noqa: F405 for INSTALLED_APPS references from star import - Revert getattr(settings, "MEDIA_ROOT", "") to settings.MEDIA_ROOT since Django provides a default empty string in global_settings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add warning that DB storage is not recommended beyond 1 GB of media in settings.py, .env.example, and docs/db-storage.md - Add migrate_db_to_s3 management command to transfer stored files from PostgreSQL back to an S3 bucket (with --dry-run support and skip-if-exists logic) - Add 5 tests for the new command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| # Allow storing media files in PostgreSQL instead of the filesystem (disabled by default) | ||
| # Useful for PaaS deployments with ephemeral filesystems (Scalingo, Heroku, etc.) | ||
| # /!\ Not recommended beyond 1 GB of media — prefer S3 for larger volumes. | ||
| # Priority: S3 > DB Storage > FileSystem |
There was a problem hiding this comment.
J'aurais dit le contraire.
| # Priority: S3 > DB Storage > FileSystem | |
| # Priority: FileSystem > S3 > DB Storage |
There was a problem hiding this comment.
Je suis d'accord, je n'avais pas vu
There was a problem hiding this comment.
Remplacé "Priority" par "Selection order: S3_HOST wins if set, then SF_USE_DB_STORAGE, then filesystem (default)" — distingue la précédence dans le code de la recommandation d'usage. Corrigé dans dddfba2.
| |----------|---------|----------------------|-------------| | ||
| | 1 | **S3** (Object Storage) | `S3_HOST` | Production avec stockage S3 compatible | | ||
| | 2 | **PostgreSQL** (DB Storage) | `SF_USE_DB_STORAGE=1` | PaaS sans S3, Docker, Plesk | | ||
| | 3 | **Système de fichiers** | _(par défaut)_ | Développement local | |
There was a problem hiding this comment.
Idem ci-dessus.
Dans les cas d'usage je rajouterais Hébergement sur VPS.
There was a problem hiding this comment.
Tableau inversé (filesystem en premier) et ajout de VPS + hébergement dédié dans les cas d'usage. Corrigé dans dddfba2.
Replace misleading "Priority" wording with "Selection order" to distinguish code precedence from recommendation. Add VPS to filesystem use cases in documentation table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| if "db_storage" not in INSTALLED_APPS: # noqa: F405 | ||
| INSTALLED_APPS.insert(-1, "db_storage") # noqa: F405 |
There was a problem hiding this comment.
pourquoi ajouter des noqa ?
|
|
||
| if settings.SF_USE_DB_STORAGE: | ||
| urlpatterns += [ | ||
| path("db-storage/", include("db_storage.urls")), |
There was a problem hiding this comment.
c'est généralement un anti pattern de servir les médias depuis une vue django, c'est assez bien expliqué dans la doc de whitenoise https://whitenoise.readthedocs.io/en/stable/django.html#serving-media-files
Ici ca va rendre compliqué de scaler car chaque chargement d'image va être géré par django et occuper le worker prévu pour servir des pages
| @@ -0,0 +1,43 @@ | |||
| # Generated by Django 6.0.3 on 2026-03-05 11:15 | |||
There was a problem hiding this comment.
ca me semble overkill de créer une app django dédiée à ca, ca pourrait vivre dans config je pense pour ne pas gener les réutilisations (actuelles ou futures) de sites conformes sous forme d'app django
| name = request.GET.get("name") | ||
| if not name: | ||
| return HttpResponseNotFound("File not found.") |
There was a problem hiding this comment.
ici je pense qu'on aurait intérêt à mieux utiliser le routeur django avec une valeur accessible directement en argument de la fonction : https://docs.djangoproject.com/fr/6.0/topics/http/urls/#example
fabienheureux
left a comment
There was a problem hiding this comment.
Je me suis permis une review car j'ai vu le sujet discuté sur Mattermost, ca me semble assez cavalier comme approche vu que des sites avec plusieurs centaines de pages risquent d'avoir de nombreuses images stockées, l'approche serait intéressante niveau simplicité d'hébergement pour quelques landing pages mais pas tellement pour des sites pouvant stocker plusieurs milliers d'images, car elle risque de présenter rapidement des problèmes de scalabilité
|
Merci pour ta review @fabienheureux . Je suis d'accord avec toi sur la scalabilité. C'est bien pour quelques landing pages que nous faisons ça. 30 petits sites (images/trafics etc) qui doivent migrer rapidement, et pour qui prendre un S3 est très compliqué administrativement . |
Wagtail 3+ renamed Revision.content_json (TextField) to Revision.content (JSONField). The migration script now serializes the JSON content to string for URL search/replace, then parses back. Adds test covering revision URL updates. Fixes numerique-gouv#482 (FieldError on Scalingo deployment) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contexte
Sur les plateformes PaaS (Scalingo, Heroku, Clever Cloud) ou les déploiements Docker, le système de fichiers est souvent éphémère : chaque redéploiement efface les fichiers uploadés. Cette PR ajoute une alternative au S3 : stocker les médias directement dans PostgreSQL.
Changements
db_storage: modèleStoredFile(BinaryField), backend Django Storage complet, vue de service des fichiers avec cache headersmigrate_s3_to_db: transfert des fichiers S3 vers la DB + mise à jour des URLs S3 dans les révisions Wagtail et les champs RichTextmigrate_files_to_db: migration du filesystem local vers la DBdocs/db-storage.mdActivation
Priorité des backends
S3_HOSTSF_USE_DB_STORAGE=1Test réel effectué
sf-no-code(187 fichiers, 5.4 MB)migrate_s3_to_db→ 189 fichiers transférés/db-storage/serve/Limitations documentées
Cache-Control: max-age=3600)Test plan
python manage.py test db_storage— 17/17 tests passent🤖 Generated with Claude Code