Skip to content

4. Reacting, and leaving

Our extension needs to know that a deal has just been lost, so it can check that somebody named the winner. It is not going to ask pipeline. It listens.

The listener

"""Reacting to ``pipeline`` without importing it.
The extension needs to know when an opportunity is lost. It does NOT call into
``pipeline`` to find out: it listens.
That is the whole difference between extending a module and forking it. The
event is a public fact; the code behind it is not.
"""
from kewos.events import subscribe
#: Emitted by the kernel on every write. We filter on the model rather than ask
#: `pipeline` for a dedicated event: a module we extend owes us nothing.
_WRITE_EVENT = "record.updated"
def note_the_winner(event: Event) -> None:
"""When an opportunity is marked lost, check that a winner was named."""
payload = event.payload or {}
if payload.get("model") != "pipeline_lead":
return
record = payload.get("record")
if record is None or getattr(record, "state", None) != "lost":
return
if getattr(record, "rivals_won_by_id", None) is None:
_log.info("opportunity %s was lost with no competitor named", record.id)
def register_listeners() -> None:
"""Subscribe on module load."""
subscribe(_WRITE_EVENT, note_the_winner)

That is the whole contract. The kernel emits record.updated on every write; we filter on the model. We do not ask pipeline for a dedicated event, because a module you extend owes you nothing: what it publishes, it publishes for everyone.

It fixes nothing, it says so

The handler does not write. It logs the omission so a review can see it. Filling the field in place of the human would be guessing, and it would poison exactly the report this module exists to produce: an invented winner counts as a winner.

The same need, with no code

What this file does can also be placed in the visual engine and shipped with the module, the way the first walkthrough does in its “Automating” step. The question to settle is always the same: is this rule yours, or the customer’s? What follows from your design judgement goes in code; what the customer will want to change without you goes in a flow.

Uninstalling without a trace

This is the test that tells you whether you extended or forked. Uninstall pipeline_rivals:

  • rivals_competitor goes: it was our table;
  • pipeline_lead loses two contributed columns, and nothing else;
  • the view patch goes, and pipeline’s view is what it was, because it was never replaced;
  • the subscription goes with the process.

None of this needed uninstall code. It follows directly from the three choices in the previous steps: our model carries our prefix, so do our fields, and our screen is a patch.

Now run the opposite in your head. If you had copied pipeline_lead.form.json to slip two fields in, uninstalling would leave you the copy, frozen at the version you took it from.

The finished extension, to download

  • pipeline_rivals-0.1.0.kws (7 KB) — this walkthrough’s module, with its two view patches, its listener and its demo data.
74af7d7e9382e16b26815412a4f29d6e608a961180f416feabf0882c14f2619f pipeline_rivals-0.1.0.kws
Fenêtre de terminal
sha256sum pipeline_rivals-0.1.0.kws
kewos module install pipeline_rivals-0.1.0.kws \
--database-url "$KEWOS_DATABASE_URL" \
--modules-root "$KEWOS_MODULES_ROOT" \
--allow-unsigned
kewos reconcile --confirm

pipeline has to be installed and activated first, or the registry freeze stops on the missing dependency, naming it. The full procedure, and why the package is unsigned, are in the last step of the first walkthrough, Shipping.

The checklist

Before shipping an extension, these five answers must be “yes”:

  1. grep -r "import modules\." my_module/ returns nothing.
  2. Everything I created carries my module’s prefix.
  3. My added fields are nullable=True.
  4. My screens on other people’s models are *.patch.json, not full views.
  5. My manifest declares depends on what I extend.

Next

You can build a module and extend one. What remains is the same work as anywhere else: packaging and installing it, described in the last step of the first walkthrough, Shipping.