Action Results
Model methods (button actions, wizard confirmations, etc.) can return action result dictionaries that instruct the frontend how to respond.
Result Types
Section titled “Result Types”Closes the current modal/wizard and optionally reloads the parent view.
return {'type': 'close', 'reload': True}
# With notificationreturn { 'type': 'close', 'reload': True, 'notify': 'Operation completed successfully'}| Property | Type | Description |
|---|---|---|
reload | bool | Whether to reload the parent view |
notify | string | Optional success message to display |
notify
Section titled “notify”Shows a notification without closing the modal. color controls the toast type
(blue info — default, green success, yellow/orange warning, red error).
return { 'type': 'notify', 'message': 'This is an informational message', 'color': 'green',}Actionable notifications
Section titled “Actionable notifications”A notification may carry an action — any action dict (e.g. a window_action or
url) — which is rendered as a button on the toast that navigates there. Use it to
turn a notification into a one-click next step (“Invoice posted → View Invoice”). This
is the same mechanism that powers actionable errors (see Exceptions),
so notifications of any colour can deep-link, not just errors.
return { 'type': 'notify', 'message': f'Invoice {invoice.number} posted.', 'color': 'green', 'action': { 'type': 'window_action', 'identifier': 'financial_document_customer_invoice_action', 'record_id': invoice.id, }, 'action_label': 'View Invoice', # optional; defaults to the action's label or "Open"}file_download
Section titled “file_download”Downloads a file to the user’s device. The file content is base64-encoded.
import base64
async def action_export_csv(self): csv_content = "Name,Email\nJohn,john@example.com" csv_bytes = csv_content.encode('utf-8') csv_base64 = base64.b64encode(csv_bytes).decode('utf-8')
return { 'type': 'file_download', 'filename': 'export.csv', 'content': csv_base64, 'mimetype': 'text/csv', }| Property | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | The download filename |
content | string | Yes | Base64-encoded file content |
mimetype | string | No | MIME type (defaults to application/octet-stream) |
Common MIME types:
text/csv- CSV filesapplication/json- JSON filesapplication/pdf- PDF filesapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet- Excel filestext/plain- Plain text files
The modal/wizard stays open after download, allowing users to perform additional actions.
wizard
Section titled “wizard”Opens a wizard modal. Used for chaining wizards or opening a wizard from a button action.
return { 'type': 'wizard', 'model': 'CreateInvoiceWizard', 'title': 'Create Invoice', 'ctx': { 'default_contact': self.contact.id, 'active_model': 'SaleOrder', 'active_ids': [self.id], }}| Property | Type | Description |
|---|---|---|
model | string | The wizard model name |
title | string | Optional modal title |
method | string | Optional method name (defaults to action_confirm) |
ctx | dict | Context passed to the wizard |
update_parent
Section titled “update_parent”Returns values to update the parent form. Useful for wizards that modify the parent record.
return { 'type': 'update_parent', 'values': { 'discount': 10, 'lines': [('C', {'product': 1, 'qty': 5})], }}window_action
Section titled “window_action”Navigates to a record’s form view.
return { 'type': 'window_action', 'action': created_invoice.id, 'model': 'Invoice'}send_message
Section titled “send_message”Opens the send message modal with optional pre-selected template.
return { 'type': 'send_message', 'template_id': 5, # Optional - pre-select template 'modal_type': 'message' # 'message', 'note', or 'followers'}| Property | Type | Description |
|---|---|---|
model | string | Model name (auto-detected from context) |
record_id | int | Record ID (auto-detected from context) |
template_id | int | EmailTemplate ID to pre-select |
modal_type | string | message, note, or followers |
rendered_subject | string | Pre-rendered subject (for transient models without DB records) |
rendered_body | string | Pre-rendered body HTML (for transient models without DB records) |
report_id | int | ReportAction ID to attach as PDF |
contacts | array | Pre-populated recipients: [{id, name, email}] |
Pre-rendered templates: For transient models (wizards with _transient = True), the standard template rendering won’t work because there’s no database record to fetch. Instead, pre-render the template on the backend and pass rendered_subject and rendered_body. See CustomerStatementWizard.action_send_statement() for an example.
report
Section titled “report”Generates and downloads a PDF report.
return { 'type': 'report', 'report_id': report_action.id, 'model': 'Invoice', 'instance_ids': [self.id], # Records to render}
# Or with computed data (for report wizards)return { 'type': 'report', 'report_id': report_action.id, 'model': 'ReportWizard', 'report_data': computed_data, # Passed to template 'contact_id': contact_id, # Optional - for customer language formatting}| Property | Type | Description |
|---|---|---|
report_id | int | ReportAction ID |
model | string | Model name |
instance_ids | array | Record IDs (record-based reports) |
report_data | object | Computed data dict (wizard-based reports) |
contact_id | int | Contact ID for customer-facing date formatting |
Examples
Section titled “Examples”Export Button on Form
Section titled “Export Button on Form”class Contact(Model): async def action_export_vcard(self): """Export contact as vCard file.""" vcard = f"""BEGIN:VCARDVERSION:3.0FN:{self.name}EMAIL:{self.email}TEL:{self.phone}END:VCARD"""
import base64 content = base64.b64encode(vcard.encode('utf-8')).decode('utf-8')
return { 'type': 'file_download', 'filename': f'{self.name}.vcf', 'content': content, 'mimetype': 'text/vcard', }Wizard with Download
Section titled “Wizard with Download”class ExportWizard(Model): _transient = True
format = Selection([ ('csv', 'CSV'), ('json', 'JSON'), ], default='csv')
async def action_download(self): """Generate and download export file.""" active_ids = self._ctx.get('active_ids', []) records = await get_model("Contact").filter(id__in=active_ids).all()
if self.format == 'csv': content = self._generate_csv(records) mimetype = 'text/csv' ext = 'csv' else: content = self._generate_json(records) mimetype = 'application/json' ext = 'json'
import base64 encoded = base64.b64encode(content.encode('utf-8')).decode('utf-8')
return { 'type': 'file_download', 'filename': f'contacts.{ext}', 'content': encoded, 'mimetype': mimetype, }Next Steps
Section titled “Next Steps”- Wizards - Transient models and wizard patterns
- UI Effects - Dynamic field updates