Skip to content

3. Completing a screen

The two fields exist and the API returns them. They appear nowhere, because a view lists the fields it shows, and pipeline’s does not know about ours.

The temptation is to declare our own pipeline_lead.form.json. That is the trap. A full view replaces the module’s, so:

  • you copy its thirty fields to add two;
  • the next release of pipeline adds a field, and your copy does not have it;
  • a second extension does the same, and you fight over the same slot.

The patch

A <model>.<kind>.patch.json file layers onto the existing view instead of replacing it.

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"
}
]
}

Nothing to call: register_views_from_dir, which your module already calls for its own screens, recognises the suffix and routes the file.

module is load-bearing. It becomes the registration’s scope key, which is what lets two extensions complete the same screen without overwriting each other. An anonymous patch would be indistinguishable from another one, so the kernel refuses it.

The available operations are add, hide, replace and move. Anchoring is before or after an existing field, and section says which block of the form to place it in.

The same gesture on the list

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"
}
]
}

The labels

A field placed on a screen shows the key <model>.<field>. It is yours, even though the model is not:

{
"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 will never translate your fields: they do not exist for it.

What it gives you

pipeline’s list, with one more column exactly where the anchor put it:

The deal list, with the "Up against" column between "Close date" and "Weighted amount"

And the form, where “Up against” follows “Source” and “Lost to” follows “Lost reason”, inside pipeline’s own “General” section:

A deal&#x27;s form, with the extension&#x27;s two fields placed in the General section

The rest of the screen is untouched. This is not a lookalike copy: it is pipeline’s view, with two operations applied as it is read.

Next

The module adds data and shows it. What remains is making it react, still without importing what it extends: Reacting, and leaving.