The ir.actions System
Every click in Odoo's interface triggers an action. When you click a menu item, a button, or a link, Odoo resolves an ir.actions record that tells the web client what to do: open a view, run server code, generate a report, or navigate to a URL. Understanding actions is key to building any Odoo interface.
ir.actions.act_window
The most common action type. It opens a view (or set of views) for a model:
<record id="action_support_tickets" model="ir.actions.act_window">
<field name="name">Support Tickets</field>
<field name="res_model">support.ticket</field>
<field name="view_mode">list,form,kanban,pivot,graph</field>
<field name="domain">[('active', '=', True)]</field>
<field name="context">{
'default_priority': '1',
'search_default_filter_my': 1,
}</field>
<field name="search_view_id" ref="view_ticket_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first support ticket
</p>
</field>
</record>
Key Attributes
res_model: the model to displayview_mode: comma-separated list of view types (list, form, kanban, pivot, graph, calendar, activity)domain: default filter applied to all viewscontext: default values and search defaultstarget:current(default, replaces content),new(dialog/modal),inline(embedded),fullscreenres_id: open a specific record directly in form view
Opening a Specific Form
<record id="action_ticket_form" model="ir.actions.act_window">
<field name="name">Ticket Details</field>
<field name="res_model">support.ticket</field>
<field name="view_mode">form</field>
<field name="res_id" eval="ref('demo_ticket_1')"/>
</record>
Opening as a Dialog
<record id="action_ticket_wizard" model="ir.actions.act_window">
<field name="name">Assign Ticket</field>
<field name="res_model">ticket.assign.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
Returning from Python
def action_open_related(self):
return {
'type': 'ir.actions.act_window',
'name': 'Related Records',
'res_model': 'related.model',
'view_mode': 'list,form',
'domain': [('ticket_id', '=', self.id)],
'context': {'default_ticket_id': self.id},
}
ir.actions.server
Server actions execute Python code, send emails, create records, or chain multiple actions. They are the backbone of Odoo's automation system:
<record id="action_send_reminder" model="ir.actions.server">
<field name="name">Send Reminder Email</field>
<field name="model_id" ref="model_support_ticket"/>
<field name="state">code</field>
<field name="code">
for record in records:
if record.state == 'new' and record.user_id:
record.message_post(
body='Reminder: This ticket needs attention.',
partner_ids=[record.user_id.partner_id.id])
</field>
</record>
Server Action States
code: execute Python code (withrecords,env,modelvariables available)object_create: create a new recordobject_write: update the current recordmulti: run multiple child actions in sequenceemail: send an email using a templatefollowers: subscribe followers to the recordnext_activity: schedule a next activity
ir.actions.client
Client actions trigger JavaScript on the frontend without a server round-trip:
<record id="action_dashboard" model="ir.actions.client">
<field name="name">Support Dashboard</field>
<field name="tag">support_dashboard</field>
<field name="params">{'show_graph': true}</field>
</record>
The tag maps to a JavaScript action registered on the frontend. Built-in client actions include display_notification and reload:
# Show a notification
def action_notify(self):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': 'Success',
'message': 'Operation completed.',
'type': 'success',
'sticky': False,
},
}
# Reload the page
def action_reload(self):
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
ir.actions.act_url
Open a URL in the browser:
def action_open_website(self):
return {
'type': 'ir.actions.act_url',
'url': 'https://www.example.com',
'target': 'new', # 'new' = new tab, 'self' = same tab
}
# Dynamic URL
def action_open_map(self):
return {
'type': 'ir.actions.act_url',
'url': f'https://maps.google.com/?q={self.address}',
'target': 'new',
}
ir.actions.report
Generate and download PDF/HTML reports:
<record id="action_report_ticket" model="ir.actions.report">
<field name="name">Ticket Report</field>
<field name="model">support.ticket</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">my_module.report_ticket</field>
<field name="report_file">my_module.report_ticket</field>
<field name="print_report_name">'Ticket_%s' % object.name</field>
<field name="binding_model_id" ref="model_support_ticket"/>
<field name="binding_type">report</field>
</record>
The binding_model_id adds the report to the Print menu on the model's form view. report_type can be qweb-pdf or qweb-html.
Returning from Python Methods
# Return an action from a button click
def action_do_something(self):
# ... perform logic ...
# Return an action dict to navigate
return {
'type': 'ir.actions.act_window',
'res_model': 'result.model',
'view_mode': 'form',
'res_id': result.id,
'target': 'current',
}
# Or return nothing to stay on current view
# (implicit return None)
Chaining Actions
Use multi-type server actions to chain multiple actions:
<record id="action_chain" model="ir.actions.server">
<field name="name">Process and Notify</field>
<field name="model_id" ref="model_support_ticket"/>
<field name="state">multi</field>
<field name="child_ids" eval="[
(4, ref('action_process')),
(4, ref('action_send_email')),
]"/>
</record>