Actions & menus¶
A model and its views don't appear anywhere until you give users a way to reach them. That's what actions and menus do:
- An
<action>binds a model to a set of view kinds and puts the result at a URL — one navigable screen. - A
<menu>places that action into Victor's navigation tree so people can click into it. - A
<button>inside a view runs a server-side action on a record and refreshes.
You declare all three in a view XML file — the same kind of file that holds your <view> definitions — wrapped in a top-level <victor> element and listed under data: in your module.yaml. This page walks through each piece using the built-in contact app as the real reference, plus a small helpdesk app (a ticket model) for original examples.
The <action> element¶
An action turns a model plus its views into a screen. Add it to a view XML file, after the <view> definitions it will offer:
<victor>
<!-- ...list, kanban, form, search view definitions above... -->
<action id="action_contacts" name="Contacts" model="contact"
views="list,kanban,form" path="contacts"/>
</victor>
That screen now lives at /a/contacts and opens the contact list by default, with a toolbar to switch to kanban or form.
Attributes¶
| Attribute | Required | Meaning |
|---|---|---|
id |
yes | The action's ref. Other modules reference it fully-qualified as <module>.<id> (e.g. contact.action_contacts). |
name |
no | Screen title / label. Defaults to the action's fully-qualified ref (e.g. contact.action_contacts). |
model |
no | The model whose records the screen shows. |
views |
no | Comma-separated, ordered list of view kinds to offer (see below). |
path |
no | URL segment. The screen is served at /a/<path>. |
There is no domain, context, or target on <action>
Filtering and grouping belong to a search view, not the action — a <filter> inside <search> carries domain and context. The <action> element itself only takes the five attributes above.
Offering multiple views¶
The views attribute is an ordered comma list. The first kind is what opens by default; the rest become toolbar options users can switch to.
| View kind | What it shows |
|---|---|
list |
A table of rows (the whole collection) |
kanban |
Draggable cards grouped by a field (the whole collection) |
card |
A responsive grid of cards (the whole collection) |
form |
A single record's editor |
graph |
A single record's steps as an interactive node graph |
list, kanban, and card are collection views — they show the whole record set — while form and graph are record-level views that open one record at a time. The default (first) kind is therefore normally a collection view, with form and graph reached by drilling into a row.
Common combinations in real modules include:
<action id="action_contacts" name="Contacts" model="contact" views="list,kanban,form" path="contacts"/>
<action id="action_tickets" name="Tickets" model="ticket" views="list,form" path="tickets"/>
<action id="action_modules" name="Modules" model="module" views="card,list" path="modules"/>
Use only the kinds you've actually defined a <view> for. See Views for how each kind is authored.
The <menu> element¶
A menu places an action into the navigation tree. The contact app hangs a Contacts entry off the Victor root:
<menu id="menu" parent="base.menu_root" label="Contacts"
action="contact.action_contacts" icon="Users" sequence="10"/>
Attributes¶
| Attribute | Required | Meaning |
|---|---|---|
id |
yes | The menu's ref. |
parent |
no | Module-qualified ref of the parent menu (e.g. base.menu_root, base.menu_settings). Omit for a top-level root menu. |
label |
no | Display text. Defaults to the menu's fully-qualified ref (e.g. contact.menu). |
action |
no | Module-qualified action ref to open (e.g. contact.action_contacts). Omit for a grouping menu that only holds children. |
icon |
no | An icon name from the Lucide set (e.g. Users, Settings, Bot, Mail, Boxes, LayoutGrid). |
path |
no | URL segment, used by root / settings-style menus (e.g. settings). |
sequence |
no | Integer ordering among siblings. Defaults to 10 — lower sorts first. |
Root menus and grouping menus¶
Whether a menu is a clickable leaf, a top-level root, or a group of tabs depends on which attributes you leave off:
- Leaf menu — has an
action. Clicking it opens that screen. (The Contacts menu above.) - Root menu — has no
parentand noaction. It's a top-level entry other modules attach to. Thebasemodule provides the Victor root:
- Grouping menu — has a
parentbut noaction, and has children. Its children render as a tabbed page: each child menu becomes a header tab. This is exactly how the Settings page works.baseprovides it:
<victor>
<menu id="menu_settings" parent="base.menu_root" label="Settings"
icon="Settings" path="settings" sequence="90"/>
</victor>
Any module can attach top-level apps under base.menu_root, or add tabs under base.menu_settings.
Cross-module references¶
References that point at another module's ref are always module-qualified as <module>.<ref>. A menu's parent= and action= are the common cases:
<menu id="menu" parent="base.menu_root" label="Contacts"
action="contact.action_contacts" icon="Users"/>
Here base.menu_root and contact.action_contacts name refs owned by the base and contact modules. Refs you define in the same file (like the action a menu opens in your own module) still work when written fully-qualified with your module name.
Adding a Settings tab¶
Because the Settings page is just a grouping menu, adding a tab needs no frontend work at all. Define an <action> for your model and a <menu> whose parent is base.menu_settings:
<victor>
<action id="action_helpdesk_settings" name="Helpdesk" model="helpdesk_config"
views="list,form" path="helpdesk-settings"/>
<menu id="menu_helpdesk_settings" parent="base.menu_settings"
label="Helpdesk" action="helpdesk.action_helpdesk_settings" icon="Settings"/>
</victor>
Install the module and a Helpdesk tab appears in Settings automatically.
Tip
The same pattern builds any tabbed group, not just Settings — create your own grouping menu (a menu with a parent and children but no action) and its children become tabs.
View buttons that run server actions¶
A <button> placed inside a view runs a registered server action on the clicked or current record, then refreshes the screen with the result. Buttons work in list, card, and form views — but a few attributes are view-kind-specific: preview is honoured only by list and card buttons, while show_on_new and run_on_save apply only to form buttons (they hinge on the form's create/save flow that collection views don't have). In a form, pair a plain confirm prompt with your button instead of preview — a form button silently ignores preview and just runs the action.
<!-- destructive list / card button with a rich confirmation preview -->
<button action="uninstall" string="Uninstall"
preview="uninstall_preview" variant="destructive"/>
<!-- form-toolbar buttons that only appear in the right state -->
<button action="confirm" string="Confirm" variant="default" invisible="state == 'done'"/>
<button action="archive" string="Archive" confirm="Archive this record?" variant="ghost"/>
Button attributes¶
| Attribute | Views | Meaning |
|---|---|---|
action |
all | Required. The registered server-action name to run. |
string |
all | Button label. |
variant |
all | Visual style: default, soft, ghost, or destructive. |
confirm |
all | Text for a plain confirm prompt shown before the action fires. In a form, this is the way to guard a button — form buttons honour confirm but not preview. |
preview |
list, card | Name of a read-only action that returns an ActionImpact; its contents are shown in a confirm modal, and the real action only runs on confirm. Use instead of confirm when consequences need spelling out. Honoured on list and card buttons only; a form button ignores it and runs the action immediately. |
invisible |
all | Expression evaluated against the persisted record; hides the button in the wrong state (e.g. state == 'done'). |
show_on_new |
form | Set to "1" to also render on an unsaved record — clicking it creates the record from the current form values, then runs the action on it. |
run_on_save |
form | Set to "1" to run the action automatically after every save (create and update), with no click. |
Form buttons and saved records
In a form view a button acts on the current record, so by default it renders only once the record is saved — a brand-new record has nothing to act on yet. show_on_new="1" overrides this by creating first, then acting.
The preview impact shape¶
A preview action is read-only and returns an ActionImpact object that the confirm modal renders:
{
"title": "Uninstall Helpdesk",
"warning": "This permanently deletes ticket data.", # optional
"sections": [
{
"label": "Records removed",
"items": ["1,204 tickets", "38 tags"],
"variant": "danger", # optional: "danger" or "default"
},
],
}
Registering the server action¶
A view <button action="..."> needs a matching server action. Register one with the actions helper from the victor SDK. The handler is async, receives the session and the target record's id, and returns a dict or None:
from victor import actions
@actions.action(model="ticket", name="close")
async def close(session, record_id):
# record_id is the clicked / current record's id
...
return {"ok": True} # or None
| Argument | Meaning |
|---|---|
model |
The model the action attaches to. |
name |
The name a <button action=...> uses to invoke it. Defaults to the function name. |
The preview companion is just another action on the same model that returns an ActionImpact instead of doing the work:
@actions.action(model="ticket", name="close_preview")
async def close_preview(session, record_id):
return {
"title": "Close ticket",
"sections": [{"label": "Effect", "items": ["Marks the ticket done and notifies the customer"]}],
}
Wiring it in module.yaml¶
Two keys connect everything at install time:
actions:lists the Python module(s) that hold your@actions.actionhandlers, so they get registered when the module installs.data:lists the view XML files that carry your<action>,<menu>, and<view>elements.
With those in place, installing the module registers the server actions and loads the actions, menus, and views — your screens show up in the navigation with no further steps.
See also¶
- Models & fields — define the model an action shows.
- Views — author the
list,kanban,form,card, andgraphviews an action offers.