3. Compléter un écran
Les deux champs existent et l’API les renvoie. Ils n’apparaissent nulle part, parce qu’une vue
liste les champs qu’elle montre, et celle de pipeline ne connaît pas les nôtres.
La tentation est de déclarer notre propre pipeline_lead.form.json. C’est le piège. Une
vue complète remplace celle du module, donc :
- vous recopiez ses trente champs pour en ajouter deux ;
- la prochaine version de
pipelineajoute un champ, et votre copie ne l’a pas ; - une deuxième extension fait pareil, et vous vous battez pour le même emplacement.
La rustine
Un fichier <modèle>.<genre>.patch.json se pose par-dessus la vue existante au lieu de la
remplacer.
modules/pipeline_rivals/views/pipeline_lead.form.patch.json :
{ "model": "pipeline_lead", "kind": "form", "module": "pipeline_rivals", "patch": [ { "op": "add", "field": { "name": "rivals_competitor_id" }, "after": "source_id", "section": "section.general" }, { "op": "add", "field": { "name": "rivals_won_by_id" }, "after": "lost_reason_id", "section": "section.general" } ]}Rien à appeler : register_views_from_dir, que votre module appelle déjà pour ses propres
écrans, reconnaît le suffixe et route le fichier.
module est porteur. Il devient la clé de portée de l’enregistrement, ce qui permet à
deux extensions de compléter le même écran sans s’écraser. Une rustine anonyme serait
indiscernable d’une autre : le noyau la refuse.
Les opérations disponibles sont add, hide, replace et move. Ancrage par before ou
after sur un champ existant, et section pour dire dans quel bloc du formulaire poser le
champ.
Le même geste sur la liste
modules/pipeline_rivals/views/pipeline_lead.list.patch.json :
{ "model": "pipeline_lead", "kind": "list", "module": "pipeline_rivals", "patch": [ { "op": "add", "field": { "name": "rivals_competitor_id" }, "before": "amount_weighted_amount" } ]}Les libellés
Un champ posé sur un écran affiche la clé <modèle>.<champ>. Elle est à vous, même si le
modèle ne l’est pas :
{ "module.pipeline_rivals": "Competitors", "model.rivals_competitor": "Competitors", "rivals_competitor.name": "Name", "rivals_competitor.tier": "Kind", "pipeline_lead.rivals_competitor_id": "Up against", "pipeline_lead.rivals_won_by_id": "Lost to"}pipeline ne traduira jamais vos champs : ils n’existent pas pour lui.
Ce que ça donne
La liste de pipeline, avec une colonne de plus exactement là où l’ancre l’a mise :

Et la fiche, où « Up against » suit « Source » et « Lost to » suit « Lost reason », dans la
section « General » de pipeline :

Le reste de l’écran est intact. Ce n’est pas une copie ressemblante : c’est la vue de
pipeline, à laquelle deux opérations ont été appliquées à la lecture.
Et ensuite
Le module ajoute des données et les montre. Reste à le faire réagir, toujours sans importer ce qu’il étend : Réagir, et s’en aller.