Améliorer la prise en compte de l'environnement dans les projets d'urbanisme.
Cette page concerne le code source du projet Envergo. Pour en savoir plus sur le projet lui-même, se référer au site Envergo.beta.gouv.fr.
Les outils principaux suivants sont utilisés :
- le framework Django
- le système de design de l'état français
- le projet Cookiecutter-Django pour l'initialisation du dépôt.
Cookiecutter-Django est un initialiseur de projet.
Par conséquent, on se référera à sa doc pour en savoir plus sur l'organisation du projet et les différents outils mis en place.
Pour développer en local, deux solutions :
1/ Créer un environnement local sur sa machine.
Il est recommandé de se baser sur la version docker.
Pour lancer l'environnement rapidement :
$ git clone … && cd envergo
$ touch .envRemplir le fichier .env avec les variables d'environnement pour travailler en local
DJANGO_SETTINGS_MODULE=config.settings.local
ENV_NAME=development
Créer et démarrer les conteneurs
$ docker compose build
$ docker compose upPour construire la base de données (dans un autre shell) :
$ docker compose run --rm django python manage.py migratePour avoir accès aux fichiers static depuis le serveur de debug :
$ npm ci
$ npm run build
$ docker compose run --rm django python manage.py collectstatic
Générer les traductions :
docker compose run --rm django python manage.py compilemessagesAjouter dans /etc/hosts les domaines utilisés pour Envergo (http://envergo.local:8000/) et le Guichet Unique de la Haie (http://haie.local:8000/).
<url du conteneur envergo_django> envergo.local haie.local
$ git clone … && cd envergo
$ touch .env
$ python -m venv .venv
$ source .venv/bin/activate
$ pip install pip-tools
$ pip-sync requirements/local.txtRemplir le fichier .env avec des valeurs appropriées.
Il est nécessaire d'avoir au préalable configuré un utilisateur dans postgres avec les droits de création de base et d'extension.
Dans les versions les plus récentes de postgis, il est nécessaire d'installer l'extension "raster".
Si, lors du docker compose up ci-dessus vous avez ce type d'erreur :
envergo_postgres | 2024-05-13 14:35:21.651 UTC [35] ERROR: type "raster" does not exist at character 118
Il vous faudra créer cette extension (dans un autre terminal, avec le docker compose up qui tourne en parallèle) :
$ docker compose run --rm postgres create_rasterPuis interrompre et relancer le docker compose up. Les migrations Django devraient alors s'exécuter sans erreur.
Pour que le navigateur puisse accéder à l'application en local, les domaines envergo.local et haie.local doivent être ajoutés au modèle Sites > Sites, par exemple via le shell de django.
from django.contrib.sites.models import Site
Site.objects.get_or_create(domain="envergo.local", name="Envergo local")
Site.objects.get_or_create(domain="haie.local", name="Haie local")Pour créer un nouveau compte, utiliser la page de création de compte de l'application. Récupérer le lien pour valider l'email dans les logs.
Pour créer un compte super utilisateur, utiliser la commande django django shell pour modifier un compte existant et lui donner les droits de super utilisateur. Envergo utilise un modèle custom pour la gestion des utilisateurs : envergo.users.models.User.
De nombreux outils sont mis en place pour garantir la qualité et l'homogénéité du code.
- pre-commit qui lance plusieurs outils de validation au moment du commit (cf. sa configuration)
- flake8 pour la validation du code python
- black pour l'auto-formattage du code python
- isort pour l'ordonnancement des imports python
- Djhtml pour l'indentation des templates
- detect-secrets pour éviter d'introduire des secrets dans la code base
Pour activer tout ça :
pre-commit installL'intégration continue est réalisée par des actions Github.
Le projet propose un fichier Editorconfig pour configurer globalement les éditeurs de code.
Pour VSCode, il est recommandé d'utiliser la configuration suivante.
Installer les extensions :
- EditorConfig pour vscode
- Flake8 pour le linting
- Black pour le formattage
- Isort pour l'organisation des imports
- Prettier pour le formattage du code css / sass
Voici un fichier settings.json à enregistres dans .vscode/settings.json pour configurer
correctement VSCode :
{
"[python]": {
"editor.autoIndent": "keep",
"editor.wrappingIndent": "same",
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"editor.rulers": [88]
},
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
},
"[django-html]": {
"editor.rulers": [120],
"editor.defaultFormatter": "monosans.djlint",
"editor.wordWrap": "wordWrapColumn"
},
"[css][scss][less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"isort.args": ["--profile", "black"],
"black-formatter.args": ["--line-length=88"],
"files.associations": {
"**/templates/*.html": "django-html",
"**/templates/*": "django-txt",
"**/requirements{/**,*}.{txt,in}": "pip-requirements"
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/**": true,
"**/.hg/store/**": true,
"**/staticfiles/**": true,
"**/.venv/**": true,
"**/*.pyc": true
},
"emmet.includeLanguages": {
"django-html": "html"
}
}Pour être certain de la présence de tous les outils configurés, il est recommandé de créer un environnement virtuel python, puis d'installer toutes les dépendances locales (cf. plus bas).
Les dépendances sont gérées avec pip-tools.
Pour installer une nouvelle dépendance, il faut éditer l'un des fichiers *.in présents dans le répertoire /requirements.
cd requirements
echo "<nomdupaquet>" >> local.in
./compile.sh
pip-sync local.txtPour mettre à jour l'image Docker, relancer build puis up.
Les tests sont écrits avec pytest. Tous les helpers de pytest-django sont disponibles.
Pour lancer les tests :
En local :
pytestVia Docker :
docker compose run --rm django pytestCette section recense les différents types de tests et les patterns à suivre pour en écrire de nouveaux. Chaque catégorie est accompagnée d'un exemple minimal.
- Les tests sont écrits avec pytest et factory_boy.
- Les
conftest.pydes répertoiresenvergo/moulinette/tests/etenvergo/evaluations/tests/fournissent une fixtureautousequi accorde l'accès à la base de données et crée un objetSite. Il n'est donc pas nécessaire d'ajouterpytestmark = pytest.mark.django_dbdans les fichiers de test de ces répertoires. - Les fixtures geodata (
france_map,loire_atlantique_map, etc.) sont ré-exportées par cesconftest.py— inutile de les importer depuisenvergo.geodata.conftest. - Les données de test doivent être créées via les factories de
envergo/moulinette/tests/factories.py(RegulationFactory,CriterionFactory,ConfigAmenagementFactory, etc.) ou les helpers partagés deenvergo/moulinette/tests/utils.py, jamais à la main avecModel.objects.create(...). Pour lesActionToTake, utiliserActionToTakeFactoryqui gère la coexistence avec les données de migration viadjango_get_or_create. - Quand plusieurs séries de tests cohérentes cohabitent dans un même fichier, il est possible d'utiliser des classes
class Test*qui servent de namespaces.
Utiliser make_amenagement_data() pour construire le dict de données et les helpers setup_* pour créer les régulations et critères. Les setup_* sont typiquement appelés dans une fixture autouse en haut du fichier de test, pas dans chaque test individuellement.
import pytest
from envergo.moulinette.models import MoulinetteAmenagement
from envergo.moulinette.tests.utils import make_amenagement_data, setup_loi_sur_leau
@pytest.fixture(autouse=True)
def loisurleau_criteria(france_map): # noqa
return setup_loi_sur_leau(france_map)
def test_3310_small_footprint_outside_wetlands():
moulinette = MoulinetteAmenagement(
make_amenagement_data(created_surface=50, final_surface=50)
)
moulinette.catalog["wetlands_within_25m"] = False
moulinette.evaluate()
assert moulinette.loi_sur_leau.zone_humide.result == "non_concerne"Helpers de setup disponibles dans envergo/moulinette/tests/utils.py :
setup_loi_sur_leau(activation_map)setup_natura2000(activation_map)setup_eval_env(activation_map)setup_ep(activation_map)setup_conditionnalite_pac(activation_map)
Coordonnées prédéfinies : COORDS_MOUAIS, COORDS_NANTES, COORDS_HERAULT, COORDS_NORMANDIE, COORDS_BIZOU.
Utiliser make_moulinette_haie_data() et make_hedge() pour construire les données. Comme pour les tests aménagement, les régulations et critères sont créés dans une fixture autouse.
import pytest
from envergo.moulinette.models import MoulinetteHaie
from envergo.moulinette.tests.factories import (
CriterionFactory,
DCConfigHaieFactory,
PerimeterFactory,
RegulationFactory,
)
from envergo.moulinette.tests.utils import (
COORDS_BIZOUS_EDGE,
COORDS_BIZOUS_INSIDE,
COORDS_BIZOUS_OUTSIDE,
make_moulinette_haie_data,
make_hedge,
)
@pytest.fixture(autouse=True)
def n2000_criteria(bizous_town_center): # noqa
regulation = RegulationFactory(regulation="natura2000_haie", has_perimeters=True)
perimeter = PerimeterFactory(
name="N2000 Bizous", activation_map=bizous_town_center, regulations=[regulation]
)
CriterionFactory(
regulation=regulation,
perimeter=perimeter,
evaluator="envergo.moulinette.regulations.natura2000_haie.Natura2000Haie",
activation_map=bizous_town_center,
activation_mode="hedges_intersection",
)
@pytest.mark.parametrize(
"coords, expected_result",
[
(COORDS_BIZOUS_INSIDE, "soumis"),
(COORDS_BIZOUS_EDGE, "soumis"),
(COORDS_BIZOUS_OUTSIDE, "non_concerne"),
],
)
def test_moulinette_evaluation(coords, expected_result):
DCConfigHaieFactory()
data = make_moulinette_haie_data(
hedge_data=[make_hedge(coords=coords)], reimplantation="replantation"
)
moulinette = MoulinetteHaie(data)
assert moulinette.natura2000_haie.result == expected_resultLes tests qui utilisent les URLs du site haie (i.e. qui font un reverse(), utilisent le client de test, ou dépendent du routage haie) doivent utiliser le marker @pytest.mark.haie. Ce marker configure automatiquement le ROOT_URLCONF, les settings de domaine et le nettoyage des caches URL.
Pour un fichier entier :
import pytest
from django.urls import reverse
pytestmark = pytest.mark.haie
def test_haie_triage_form(client):
url = reverse("triage")
res = client.get(url)
assert res.status_code == 200Pour un test isolé :
import pytest
@pytest.mark.haie
def test_haie_specific(client):
# ...Sans ce marker, les reverse() vers des URLs haie lèveront un NoReverseMatch.
Note : les fichiers dans envergo/moulinette/tests/ n'ont pas besoin de pytest.mark.django_db (le conftest.py du répertoire s'en charge). Les fichiers hors de ce répertoire doivent l'ajouter explicitement :
pytestmark = [pytest.mark.django_db, pytest.mark.haie]Le conftest.py dans envergo/evaluations/tests/ fournit une fixture moulinette_config qui crée un ConfigAmenagement avec des adresses email standard. La fixture ne retourne rien — elle crée les objets en base pour que le test puisse fonctionner. Demander cette fixture dans la signature du test suffit à déclencher le setup.
from django.urls import reverse
def test_eval_request_wizard_step_1(client, moulinette_config):
url = reverse("request_eval_wizard_step_1")
data = {"address": "42 rue du Test, 44000 Testville"}
res = client.post(url, data=data)
assert res.status_code == 302Les tests qui nécessitent un setup plus complet (régulations + critères) définissent leur propre fixture moulinette_config locale avec autouse=True qui étend le setup de base.
Boilerplate minimal pour un nouveau fichier dans envergo/moulinette/tests/ :
"""Tests for <feature>."""
import pytest
from envergo.moulinette.models import MoulinetteAmenagement
from envergo.moulinette.tests.utils import make_amenagement_data, setup_loi_sur_leau
@pytest.fixture(autouse=True)
def loisurleau_criteria(france_map): # noqa
return setup_loi_sur_leau(france_map)
def test_example():
moulinette = MoulinetteAmenagement(
make_amenagement_data(created_surface=200, final_surface=200)
)
moulinette.evaluate()
assert moulinette.loi_sur_leau.zone_humide.result == "non_concerne"Aucun import de conftest, aucun pytestmark, aucune fixture autouse_site locale nécessaire — l'accès à la base de données et l'objet Site sont gérés par le conftest.py du répertoire.
Les tests end-to-end sont écrits avec Playwright. Les tests E2E permet de valider que les chemins utilisateurs critiques (faire une simulation, demander un avis, répondre à une demande d'avis, etc) fonctionnent correctement. Cela permet en outre de vérifier le bon fonctionnement des composants JavaScript de plus en plus présent sur les pages et pour le moment non couvert par d'autre tests.
Ils se basent sur une base de données de tests dédiée contenant une jeu de données minimum présent dans ce fichier et que l’on peut remplir pour les besoins de chaque test.
Ils tournent dans la CI de Github.
Pour lancer les tests E2E en local, il faut avant tout créer une base de données dédiée similaire à celle qui sera utilisée par la CI. Pour cela, il faut lancer les commandes suivantes :
$ . envs/postgres
$ docker compose exec postgres bash -c 'dropdb --if-exists envergo-test -U "$POSTGRES_USER" -f'
$ docker compose exec postgres bash -c 'createdb envergo-test -U "$POSTGRES_USER" -O "$POSTGRES_USER"'
$ docker compose run -e POSTGRES_DB=envergo-test --rm django python manage.py migrate
$ docker compose run -e POSTGRES_DB=envergo-test --rm django python manage.py loaddata e2e/fixtures/db_seed.jsonEnsuite, installez les dépendances Node du projet (dont Playwright) et les navigateurs Playwright :
$ npm ci
$ npm run playwright:installVous devez tout d'abord lancer l'application en pointant vers la base de test, avec le bon fichier de settings et en définissant le site que vous souhaitez tester :
Pour aménagement :
$ POSTGRES_DB=envergo-test DJANGO_ENVERGO_AMENAGEMENT_DOMAIN=localhost docker compose -f docker-compose.yml -f docker-compose.e2e.yml up -dPour le GUH :
$ POSTGRES_DB=envergo-test DJANGO_ENVERGO_HAIE_DOMAIN=localhost docker compose -f docker-compose.yml -f docker-compose.e2e.yml up -dEnfin vous pouvez lancer les tests avec l'une des commandes suivantes :
Pour aménagement :
$ npm run e2e-amenagement:ui # pour lancer les tests dans un navigateur
$ npm run e2e-amenagement # pour lancer les tests dans un shellPour le GUH :
$ npm run e2e-haie:ui # pour lancer les tests dans un navigateur
$ npm run e2e-haie # pour lancer les tests dans un shellDeux possibilités existent pour la mise en disponibilité d'un environnement de recette :
- 1/ création d'une « review app » manuellement via scalingo ;
- 2/ utilisation de l'environnement de recette permanent
envergo.incubateur.net.
Les « review app » peuvent être créées manuellement à l'envie depuis l'interface de Scalingo. Une review app est automatiquement supprimée lorsque la Pull Request correspondante est fusionnée.
L'environnement de staging est permanent, avec un déploiement automatique de la
branche staging.
Le workflow de collaboration git en vigueur est le suivant :
- la branche
mainne contient que du code absolument prêt à passer en prod (revue de code ok, review PO ok) - sauf commit absolument trivial, tous les devs sont effectués sur des branches dédiées avant de pouvoir être fusionnées
- sauf en cas de branche triviale et au jugé, les branches doivent passer par une revue de code avant d'être fusionnées
- la branche
stagingcontient du code fonctionnel, mais en cours de validation ; cette branche est déployée automatiquement sur l'environnement de staging permanent - Les Pull Requests doivent systématiquement être fusionnées dans
main, et uniquement après validation complete - si la création d'une review app dédiée est jugée trop fastidieuse, une branche de dev peut être fusionnée dans
stagingpour en faciliter la validation. - il est interdit de pusher du code sur
prodqui ne soit pas déjà dansmain - pour effectuer une mise en prod, on fusionne
maindansprod(fast forward) - de façon exceptionnelle, pour déployer un correctif urgemment en prod sans
devoir déployer toute la branche
main, on peut :- publier et valider le correctif sur
main; - effectuer un
cherry-pickdu commit pour les intégrer de manière unitaire à la brancheprod.
- publier et valider le correctif sur
Le déploiement se fait sur la plateforme Scalingo. Pour lancer un déploiement, il suffit de pousser de nouveaux commits sur la branche prod.
Le déploiement se lancera automatiquement si les actions github sont au vert.
Le point d'entrée se trouve dans le fichier Procfile.
Les scripts utilisés sont dans le répertoire bin.
Le workflow à suivre :
- Envoyer un message sur le canal #startup-envergo-produit pour prévenir de la mise en production imminente
- S'assurer du bon fonctionnement de main en local (notamment les nouvelles fonctionnalités)
- Si la CI est ok sur la branche main, fusionner main dans prod et pousser la branche prod
- Quand le déploiement est terminé, vérifier que le site est bien accessible
- Prévenir sur le canal de la finalisation de la mise en prod
- Fusionner la branche main dans staging et pousser la branche staging
- (facultatif) fusionner et tester les mises à jour de dépendance proposées par Snyk
Les tickets sont déplacés de "Fusionnés" à "Done en prod" par læ PO.
Envergo utilise GeoDjango, une version de Django s'appuyant sur des dépendances externes pour les fonctions géographiques (gdal, geos, proj…).
Pour installer ces dépendances, Scalingo proposait un buildpack dédié, qui est tombé en désuétude.
À titre de solution temporaire, les actions suivantes ont été réalisées :
1/ Forker le heroku-geo-buildpack et modifier cette ligne pour obtenir la bonne url.
2/ Remplacer le buildpack scalingo par l'url du buildpack clôné : https://github.com/MTES-MCT/envergo/blob/fix_geo_buildpack/.buildpacks#L3
3/ Configurer la variable d'environnement DISABLE_COLLECTSTATIC. (On appelle déjà manuellement collectstatic dans notre build https://github.com/MTES-MCT/envergo/blob/main/bin/build_assets.sh#L35).
4/ Lancer le déploiement. L'app build sans soucis. Je n'ai pas encore noté de bugs sur les fonctions geo.
Se référer à cette documentation.
$ scalingo -a envergo env | grep POSTGRESQL
$ scalingo -a envergo db-tunnel DATABASE_URL
Building tunnel to envergo-1234.postgresql.dbs.scalingo.com:35314
You can access your database on:
127.0.0.1:10000Depuis un autre shell :
pg_dump --dbname postgresql://<user>:<pass>@localhost:10000/<db> > /tmp/envergo.dumpAlternative : récupérer le backup nocture depuis Scalingo.
$ . envs/postgres
$ docker compose exec postgres bash -c 'dropdb envergo -U "$POSTGRES_USER" -f'
$ docker compose exec postgres bash -c 'createdb envergo -U "$POSTGRES_USER" -O "$POSTGRES_USER"'
$ cat /tmp/envergo.dump | docker exec -i envergo_postgres psql -U $POSTGRES_USER -d $POSTGRES_DB
$ docker compose run --rm django python manage.py migrate
$ docker compose run --rm django python manage.py anonymize_databaseLes documents sont stockés sur un répertoire distant compatible avec le protocole S3 sur Scaleway ce processus est géré via la librairie python boto en combinaison avec le package default_storage de Django
Voir la documentation officielle Scaleway sur le stockage de fichiers
Chaque semaine, on souhaite faire une sauvegarde du contenu des buckets s3 de production. Ceux-ci seront sauvegardés dans un object storage "glacier".
Pour executer cette sauvegarde, on utilise github action
Pour s'exécuter, github action a besoin des identifiants s3 à configurer dans Settings > Secrets and variables > Actions.
Ajouter les Repository secrets :
- S3_ACCESS_KEY
- S3_SECRET_KEY
Pour récupérer les backups et les restaurer.
Voir la documentation Scaleway pour restaurer un fichier depuis le glacier
Voici un petit index des acronymes et termes métiers fréquemment rencontrés.
- LSE : Loi sur l'eau
- 3.2.2.1, 2.1.5.0… : références à certaines rubriques de la Loi sur l'eau, décrivant les critères qui font que certains projets sont soumis ou non à déclaration Loi sur l'eau.
- IOTA : Installations, ouvrages, travaux et aménagements, i.e un « projet ».
- DREAL : Direction régionale de l'Environnement, de l'aménagement et du logement.
- CEREMA : Centre d'études et d'expertise sur les risques, l'environnement, la mobilité et l'aménagement
- DGALN : Direction générale de l'Aménagement, du Logement et de la Nature