Ajoute la section [home_assistant] au TOML et sa validation au chargement via get_home_assistant_config(). La section est facultative pour préserver la compatibilité avec les configurations existantes, mais si elle est présente elle doit être complète et référencer un type de journée, un trajet et un véhicule à moteur existants, sous peine de refuser le démarrage de l'application. La configuration validée est exposée dans app.config['HOME_ASSISTANT']. Co-authored-by: OpenAI/GPT-5.6-Luna-Pro <vibecoder@antoineve.me>
184 lines
6.3 KiB
Python
184 lines
6.3 KiB
Python
"""Package principal de l'application Flask.
|
|
|
|
Ce package initialise l'application Flask en utilisant le "Factory Pattern" (via la fonction `create_app`).
|
|
Il configure également la base de données SQLite (via Flask-SQLAlchemy), charge la configuration TOML,
|
|
enregistre les blueprints de routes, applique les migrations de schéma manuelles, et définit les filtres Jinja2 personnalisés.
|
|
|
|
Architecture et composants clés :
|
|
1. Factory Pattern : `create_app` permet d'instancier l'application de manière isolée, facilitant les tests.
|
|
2. Base de données : SQLite stockée dans `instance/worklog.db`.
|
|
3. Migration de schéma : Gérée manuellement par `_migrate_db` sans utiliser Alembic.
|
|
4. Filtres Jinja2 :
|
|
- `date_fr` : Formate une date Python en chaîne lisible en français (ex: "mercredi 11 mars 2026").
|
|
- `day_type_fr` : Traduit les codes internes des types de jours (ex: "WORK" -> "Travail").
|
|
"""
|
|
|
|
import os
|
|
import tomllib
|
|
|
|
import sqlalchemy as sa
|
|
from flask import Flask
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
|
|
db = SQLAlchemy()
|
|
|
|
|
|
def _migrate_db(app):
|
|
"""Applique les migrations de schéma manquantes de manière incrémentale (sans Alembic).
|
|
|
|
Cette fonction vérifie l'état actuel de la base de données SQLite avant d'exécuter
|
|
des instructions DDL (comme `ALTER TABLE`). Elle permet d'éviter les erreurs si la table
|
|
`work_entries` n'existe pas encore (auquel cas `db.create_all()` s'en charge) ou si la
|
|
colonne `motor_vehicle_id` a déjà été ajoutée lors d'un démarrage précédent.
|
|
|
|
Paramètres:
|
|
app (Flask): L'instance de l'application Flask en cours d'initialisation.
|
|
"""
|
|
import sqlite3
|
|
|
|
db_path = os.path.join(app.instance_path, "worklog.db")
|
|
if not os.path.exists(db_path):
|
|
return # Nouvelle DB, create_all() s'en charge
|
|
conn = sqlite3.connect(db_path)
|
|
tables = {row[0] for row in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")}
|
|
if "work_entries" not in tables:
|
|
conn.close()
|
|
return # Table pas encore créée, create_all() s'en charge
|
|
columns = {row[1] for row in conn.execute("PRAGMA table_info(work_entries)").fetchall()}
|
|
conn.close()
|
|
|
|
with app.app_context():
|
|
engine = db.engine
|
|
if "motor_vehicle_id" not in columns:
|
|
with engine.connect() as conn:
|
|
conn.execute(
|
|
sa.text("ALTER TABLE work_entries ADD COLUMN motor_vehicle_id VARCHAR(64)")
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
_JOURS_FR = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche"]
|
|
_MOIS_FR = [
|
|
"",
|
|
"janvier",
|
|
"février",
|
|
"mars",
|
|
"avril",
|
|
"mai",
|
|
"juin",
|
|
"juillet",
|
|
"août",
|
|
"septembre",
|
|
"octobre",
|
|
"novembre",
|
|
"décembre",
|
|
]
|
|
|
|
|
|
_DAY_TYPE_LABELS = {
|
|
"WORK": "Travail",
|
|
"TT": "Télétravail",
|
|
"GARDE": "Garde",
|
|
"ASTREINTE": "Astreinte",
|
|
"FORMATION": "Formation",
|
|
"RTT": "RTT",
|
|
"CONGE": "Congé",
|
|
"MALADE": "Maladie",
|
|
"FERIE": "Férié",
|
|
}
|
|
|
|
|
|
def _day_type_fr(code):
|
|
"""Filtre Jinja2 pour traduire un code de type de jour en libellé français.
|
|
|
|
Exemple:
|
|
`{{ 'WORK' | day_type_fr }}` -> "Travail"
|
|
|
|
Paramètres:
|
|
code (str): Le code interne du type de jour (ex: "WORK", "TT", "GARDE").
|
|
|
|
Retourne:
|
|
str: Le libellé en français correspondant, ou le code d'origine si aucune traduction n'est définie.
|
|
"""
|
|
return _DAY_TYPE_LABELS.get(code, code)
|
|
|
|
|
|
def _date_fr(d):
|
|
"""Filtre Jinja2 pour formater une date en français lisible.
|
|
|
|
Exemple:
|
|
`{{ entry.date | date_fr }}` -> "mercredi 11 mars 2026"
|
|
|
|
Paramètres:
|
|
d (datetime.date): L'objet date à formater.
|
|
|
|
Retourne:
|
|
str: La date formatée en français (jour de la semaine, jour du mois, mois en toutes lettres, année).
|
|
"""
|
|
jour = _JOURS_FR[d.weekday()]
|
|
mois = _MOIS_FR[d.month]
|
|
return f"{jour} {d.day} {mois} {d.year}"
|
|
|
|
|
|
def create_app(config_path=None):
|
|
"""Factory de création et de configuration de l'application Flask.
|
|
|
|
Cette fonction réalise les étapes suivantes :
|
|
1. Instancie l'application Flask avec le support des configurations relatives à l'instance.
|
|
2. Crée le dossier d'instance s'il n'existe pas.
|
|
3. Configure l'URI de la base de données SQLite (`instance/worklog.db`).
|
|
4. Charge la configuration TOML depuis `config.toml` (ou le chemin spécifié).
|
|
5. Initialise l'extension Flask-SQLAlchemy (`db`).
|
|
6. Enregistre les filtres Jinja2 personnalisés (`date_fr` et `day_type_fr`).
|
|
7. Enregistre les blueprints de routes (`dashboard`, `entries`, `reports`).
|
|
8. Exécute la migration de schéma manuelle (`_migrate_db`) puis crée les tables manquantes (`db.create_all()`).
|
|
|
|
Paramètres:
|
|
config_path (str | None): Chemin optionnel vers le fichier de configuration TOML.
|
|
Par défaut, cherche `config.toml` à la racine du projet.
|
|
|
|
Retourne:
|
|
Flask: L'instance de l'application Flask configurée et prête à l'emploi.
|
|
"""
|
|
app = Flask(__name__, instance_relative_config=True)
|
|
|
|
os.makedirs(app.instance_path, exist_ok=True)
|
|
|
|
app.config["SQLALCHEMY_DATABASE_URI"] = (
|
|
f"sqlite:///{os.path.join(app.instance_path, 'worklog.db')}"
|
|
)
|
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
|
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-secret-change-in-prod")
|
|
|
|
# Load TOML config
|
|
if config_path is None:
|
|
config_path = os.path.join(os.path.dirname(app.root_path), "config.toml")
|
|
if os.path.exists(config_path):
|
|
with open(config_path, "rb") as f:
|
|
app.config["TOML"] = tomllib.load(f)
|
|
else:
|
|
app.config["TOML"] = {}
|
|
|
|
from app.config_loader import get_home_assistant_config
|
|
|
|
with app.app_context():
|
|
app.config["HOME_ASSISTANT"] = get_home_assistant_config()
|
|
|
|
db.init_app(app)
|
|
app.jinja_env.filters["date_fr"] = _date_fr
|
|
app.jinja_env.filters["day_type_fr"] = _day_type_fr
|
|
|
|
from app.routes.dashboard import bp as dashboard_bp
|
|
from app.routes.entries import bp as entries_bp
|
|
from app.routes.reports import bp as reports_bp
|
|
|
|
app.register_blueprint(dashboard_bp)
|
|
app.register_blueprint(entries_bp)
|
|
app.register_blueprint(reports_bp)
|
|
|
|
with app.app_context():
|
|
_migrate_db(app)
|
|
db.create_all()
|
|
|
|
return app
|