7. Les états
This content is not available in your language yet.
Un emprunt est sorti, puis rendu, ou perdu. C’est une machine à états, et on ne l’écrit pas à la main.
Déclarer le cycle
models/workflow.py :
"""``library_loan`` lifecycle: out, then returned or lost."""
from __future__ import annotations
from typing import TYPE_CHECKING
from kewos.views import register_status_colorsfrom kewos.workflow import StateMachine, has_state_machine, register_state_machine
if TYPE_CHECKING: from kewos.orm.registry import Registry
_STATE_COLORS = {"out": "info", "returned": "success", "lost": "destructive"}
def _build() -> StateMachine: machine = StateMachine(initial="out", states=("out", "returned", "lost")) machine.add_transition(trigger="check_in", source="out", dest="returned") machine.add_transition(trigger="declare_lost", source="out", dest="lost") return machine
def register_loan_workflow(_registry: Registry) -> None: """Bind the loan state machine and its status colours (idempotent).""" if not has_state_machine("library_loan"): register_state_machine("library_loan", _build()) register_status_colors("library_loan", "state", _STATE_COLORS)Et on l’appelle depuis models/__init__.py, après les modèles.
Ce que ça produit

Trois choses sont apparues, sans une ligne d’interface :
- La piste d’états, avec l’étape courante mise en avant et les suivantes en attente.
- Les boutons de transition, « Check in » et « Declare lost », qui n’apparaissent que
lorsque la transition est possible depuis l’état courant. Depuis
returned, il n’y en a aucun. - Les couleurs sémantiques : un emprunt sorti est neutre, un emprunt rendu est vert, un emprunt perdu est rouge. Elles viennent des jetons du thème, jamais d’une couleur en dur, et elles se retrouvent dans la liste et dans le kanban.
L’écueil que j’ai rencontré en l’écrivant
Ma première tentative affichait workflow.check_in sur le bouton : une clé de traduction non
résolue, servie telle quelle à l’utilisateur. J’avais écrit la clé sous action.check_in.
La bonne forme est workflow.<trigger>.
{ "workflow.check_in": "Check in", "workflow.declare_lost": "Declare lost"}Aucune erreur n’est levée dans ce cas : une clé absente s’affiche brute. C’est exactement le genre de défaut qui passe une revue de code et se voit en démonstration.
Garder une transition
Une transition peut refuser de s’exécuter.
machine.add_transition( trigger="check_in", source="out", dest="returned", guard=lambda instance: instance.taken_on is not None,)Le garde renvoie faux, la transition est refusée, l’interface affiche l’erreur. On ne peut pas contourner en appelant l’API : la transition passe par le même chemin.
Demander des informations au moment de la transition
form= déclare les champs que l’utilisateur doit renseigner avant que la transition ne parte.
machine.add_transition( trigger="declare_lost", source="out", dest="lost", form=("lost_note",),)Le moteur affiche une boîte de dialogue avec ces champs, et l’effet peut alors s’appuyer dessus. C’est ainsi qu’on garantit qu’un emprunt déclaré perdu porte toujours une explication.
Agir au passage
effect= reçoit la session, et s’exécute dans la transaction de la transition. Ce qui est
écrit là est annulé avec elle si quelque chose échoue plus loin.
def _mark_returned(session, _classes, instance) -> None: """Stamp the return date and free the copy, atomically with the transition.""" instance.returned_on = date.today()
machine.add_transition( trigger="check_in", source="out", dest="returned", effect=_mark_returned)C’est l’endroit pour tracer, pour émettre un événement, pour mettre à jour un compteur. Ce n’est pas l’endroit pour appeler un service externe : cela tiendrait la transaction ouverte pendant un appel réseau. Émettez un événement, et laissez un traitement de fond s’en charger.
Un état définitif
Un modèle peut déclarer qu’à partir d’un certain état, plus rien ne se modifie :
registry.register_model( "library_loan", module="library", fields=[...], immutable_after_state=("returned", "lost"),)Une correction se fait alors par contre-écriture, pas par modification. C’est la même règle que pour une écriture comptable, et pour la même raison : l’historique doit rester défendable.