Aller au contenu

2. Ajouter des champs

pipeline_lead appartient à pipeline. Nous allons lui ajouter deux champs sans ouvrir un seul fichier de ce module.

Le geste

"""Adding two fields to ``pipeline_lead``, without forking it.
``extend_model`` contributes fields to a model another module declared. The
target does not need to exist yet: the check is deferred to freeze time, so
module load order stays free.
What this is NOT: an import of ``modules.pipeline``. This module names the
model, never the code behind it. `pipeline` can change its internals, and this
keeps working.
"""
def extend_lead(registry: Registry) -> None:
"""Put "who else is bidding" and "who won" on the opportunity."""
registry.extend_model(
"pipeline_lead",
module="pipeline_rivals",
fields=[
# Who we are up against while the deal is open.
Link(
name="rivals_competitor_id",
target_model="rivals_competitor",
nullable=True,
on_delete="set_null",
),
# Who actually won it. Separate from the field above on purpose: the
# competitor we feared is often not the one that took the deal, and
# collapsing the two loses exactly the fact worth reviewing.
Link(
name="rivals_won_by_id",
target_model="rivals_competitor",
nullable=True,
on_delete="set_null",
),
],
)

Quatre décisions, pas une seule ligne d’accident

Le préfixe. Les champs s’appellent rivals_*, pas competitor_id. Ils vivent dans une table qui ne nous appartient pas, aux côtés de champs que d’autres extensions peuvent ajouter demain. Un nom générique, c’est une collision qui attend son heure, et elle se produira chez un client, pas chez vous.

nullable=True. Non négociable sur un modèle existant. Une colonne obligatoire ajoutée à une table qui a déjà des lignes ne peut pas être créée : la migration échoue, ou pire, elle réussit après avoir inventé une valeur par défaut pour des affaires closes il y a deux ans.

on_delete="set_null". Supprimer un concurrent ne doit pas supprimer une affaire. Le lien disparaît, l’affaire reste. C’est la seule réponse défendable : la donnée qui a de la valeur ici est l’historique des affaires, pas la fiche du concurrent.

Deux champs, pas un. « Contre qui nous étions » et « qui a gagné » ne sont pas la même information, et ce n’est pas de la coquetterie de modélisation : dans une revue, l’écart entre les deux est précisément ce qu’on cherche. Les fusionner efface la question.

Ce qui arrive en base

Rien à écrire. kewos reconcile voit deux colonnes de plus sur un modèle déjà provisionné et les ajoute :

main [active]: reconciled (0 migration(s), 2 column(s))
done: 1 tenant(s), 0 failed

Deux colonnes ajoutées, aucune touchée. C’est ce que le rapprochement de schéma sait faire sans danger ; tout ce qui détruit passe par une migration écrite à la main et confirmée.

L’ordre de chargement ne vous concerne pas

extend_model accepte un modèle qui n’existe pas encore : la vérification est repoussée au gel du registre. Vous n’avez donc pas à savoir si pipeline a été lu avant vous. Si le modèle n’existe toujours pas au gel, l’erreur est franche et nomme le modèle manquant, ce qui est le symptôme d’une dépendance oubliée dans le manifeste.

Les droits suivent

Notre modèle a son propre acl.json, et il réutilise les groupes de pipeline :

{
"rivals_competitor": {
"read": ["pipeline_user", "pipeline_manager", "kws_admin"],
"create": ["pipeline_manager", "kws_admin"],
"write": ["pipeline_manager", "kws_admin"],
"unlink": ["kws_admin"]
}
}

Nommer pipeline_manager n’est pas un import : c’est un code, une chaîne de caractères stable, exactement comme un nom de modèle. Personne n’a à recevoir un nouveau groupe pour voir les concurrents ; qui gère le portefeuille les gère.

Et ensuite

Les champs existent, l’API les renvoie déjà. Ils ne sont sur aucun écran : Compléter un écran.