Skip to content

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 parent and no action. It's a top-level entry other modules attach to. The base module provides the Victor root:
<victor>
  <menu id="menu_root" label="Victor" icon="LayoutGrid" sequence="1"/>
</victor>
  • Grouping menu — has a parent but no action, 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. base provides 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.action handlers, so they get registered when the module installs.
  • data: lists the view XML files that carry your <action>, <menu>, and <view> elements.
actions: [actions]
data: [views/menu.xml, views/settings.xml, views/ticket.xml]

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, and graph views an action offers.