16 KiB
Jardin App — Synthèse de développement
Application web de gestion de jardins (potager, serre, plein air), self-hosted, mobile-first. Thème visuel : Gruvbox Dark "seventies" — vintage, chaleureux, contrasté. Date de dernière mise à jour : 2026-03-09
Stack technique
| Couche | Technologie |
|---|---|
| Backend | Python 3.13 · FastAPI · SQLModel · SQLite |
| Frontend | Vue 3 · Vite · TypeScript · Tailwind CSS · Pinia |
| Tests | pytest (49 tests) |
| Déploiement | Docker Compose (backend + frontend + volumes db/uploads) |
Ports locaux : backend 8060, frontend 5173 (dev) / nginx (prod)
Architecture générale
jardin/
├── backend/
│ ├── app/
│ │ ├── main.py — lifespan, routers, migration auto, seed
│ │ ├── migrate.py — ALTER TABLE pour colonnes manquantes (sans perte)
│ │ ├── seed.py — données de démo (plantes, outils, dictons, astuces)
│ │ ├── config.py — DATABASE_URL, STATION_URL, METEO_LAT/LON
│ │ ├── database.py — engine SQLite + PRAGMA foreign_keys=ON
│ │ ├── models/ — SQLModel tables
│ │ └── routers/ — endpoints FastAPI par domaine
│ ├── scripts/ — scripts one-shot (migration, import)
│ └── tests/ — pytest (49 tests)
├── frontend/
│ ├── src/
│ │ ├── api/ — clients Axios par domaine (*.ts + *.js)
│ │ ├── stores/ — stores Pinia
│ │ ├── views/ — pages Vue (16 vues)
│ │ └── utils/ — helpers partagés
│ └── tailwind.config.js — palette Gruvbox Dark personnalisée
├── data/
│ ├── jardin.db — base SQLite de production
│ └── uploads/ — photos uploadées (UUID.jpg)
└── docs/ — plans, designs, données source
Modèle de données (tables actives)
| Table | Rôle |
|---|---|
plant |
Plantes (nom commun, famille, caractéristiques culture) |
plant_variety |
Variétés d'une plante (variete, boutique, prix, poids, DLUO) |
plant_image |
Photos associées à une plante |
garden |
Jardins (nom, type, sol, exposition, dimensions) |
garden_cell |
Cases de la grille 2D du jardin |
planting |
Instance : plante X dans jardin Y |
planting_event |
Arrosage / taille / traitement / observation |
task |
Tâches (ponctuelles ou récurrentes avec fréquence_jours) |
tool |
Outils de jardinage |
media |
Photos (upload, identification PlantNet/YOLO) |
recolte |
Récoltes (quantité, unité, date) |
meteostation |
Données station météo locale (WeeWX) |
meteoopenmeteo |
Données open-meteo.com (prévisions) |
lunar_calendar_entry |
Calendrier lunaire (phases, types de jours) |
astuce |
Astuces jardinage (catégorie, mois, tags) |
dicton |
Dictons saisonniers |
saint_du_jour |
Saints du calendrier (366 entrées) |
achat_intrant |
Achats d'intrants (semences, engrais…) |
fabrication |
Fabrications maison (compost, préparations…) |
user_settings |
Préférences interface (tailles, URL station…) |
Pages de l'interface (16 vues)
| Route | Vue | Description |
|---|---|---|
/ |
DashboardView | Tâches du jour, météo résumée, plantations actives |
/jardins |
JardinsView | Liste et création de jardins |
/jardins/:id |
JardinDetailView | Grille 2D, cases, plantations par case |
/plantes |
PlantesView | Catalogue plantes + variétés, popup détail/édition |
/varietes |
VarietesView | Vue dédiée variétés (ancienne) |
/plantations |
PlantationsView | Liste filtrable, création, fiche plantation |
/taches |
TachesView | Kanban / liste des tâches |
/planning |
PlanningView | Calendrier 4 semaines |
/meteo |
CalendrierView | Tableau météo + lunaire + dictons + navigation |
/lunaire |
LunaireView | Calendrier lunaire détaillé |
/astuces |
AstucesView | Astuces filtrables par catégorie/mois/tag |
/bibliotheque |
BibliothequeView | Galerie photos + identification PlantNet/YOLO |
/outils |
OutilsView | Outils de jardinage (CRUD) |
/intrants |
IntratsView | Onglets Achats + Fabrications |
/reglages |
ReglagesView | URL station, backup ZIP, tailles UI |
Historique du développement
Phase 1 — Fondations (commit initial → 4787f04)
- Scaffold projet Docker Compose (backend FastAPI + frontend Vue 3)
- 10 tables SQLModel, CRUD complet jardins / variétés / plantations / tâches
- Seed données démo : 1 jardin, variétés courantes, tâches types
- Frontend : layout header + drawer + router 9 routes, API layer + stores Pinia
- Vues MVP : Dashboard, Jardins, Grille, Variétés, Tâches, Plantations
Phase 2 — Photos & identification (2d8b1b4 → 326158f)
- Service YOLO (container FastAPI dédié) pour détection locale de plantes
- Service PlantNet API pour identification par photo
- Cache Redis pour les identifications
- Endpoint
POST /api/identify: PlantNet en premier, YOLO en fallback - Vue BibliothequeView : galerie lightbox + modal d'upload + identification
- Fix : noms PlantNet en français (
lang=fr), nginxclient_max_body_size 20M
Phase 3 — Météo & calendrier (3032751 → 0d3bf20)
- Tables
MeteoStation+MeteoOpenMeteo(SQLModel) - APScheduler 3 jobs : station WeeWX (1×/heure), open-meteo (1×/heure), cleanup
- Scraper WeeWX RSS pour données actuelles + NOAA pour données de la veille
- Service open-meteo enrichi (sol, ETP, past_days, humidité)
- 6 endpoints météo : tableau synthétique, station, prévisions, navigation
- CalendrierView : tableau météo + lunaire + dictons + navigation Prev/Today/Next
- Import calendrier lunaire (phases + types jours : racine/feuille/fleur/fruit)
- Table
saint_du_jour: 366 entrées importées
Phase 4 — Astuces & réglages UI (cc69d0d → fb33540)
- Table
astuce: colonnes catégorie/tags/mois, CRUD + filtres - AstucesView avec filtres catégorie / mois / tag
- Réglages interface : sliders taille texte/menu/icônes/miniatures (CSS vars globales)
- Export
UI_SIZE_DEFAULTSpartagé entre composants
Phase 5 — Intrants (faa469e → f8e64d6)
- Tables
AchatIntrant+Fabrication(SQLModel) - Migration automatique via
migrate.py - Routers CRUD : achats, fabrications
- Frontend : stores Pinia + clients API + IntratsView avec onglets Achats / Fabrications
Phase 6 — Plantes & Variétés — restructuration complète (e40351e → 4c279c3)
Moteur principal de cette session (2026-03-08/09)
Voir détail complet ci-dessous.
Dernières modifications — Phase 6 : Plantes & Variétés
Contexte
La gestion des plantes reposait sur une table unique où chaque entrée mêlait les caractéristiques de l'espèce (nom commun, famille, culture) et les données commerciales d'une variété particulière (boutique, prix, DLUO). Cette structure rendait impossible d'associer plusieurs variétés à une même plante.
Décision architecturale : scission en 2 tables distinctes.
Nouveau modèle de données
Table plant (espèce / nom commun)
id, nom_commun, nom_botanique, famille, type_plante, categorie
besoin_eau, besoin_soleil, espacement_cm, hauteur_cm, temp_min_c
temp_germination, temps_levee_j ← nouveaux champs
duree_culture_j, profondeur_semis_cm, sol_conseille
semis_interieur_mois, semis_exterieur_mois, repiquage_mois
plantation_mois, recolte_mois
maladies_courantes, astuces_culture, url_reference, notes
associations_favorables (JSON), associations_defavorables (JSON)
created_at
Table plant_variety (variété commerciale)
id, plant_id (FK → plant.id), variete, tags, notes_variete
boutique_nom, boutique_url, prix_achat, date_achat, poids, dluo
created_at
Schéma de réponse API PlantWithVarieties
Objet non persisté retourné par tous les GET /api/plants* :
= tous les champs de Plant + varieties: List[PlantVariety]
Fichiers modifiés — Backend
| Fichier | Modification |
|---|---|
backend/app/models/plant.py |
Modèle Plant épuré + PlantVariety + PlantWithVarieties + PlantImage |
backend/app/models/__init__.py |
Export PlantVariety, PlantWithVarieties |
backend/app/database.py |
Activation PRAGMA foreign_keys=ON (event listener SQLAlchemy) |
backend/app/migrate.py |
Ajout section plant_variety + colonnes temp_germination/temps_levee_j sur plant — suppression colonnes boutique/dluo de la section plant |
backend/app/seed.py |
Extraction du champ variete avant création Plant + création PlantVariety associée |
backend/app/routers/plants.py |
Remplacement complet : helper _with_varieties(), GET retourne PlantWithVarieties, CASCADE sur DELETE, 4 endpoints CRUD /plants/{id}/varieties |
backend/tests/test_plants.py |
Réécriture du test variétés pour la nouvelle architecture |
Nouveaux endpoints variétés
GET /api/plants → List[PlantWithVarieties]
GET /api/plants/{id} → PlantWithVarieties
POST /api/plants/{id}/varieties → PlantVariety (201)
GET /api/plants/{id}/varieties → List[PlantVariety]
PUT /api/plants/{id}/varieties/{vid} → PlantVariety
DELETE /api/plants/{id}/varieties/{vid} → 204
Scripts one-shot (déjà exécutés)
backend/scripts/migrate_plant_varieties.py
Migration de la base existante :
- Crée la table
plant_variety - Migre
variete/tags/boutique_*de chaque plant → une entréeplant_variety - Fusionne le plant "haricot grimpant" (id=21) comme variété "Grimpant Neckarkönigin" sous "Haricot" (id=7) → supprime plant id=21
- Ajoute colonnes
temp_germinationettemps_levee_jàplant
Résultat : 20 plants · 21 variétés
backend/scripts/import_graines.py
Import des données documentaires :
docs/graine/caracteristiques_plantation.json: 14 sachets de grainesdocs/arbustre/caracteristiques_arbustre.json: 4 arbustes/fruitiers- Mapping intelligent nom sachet → nom commun BDD (
NOM_MAP) - Conversion mois romains → CSV (
roman_to_csv: "III-IV" → "3,4") - Enrichissement des champs
plant(semis, récolte, profondeur, espacement, T° germination) - Création
PlantVarietyavec nom de variété extrait - Copie des 36 photos de sachets dans
data/uploads/+ entréesmedia - Script idempotent (guard
SELECTavant chaqueINSERT)
Résultat : 25 plants · 39 variétés · 36 photos importées
Fichiers modifiés — Frontend
| Fichier | Modification |
|---|---|
frontend/src/api/plants.ts |
Interface PlantVariety + interface Plant mise à jour (sans boutique/variete, avec varieties?, temp_germination?, temps_levee_j?) + méthodes createVariety, updateVariety, deleteVariety |
frontend/src/stores/plants.ts |
Actions update, createVariety, updateVariety, removeVariety avec synchronisation du store local |
frontend/src/views/PlantesView.vue |
Voir détail ci-dessous |
frontend/src/views/TachesView.vue |
Adaptation plant.varieties?.[0]?.variete (champ déplacé) |
frontend/src/utils/plants.ts |
formatPlantLabel utilise varieties?.[0]?.variete au lieu de plant.variete |
PlantesView.vue — nouvelles fonctionnalités
Script setup :
detailPlantObj = ref<Plant | null>: référence à la plante affichéedetailVarieties = computed(() => detailPlantObj.value?.varieties ?? []): liste des variétés- Refs formulaire variété :
showFormVariety,editVariety,formVariety - Fonctions :
openAddVariety,openEditVariety,closeFormVariety,submitVariety,deleteVariety submitPlant: extrait les champs variété du form avant envoi au backend, puis crée/modifie laPlantVarietyséparément
Template :
- Bouton "➕ Variété" dans le footer du popup détail plante
- Champs
temp_germinationettemps_levee_jdans la section caractéristiques (conditionnels) - Liste des variétés dans le popup détail avec boutons ✏️ et ✕, affichage DLUO expirée
- Popup formulaire variété (
z-[70]) : nom variété, tags, enseigne (select BOUTIQUES), prix, poids, date achat, DLUO, URL, notes
État de la base de production après la phase 6
plant : 25 entrées (20 potager + 5 arbustes)
plant_variety : 39 entrées
media : 86 entrées (dont 36 photos sachets)
Répartition par catégorie :
- Potager : Tomate, Carotte, Courgette, Laitue, Ail, Oignon, Haricot (×2 variétés), Pois (×2), Poireau, Pomme de terre, Fraise, Framboise, Persil, Échalote, Chou-fleur, Chou, Betterave, Radis, Épinard, Basilic, Courge
- Arbuste : Vigne, Cassissier, Framboisier
État des tests
49 tests passed (pytest)
Couverture par fichier de test :
test_gardens.py — CRUD jardins + filtres
test_plants.py — CRUD plantes + test_plant_variety_crud (nouvelle architecture)
test_plantings.py — CRUD plantations
test_tasks.py — CRUD tâches + filtres statut/planting
test_tools.py — CRUD outils
test_varieties.py — CRUD variétés (ancienne table)
Points en cours / backlog
Fonctionnalités planifiées (amelioration.md)
- Photos sachet recto/verso dans le popup variété (upload + compression WebP)
- Page 404 catch-all (route Vue manquante)
- Export/import JSON complet
- Observations dans PlantationsView (backend prêt, UI manquante)
- Restauration depuis ZIP (upload + restore)
- Réglages par sections (interface, jardin, plante, tâches, calendrier)
- Capteurs IoT : ensoleillement, température sol/air, humidité, pH (MQTT)
- "Astuce du jour" dans le Dashboard
- Vue Gantt dans PlanningView
- Satisfaction plante (étoiles 1-5)
Dette technique connue
- Colonnes orphelines dans
plant:variete,tags,boutique_*,prix_achat,date_achat,poids,dluotoujours présentes en SQLite (impossibilité de DROP COLUMN) — SQLModel les ignore, pas d'impact fonctionnel PlantCreatemanquant : les endpointsPOST/PUT /api/plantsacceptent le modèlePlant(table=True) directement au lieu d'un schémaPlantCreatedédié- N+1 queries sur
GET /api/plants: une query par plante pour charger ses variétés — acceptable jusqu'à ~200 plantes planting.variety_idFK mal nommée : pointe versplant.idet nonplant_variety.id— à corriger avant d'implémenter le choix de variété lors d'une plantation- Données orphelines BDD : jardin id=1 supprimé mais 24
garden_cells+ 1planting+ 1measurementsubsistent
Commandes utiles
# Lancer l'environnement complet
docker compose up --build
# Backend seul (développement)
cd backend && uvicorn app.main:app --reload --port 8060
# Frontend seul (développement)
cd frontend && npm run dev
# Tests backend
cd backend && python3 -m pytest tests/ -v
# Build frontend
cd frontend && npm run build
# Réexécuter l'import graines (idempotent)
python3 backend/scripts/import_graines.py
# Vérifier état BDD
python3 -c "
import sqlite3; conn = sqlite3.connect('data/jardin.db')
print('plant:', conn.execute('SELECT COUNT(*) FROM plant').fetchone()[0])
print('plant_variety:', conn.execute('SELECT COUNT(*) FROM plant_variety').fetchone()[0])
print('media:', conn.execute('SELECT COUNT(*) FROM media').fetchone()[0])
conn.close()
"