refactor: migrer datetime.utcnow() vers datetime.now(UTC)

Co-authored-by: OpenAI/GPT-5.6-terra <vibecoder@antoineve.me>
This commit is contained in:
2026-08-13 15:21:51 +02:00
parent b6fa09a709
commit 1a679ea1c7
3 changed files with 10 additions and 6 deletions

View File

@@ -78,7 +78,8 @@ Flask app using the factory pattern (`create_app()` in `app/__init__.py`). The D
- **Pas de migration de schéma** : l'app utilise `db.create_all()` uniquement (pas d'Alembic). Tout changement de modèle nécessite de supprimer `instance/worklog.db` en dev, ou une migration manuelle en prod. - **Pas de migration de schéma** : l'app utilise `db.create_all()` uniquement (pas d'Alembic). Tout changement de modèle nécessite de supprimer `instance/worklog.db` en dev, ou une migration manuelle en prod.
- **Barème kilométrique** : les tranches dans `config.toml` sont à mettre à jour manuellement chaque année (section `[bareme_kilometrique.YYYY]`). - **Barème kilométrique** : les tranches dans `config.toml` sont à mettre à jour manuellement chaque année (section `[bareme_kilometrique.YYYY]`).
- **`datetime.utcnow()` déprécié** : Les modèles utilisent actuellement `datetime.utcnow` (générant un avertissement sur Python 3.12+ et obsolète sur Python 3.14+). Il est formellement recommandé d'utiliser `datetime.now(UTC)` (avec `from datetime import UTC`) à la place pour toute nouvelle manipulation de date/heure ou lors de la prochaine évolution des modèles. - **Métadonnées et horodatages (`created_at` / `updated_at`)** : L'application utilise `datetime.now(UTC)` pour enregistrer ces métadonnées. Bien que les valeurs soient émises en UTC, SQLite et SQLAlchemy restituent par défaut des objets `datetime` naïfs (sans `tzinfo`). Ces champs sont actuellement informatifs et non exploités par la logique métier. En cas de besoin ultérieur d'exploitation de ces métadonnées avec fuseau, les pistes incluent la conservation explicite du fuseau (`DateTime(timezone=True)`) ou la stricte convention documentée « naïf = UTC ».
- **Calculs de durée métier et transitions DST** : Le calcul de la durée des plages horaires de travail (`TimeSlot` via `total_minutes()`) est totalement indépendant des métadonnées et repose sur des heures murales (locales). Une plage horaire traversant un changement d'heure saisonnier (passage heure d'été/hiver / DST) soulève un enjeu métier spécifique (gestion des durées d'heures locales) qui nécessiterait, le cas échéant, une représentation dédiée ou une politique métier spécifique.
- **Filtres Jinja2** (définis dans `app/__init__.py`) : `{{ date | date_fr }}` pour les dates en français ; `{{ day_type | day_type_fr }}` pour les libellés de types de jours (WORK→Travail, TT→Télétravail, etc.). - **Filtres Jinja2** (définis dans `app/__init__.py`) : `{{ date | date_fr }}` pour les dates en français ; `{{ day_type | day_type_fr }}` pour les libellés de types de jours (WORK→Travail, TT→Télétravail, etc.).
- **`db.get_engine()` deprecated** en Flask-SQLAlchemy 3.x → utiliser `db.engine`. - **`db.get_engine()` deprecated** en Flask-SQLAlchemy 3.x → utiliser `db.engine`.
- **Migration `_migrate_db`** : vérifier l'existence de la table avant `ALTER TABLE` — SQLite peut avoir un fichier DB sans tables (ex: premier démarrage avec `instance/worklog.db` vide). - **Migration `_migrate_db`** : vérifier l'existence de la table avant `ALTER TABLE` — SQLite peut avoir un fichier DB sans tables (ex: premier démarrage avec `instance/worklog.db` vide).

View File

@@ -1,4 +1,4 @@
from datetime import date, datetime, time from datetime import UTC, date, datetime, time
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as so import sqlalchemy.orm as so
@@ -27,9 +27,11 @@ class WorkEntry(db.Model):
motor_vehicle_id: so.Mapped[str | None] = so.mapped_column(sa.String(64), nullable=True) motor_vehicle_id: so.Mapped[str | None] = so.mapped_column(sa.String(64), nullable=True)
day_type: so.Mapped[str] = so.mapped_column(sa.String(16), nullable=False, default="WORK") day_type: so.Mapped[str] = so.mapped_column(sa.String(16), nullable=False, default="WORK")
comment: so.Mapped[str | None] = so.mapped_column(sa.Text, nullable=True) comment: so.Mapped[str | None] = so.mapped_column(sa.Text, nullable=True)
created_at: so.Mapped[datetime] = so.mapped_column(sa.DateTime, default=datetime.utcnow) created_at: so.Mapped[datetime] = so.mapped_column(
sa.DateTime, default=lambda: datetime.now(UTC)
)
updated_at: so.Mapped[datetime] = so.mapped_column( updated_at: so.Mapped[datetime] = so.mapped_column(
sa.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow sa.DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)
) )
time_slots: so.Mapped[list["TimeSlot"]] = so.relationship( time_slots: so.Mapped[list["TimeSlot"]] = so.relationship(

View File

@@ -262,8 +262,9 @@ sudo systemctl restart tableau-de-bord-pro
- En **développement** : Tu peux simplement supprimer le fichier `instance/worklog.db` pour qu'il soit recréé au prochain démarrage (attention, cela supprime tes données de test). - En **développement** : Tu peux simplement supprimer le fichier `instance/worklog.db` pour qu'il soit recréé au prochain démarrage (attention, cela supprime tes données de test).
- En **production** : Tu dois écrire une migration manuelle dans la fonction `_migrate_db` située dans `app/__init__.py`. Cette fonction s'exécute au démarrage de l'application, vérifie l'existence des colonnes via SQLite, et applique les instructions `ALTER TABLE` nécessaires de manière sécurisée. - En **production** : Tu dois écrire une migration manuelle dans la fonction `_migrate_db` située dans `app/__init__.py`. Cette fonction s'exécute au démarrage de l'application, vérifie l'existence des colonnes via SQLite, et applique les instructions `ALTER TABLE` nécessaires de manière sécurisée.
### Dépréciations à surveiller ### Horodatages de métadonnées et calculs de durée métier
- **`datetime.utcnow()`** : Cette méthode est dépréciée depuis Python 3.12. Il est formellement recommandé d'utiliser `datetime.now(UTC)` (avec `from datetime import UTC`) à la place. Lors d'une prochaine évolution majeure des modèles, il faudra remplacer les occurrences existantes. - **Métadonnées (`created_at`, `updated_at`)** : L'application utilise `datetime.now(UTC)` pour enregistrer ces métadonnées. Bien que les valeurs soient émises en UTC, SQLite et SQLAlchemy stockent et restituent par défaut des objets `datetime` naïfs (sans `tzinfo`). Ces champs sont actuellement informatifs et non exploités par la logique métier. Si un besoin de lecture ou de manipulation de ces métadonnées avec fuseau émergeait, les pistes incluent l'utilisation de types `DateTime(timezone=True)` ou la stricte convention documentée « naïf = UTC ».
- **Calculs de durée métier et transitions DST** : Le calcul de la durée des plages horaires de travail (`TimeSlot` via `total_minutes()`) est totalement indépendant des métadonnées et repose sur des heures murales (locales). Une plage horaire traversant un changement d'heure saisonnier (passage heure d'été/hiver / DST) soulève un enjeu métier spécifique (gestion des durées d'heures locales) qui nécessiterait, le cas échéant, une représentation dédiée ou une politique métier spécifique.
- **`db.get_engine()`** : Déprécié dans Flask-SQLAlchemy 3.x. Utilise toujours `db.engine` à la place. - **`db.get_engine()`** : Déprécié dans Flask-SQLAlchemy 3.x. Utilise toujours `db.engine` à la place.
--- ---