152 lines
5.3 KiB
Python
152 lines
5.3 KiB
Python
"""Migration automatique SQLite — détecte et ajoute les colonnes manquantes."""
|
|
import logging
|
|
from sqlalchemy import text
|
|
from app.database import engine
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Définition des colonnes attendues par table : {table: [(col, type_sql, default)]}
|
|
EXPECTED_COLUMNS: dict[str, list[tuple[str, str, str | None]]] = {
|
|
"plant": [
|
|
("categorie", "TEXT", None),
|
|
("hauteur_cm", "INTEGER", None),
|
|
("maladies_courantes", "TEXT", None),
|
|
("astuces_culture", "TEXT", None),
|
|
("url_reference", "TEXT", None),
|
|
("associations_favorables", "TEXT", None), # JSON list[str]
|
|
("associations_defavorables", "TEXT", None), # JSON list[str]
|
|
("temp_germination", "TEXT", None),
|
|
("temps_levee_j", "TEXT", None),
|
|
],
|
|
"plant_variety": [
|
|
("variete", "TEXT", None),
|
|
("tags", "TEXT", None),
|
|
("notes_variete", "TEXT", None),
|
|
("boutique_nom", "TEXT", None),
|
|
("boutique_url", "TEXT", None),
|
|
("prix_achat", "REAL", None),
|
|
("date_achat", "TEXT", None),
|
|
("poids", "TEXT", None),
|
|
("dluo", "TEXT", None),
|
|
],
|
|
"garden": [
|
|
("latitude", "REAL", None),
|
|
("longitude", "REAL", None),
|
|
("altitude", "REAL", None),
|
|
("adresse", "TEXT", None),
|
|
("longueur_m", "REAL", None),
|
|
("largeur_m", "REAL", None),
|
|
("surface_m2", "REAL", None),
|
|
("carre_potager", "INTEGER", "0"),
|
|
("carre_x_cm", "INTEGER", None),
|
|
("carre_y_cm", "INTEGER", None),
|
|
("photo_parcelle", "TEXT", None),
|
|
("ensoleillement", "TEXT", None),
|
|
],
|
|
"task": [
|
|
("frequence_jours", "INTEGER", None),
|
|
("date_prochaine", "TEXT", None),
|
|
("outil_id", "INTEGER", None),
|
|
],
|
|
"meteostation": [
|
|
("t_min", "REAL", None),
|
|
("t_max", "REAL", None),
|
|
],
|
|
"tool": [
|
|
("photo_url", "TEXT", None),
|
|
("video_url", "TEXT", None),
|
|
("notice_texte", "TEXT", None),
|
|
("notice_fichier_url", "TEXT", None),
|
|
("boutique_nom", "TEXT", None),
|
|
("boutique_url", "TEXT", None),
|
|
("prix_achat", "REAL", None),
|
|
],
|
|
"planting": [
|
|
("boutique_nom", "TEXT", None),
|
|
("boutique_url", "TEXT", None),
|
|
("tarif_achat", "REAL", None),
|
|
("date_achat", "TEXT", None),
|
|
("cell_ids", "TEXT", None), # JSON : liste des IDs de zones (multi-sélect)
|
|
],
|
|
"plantvariety": [
|
|
# ancien nom de table → migration vers "plant" si présente
|
|
],
|
|
"media": [
|
|
("identified_species", "TEXT", None),
|
|
("identified_common", "TEXT", None),
|
|
("identified_confidence", "REAL", None),
|
|
("identified_source", "TEXT", None),
|
|
],
|
|
"astuce": [
|
|
("categorie", "TEXT", None),
|
|
("tags", "TEXT", None),
|
|
("mois", "TEXT", None),
|
|
("photos", "TEXT", None),
|
|
("videos", "TEXT", None),
|
|
],
|
|
"achat_intrant": [
|
|
("categorie", "TEXT", None),
|
|
("nom", "TEXT", None),
|
|
("marque", "TEXT", None),
|
|
("boutique_nom", "TEXT", None),
|
|
("boutique_url", "TEXT", None),
|
|
("prix", "REAL", None),
|
|
("poids", "TEXT", None),
|
|
("date_achat", "TEXT", None),
|
|
("dluo", "TEXT", None),
|
|
("notes", "TEXT", None),
|
|
("jardin_id", "INTEGER", None),
|
|
("plantation_id", "INTEGER", None),
|
|
("tache_id", "INTEGER", None),
|
|
],
|
|
"fabrication": [
|
|
("type", "TEXT", None),
|
|
("nom", "TEXT", None),
|
|
("ingredients", "TEXT", None),
|
|
("date_debut", "TEXT", None),
|
|
("date_fin_prevue", "TEXT", None),
|
|
("statut", "TEXT", "'en_cours'"),
|
|
("quantite_produite", "TEXT", None),
|
|
("notes", "TEXT", None),
|
|
("jardin_id", "INTEGER", None),
|
|
("plantation_id", "INTEGER", None),
|
|
("tache_id", "INTEGER", None),
|
|
],
|
|
}
|
|
|
|
|
|
def _existing_columns(conn, table: str) -> set[str]:
|
|
rows = conn.execute(text(f"PRAGMA table_info({table})")).fetchall()
|
|
return {row[1] for row in rows}
|
|
|
|
|
|
def _existing_tables(conn) -> set[str]:
|
|
rows = conn.execute(text("SELECT name FROM sqlite_master WHERE type='table'")).fetchall()
|
|
return {row[0] for row in rows}
|
|
|
|
|
|
def run_migrations() -> None:
|
|
with engine.connect() as conn:
|
|
tables = _existing_tables(conn)
|
|
|
|
# Renommage plantvariety → plant si l'ancienne table existe et la nouvelle non
|
|
if "plantvariety" in tables and "plant" not in tables:
|
|
conn.execute(text("ALTER TABLE plantvariety RENAME TO plant"))
|
|
conn.commit()
|
|
logger.info("Migration: plantvariety renommée en plant")
|
|
tables = _existing_tables(conn)
|
|
|
|
for table, columns in EXPECTED_COLUMNS.items():
|
|
if table not in tables or not columns:
|
|
continue
|
|
existing = _existing_columns(conn, table)
|
|
for col_name, col_type, default in columns:
|
|
if col_name not in existing:
|
|
if default is not None:
|
|
sql = f"ALTER TABLE {table} ADD COLUMN {col_name} {col_type} DEFAULT {default}"
|
|
else:
|
|
sql = f"ALTER TABLE {table} ADD COLUMN {col_name} {col_type}"
|
|
conn.execute(text(sql))
|
|
conn.commit()
|
|
logger.info(f"Migration: colonne {table}.{col_name} ajoutée")
|