Extending other modules¶
You rarely build a module in isolation. Often you want to add a field to a model that another module already owns, or slot one of your fields into a form that another module already renders. Victor lets you do both without touching the other module's source — you declare the change from your own module, and the framework merges it in.
There are two kinds of extension:
- Model extension — add fields to a model another module defines.
- View extension — insert, replace, or remove nodes in a view another module defines.
Both follow the same rule: you may only extend something that belongs to a module you
depend on. That dependency is declared once, in module.yaml.
Prerequisites
This page assumes you already know how to define your own models & fields and views. Extension reuses exactly those building blocks — you are just pointing them at something that already exists.
Declare the dependency¶
You can only extend a model or view that lives in a module listed in your requires. Add
the owning module's technical name there:
name: acme_crm
label: Acme CRM
version: 0.1.0
requires: [contact]
models: [models]
data: [views/lead.xml, views/inherit_contact.xml]
requires does two things: it guarantees the contact module is installed and loaded
before yours, and it grants you permission to reach into its models and views. Without it,
your extension has nothing valid to target.
models: registers your Python model file(s); data: loads your view XML — the same keys
you use for any module. Extensions are just ordinary model and view files that happen to
target existing names.
Add fields to another module's model¶
To extend a model, declare a models.Model whose name equals the target model's
technical name, and add your new fields on it. Setting name to a name that already
exists tells Victor "extend this model" rather than "create a new one".
from victor import fields, models
class ContactExt(models.Model):
name = "contact" # match the existing model's technical name to extend it
lead_count = fields.Integer(label="Leads")
Here contact is a model owned by the built-in contact app. Because your module.yaml
requires contact, this class merges a new lead_count field into it. Records of contact
now carry that field everywhere they are used.
The class name doesn't matter
Only the name attribute is significant. ContactExt, MyContactFields — call the
Python class whatever reads well. Victor keys the extension off name = "contact", not
off the class name.
The distinction is entirely in name:
name value |
Meaning |
|---|---|
| A technical name no module has defined yet | Defines a new model. |
| A technical name an existing model already uses | Extends that model — your fields are added to it. |
You add fields exactly as you would on a model of your own — any fields.* type is fair
game. Extension is additive: the fields you declare are merged into the base model, so
new names simply appear alongside the ones already there. You cannot remove a base field or
change the model's identity (its technical name) from an extension.
A same-name field overrides the base field
Additive is the common case, but there is one nuance. If a field you declare uses the same name as one the base model already defines, your definition replaces that field rather than adding a second copy — the last module to declare the name wins. Reach for this deliberately when you want to retune an existing field (say, relabel it); pick a fresh name whenever you mean to add something new.
Extend an existing view¶
To change a view another module renders, use the <extend> element at the root of your view
file (inside <victor>). Instead of describing a whole new view, <extend> describes a set
of edits applied to an existing one.
<victor>
<extend id="contact_form_lead" view="contact.view_form">
<after field="email"><field name="lead_count"/></after>
</extend>
</victor>
This locates the email field in the contact module's view_form and inserts your
lead_count field directly after it.
The <extend> element¶
| Attribute | Required | Description |
|---|---|---|
id |
yes | A name for your extension. Unique within your module. |
view |
yes | Qualified reference to the base view you are editing: <module>.<view_id>, e.g. contact.view_form. |
priority |
no | Orders this extension relative to other extensions of the same view. Defaults to 16; lower runs earlier. Only relevant when several modules extend the same view. |
The view reference is always qualified with the owning module — that is how Victor
knows which base view to load, and it must be a view in a module you require.
Directive ops¶
Each child of <extend> is a directive op: it locates a node in the base view and edits
it. The available ops:
| Op | Effect |
|---|---|
<after> |
Insert the op's children immediately after the target node. |
<before> |
Insert the op's children immediately before the target node. |
<inside> |
Append the op's children into the target node (as its last children). |
<replace> |
Swap the target node for the op's children. |
<remove> |
Delete the target node. |
<set> |
Write the op's other attributes onto the target node. |
after, before, inside, and replace carry a fragment (the nodes to insert or
substitute) as their children. remove takes no children. set takes no children — it
carries attributes that are copied onto the target.
Locating the target node¶
Every op names exactly one node to act on, using one of three locator attributes:
| Locator | Targets |
|---|---|
field="<name>" |
The <field name="<name>"> element — the common case. |
id="<id>" |
The element carrying that id. |
select="<path>" |
A path selector: steps like tag[@attr='val'] separated by /, with .. to step up to the parent. |
Use field= whenever you are targeting a field (nearly always). Reach for id= when a node
carries an explicit id, and select= for structural nodes that a single field/id can't
pinpoint.
Putting the ops together¶
The following shows every op and locator. Only <after> with field= appears in the
shipped example above; the rest follow the same shape.
<extend id="my_ext" view="contact.view_form">
<!-- locate a node by field=, id=, or select= -->
<after field="email"><field name="lead_count"/></after>
<before field="stage"><field name="note"/></before>
<inside id="details"><field name="extra"/></inside>
<replace field="stage"><field name="stage" widget="..."/></replace>
<remove field="stage"/>
<set field="email" widget="email"/>
</extend>
Read each op as "find X, then do Y": <after field="email">…</after> finds the email
field and places its children after it; <set field="email" widget="email"> finds the
email field and sets widget="email" on it; <remove field="stage"/> deletes the stage
field entirely.
The target must exist
An op fails if its locator matches no node in the base view. If the base module renames or removes a field you targeted, your extension breaks — keep locators aligned with the version of the module you require.
A model-only extension¶
Not every extension touches a view. If all you need is an extra field, a model extension on
its own is enough — no view file, no data: entry.
# models.py
from victor import fields, models
class WidgetExt(models.Model):
name = "widget" # extends the existing widget model
note = fields.String(label="Note")
# module.yaml
name: extmod
label: Widget Extension
version: 0.1.0
requires: [widget]
models: [models]
That is a complete, valid module. It requires widget, adds a note field to the existing
widget model, and does nothing else. Add a view extension later if and when you want that
field to appear on screen.
Checklist¶
To extend another module, make sure you have:
- Listed the owning module in
requires:in yourmodule.yaml. - For a model extension — a
models.Modelwhosenameequals the target model's technical name, registered undermodels:. - For a view extension — an
<extend view="<module>.<view_id>" id="…">block with one or more directive ops, in a file registered underdata:.
From here, see Models & fields for the field types you can add and Views for the full view DSL your inserted fragments are written in.