为什么 Odoo 的 return 如此特殊?
在普通 Python 中,return 只是返回一个值。但在 Odoo 中,return 承载着控制用户界面流程的重任 。它不是简单的"返回结果",而是告诉 Odoo 框架 "接下来该做什么"。
💡 核心概念 :在 Odoo 中,控制器和模型方法返回的不是数据,而是操作指令。这些指令告诉 Odoo 前端"下一步该显示什么"。
1. Odoo Action 的本质
什么是 Action?
Action 是 Odoo 中一种特殊的字典结构,它包含:
- 类型:决定要执行什么操作
- 参数:执行操作所需的详细信息
- 上下文:影响操作行为的附加信息
为什么需要 Action?
想象一下餐厅点餐系统:
- 你点了一份披萨(调用方法)
- 厨房可能有多种响应:
- 直接上菜(返回数据)
- 说"需要等15分钟"(显示消息)
- 让你先去取号(跳转到另一个界面)
- 告诉你"没有原料了,请点别的"(错误处理)
Odoo Action 就是这套"厨房响应系统",它让后端可以精确控制前端行为。
2. 详细解析各类 Action
2.1 ir.actions.act_window - 打开新窗口
这是 Odoo 中最常用的 Action 类型,用于打开模型视图。
完整结构
{
'type': 'ir.actions.act_window',
'name': '窗口标题',
'res_model': '目标模型名',
'view_mode': '视图模式列表',
'views': [(view_id, view_type), ...],
'target': '打开方式',
'res_id': 单条记录ID,
'domain': 域过滤条件,
'context': 传递的上下文,
'flags': 额外标志,
}
关键参数详解
|-------------|-------------------------------|----------------------------------------------------------------------|-------------------------|
| 参数 | 值示例 | 说明 | 业务场景 |
| view_mode | 'tree,form' | 优先使用的视图类型 | 列表+表单视图组合 |
| target | 'current' | 打开方式: - current:当前标签页 - new:新标签页 - fullscreen:全屏 - inline:内联 | 编辑记录用current,查看用new |
| res_id | 42 | 特定记录ID | 直接打开某条记录 |
| domain | [('state','=','draft')] | 记录过滤条件 | 只显示草稿状态记录 |
| context | {'search_default_today': 1} | 传递额外上下文 | 设置默认搜索条件 |
| flags | {'mode': 'readonly'} | 视图行为标志 | 以只读模式打开 |
实战示例:打开销售订单列表
def action_view_sales_orders(self):
return {
'type': 'ir.actions.act_window',
'name': _('Sales Orders'),
'res_model': 'sale.order',
'view_mode': 'tree,form',
'domain': [('partner_id', '=', self.id)],
'context': {'default_partner_id': self.id},
'target': 'current',
}
通俗解释:
"系统,请打开销售订单列表视图,只显示与当前客户相关的订单,同时设置新建订单时默认选中此客户,并在当前标签页打开。"
高级技巧
-
强制重载当前视图
'flags': {'reload': True}
相当于用户点击了"刷新"按钮
-
打开特定记录的特定视图
'views': [(False, 'form')],
'res_id': order_id,
直接打开指定ID的表单视图
-
设置默认值
'context': {
'default_partner_id': self.id,
'default_date_order': fields.Date.today()
}
新建记录时自动填充字段
2.2 ir.actions.client - 客户端操作
这类 Action 用于执行前端 JavaScript 操作,不涉及新数据加载。
完整结构
{
'type': 'ir.actions.client',
'tag': '客户端动作标识符',
'name': '动作名称',
'params': 传递给前端的参数,
'target': 'target_type',
}
关键参数详解
|----------|--------------------------------------|----------------------------------------------------------------------------------------|
| 参数 | 值示例 | 说明 |
| tag | 'reload' | 前端预定义的动作标识: - reload:重载当前视图 - display_notification:显示通知 - toggle_fullscreen:切换全屏 |
| params | {'title': '成功', 'type': 'success'} | 传递给前端的参数 |
实战示例:显示成功通知
def action_show_success(self):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Operation Successful'),
'message': _('Your changes have been saved.'),
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'}
}
}
通俗解释:
"前端,请显示一个成功通知:标题是'操作成功',内容是'更改已保存',3秒后自动消失,关闭后执行'关闭窗口'动作。"
高级技巧
-
显示带按钮的通知
'params': {
'title': '提醒',
'message': '会议将在15分钟后开始',
'type': 'warning',
'sticky': True,
'buttons': [{
'text': '查看详情',
'click': '() => window.location.href="/odoo/calendar"'
}]
} -
重载当前视图
return {'type': 'ir.actions.client', 'tag': 'reload'}
相当于用户手动点击了刷新按钮
-
自定义客户端动作
前端定义
odoo.define('my_module.custom_action', function (require) {
return {
action: function (params) {
alert(params.message);
}
};
});后端调用
return {
'type': 'ir.actions.client',
'tag': 'my_module.custom_action',
'params': {'message': 'Hello from backend!'}
}
2.3 ir.actions.act_url - URL 跳转
用于跳转到外部或内部URL。
完整结构
{
'type': 'ir.actions.act_url',
'url': '目标URL',
'target': '打开方式',
}
关键参数详解
|----------|----------------|---------------------------------|
| 参数 | 值示例 | 说明 |
| url | '/web/login' | 要跳转的URL |
| target | 'self' | 打开方式: - self:当前窗口 - new:新窗口 |
实战示例:跳转到视频会议
def action_join_video_call(self):
return {
'type': 'ir.actions.act_url',
'url': self.videocall_location,
'target': 'new'
}
通俗解释:
"请在新窗口中打开这个视频会议链接。"
高级技巧
-
带参数的内部URL
'url': f'/my_custom_page?event_id={self.id}'
-
跳转到Odoo内部路径
'url': '/odoo/app/calendar'
注意:/odoo 前缀是Odoo 15+的特性
-
安全处理外部URL
确保是安全的URL
if not url.startswith(('http://', 'https://')):
url = f"https://{url}"
2.4 ir.actions.do_nothing - 无操作
用于完成操作但不改变界面。
完整结构
{
'type': 'ir.actions.do_nothing',
'warning': {
'title': '标题',
'message': '内容'
}
}
实战示例:仅显示警告
def action_confirm_payment(self):
# 执行付款逻辑...
return {
'type': 'ir.actions.do_nothing',
'warning': {
'title': _("Payment Processed"),
'message': _("The payment has been successfully processed.")
}
}
通俗解释:
"操作已完成,请在当前界面显示这个成功提示,但不要跳转或刷新。"
高级技巧
-
与业务逻辑结合
def action_process(self):
# 处理业务...
if error:
return {
'type': 'ir.actions.do_nothing',
'warning': {'title': '错误', 'message': error_msg}
}
return {'type': 'ir.actions.do_nothing'} # 无提示 -
显示详细错误信息
'warning': {
'title': '验证失败',
'message': '请检查以下字段:\n- 客户信息\n- 付款方式',
'type': 'danger'
}
2.5 ir.actions.server - 服务器动作
用于触发自动化服务器操作。
完整结构
{
'type': 'ir.actions.server',
'name': '动作名称',
'res_model': '目标模型',
'binding_model_id': 模型ID,
'binding_type': 'report' 或 'action',
'usage': 'ir_actions_server',
}
实战示例:触发自动化动作
def action_trigger_automation(self):
server_action = self.env.ref('my_module.my_server_action')
return server_action.run()
通俗解释:
"请执行ID为'my_server_action'的服务器自动化动作。"
高级技巧
-
动态创建服务器动作
action = self.env['ir.actions.server'].create({
'name': '临时动作',
'model_id': self.env['ir.model']._get('res.partner').id,
'state': 'code',
'code': 'records.write({"active": False})'
})
return action.run()
2.6 ir.actions.report - 报表生成
用于生成和显示报表。
完整结构
{
'type': 'ir.actions.report',
'report_type': 'qweb-pdf' 或 'qweb-html',
'report_name': '报表XML ID',
'report_file': '报表文件名',
'context': 报表上下文,
'data': 传递给报表的数据,
}
实战示例:生成销售订单PDF
def action_print_sales_order(self):
return {
'type': 'ir.actions.report',
'report_type': 'qweb-pdf',
'report_name': 'sale.report_saleorder',
'report_file': 'sale.report_saleorder',
'context': {'active_id': self.id},
'data': {'ids': self.ids, 'model': 'sale.order'}
}
通俗解释:
"请使用'sale.report_saleorder'模板生成销售订单的PDF报表。"
高级技巧
-
自定义报表数据
'data': {
'ids': self.ids,
'model': 'sale.order',
'form': {
'date_from': start_date,
'date_to': end_date
}
} -
HTML预览模式
'report_type': 'qweb-html',
用于在网页中直接预览报表
3. Action 使用场景对比
|---------------|-------------------------|-----------------|
| 场景 | 推荐 Action 类型 | 为什么 |
| 打开另一个模型的列表/表单 | ir.actions.act_window | 标准的视图导航机制 |
| 显示成功/错误消息 | ir.actions.client | 不需要加载新数据,只需前端反馈 |
| 跳转到外部网站 | ir.actions.act_url | 安全地处理URL跳转 |
| 完成操作但保持当前界面 | ir.actions.do_nothing | 不改变UI但提供反馈 |
| 执行复杂业务逻辑 | ir.actions.server | 触发预定义的自动化流程 |
| 生成PDF/Excel报表 | ir.actions.report | 专用的报表生成机制 |
4. 实战案例:日历事件中的 Action 链
让我们看看日历事件中一个完整的 Action 流程:
def action_send_invitation(self):
# 1. 检查是否有参会者
if not self.partner_ids:
return {
'type': 'ir.actions.do_nothing',
'warning': {
'title': _('No Attendees'),
'message': _('Please add at least one attendee before sending invitation.')
}
}
# 2. 发送邀请邮件
self.attendee_ids._send_invitation_emails()
# 3. 显示成功通知
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Invitation Sent'),
'message': _('The meeting invitation has been sent to all attendees.'),
'type': 'success',
'sticky': False
}
}
执行流程:
- 用户点击"发送邀请"按钮
- 系统检查是否有参会者
- 如果没有 → 返回
do_nothing显示警告 - 如果有 → 继续执行
- 如果没有 → 返回
- 系统发送邮件
- 返回
clientAction 显示成功通知
5. 常见错误与最佳实践
常见错误
-
返回了错误的 Action 类型
错误:应该返回 act_window 而不是直接返回记录
return self.env['sale.order'].browse(order_id)
-
缺少必要参数
错误:缺少 res_model
return {'type': 'ir.actions.act_window', 'view_mode': 'form'}
-
返回多个 Action
错误:一个方法只能返回一个 Action
if condition1:
return action1
if condition2:
return action2...可能没有返回值!
最佳实践
-
始终检查返回值
def safe_action(self):
if not self:
return {'type': 'ir.actions.do_nothing'}
# 正常逻辑... -
使用常量定义 Action 类型
ACT_WINDOW = 'ir.actions.act_window'
ACT_CLIENT = 'ir.actions.client'return {'type': ACT_WINDOW, ...}
-
封装常用 Action
def _return_success_notification(self, message):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {'title': '成功', 'message': message, 'type': 'success'}
}使用
return self._return_success_notification('操作已完成')
-
考虑多记录场景
def action_view_related(self):
# 处理单条和多条记录
if len(self) == 1:
return {..., 'res_id': self.id}
return {..., 'domain': [('id', 'in', self.ids)]}
6. 调试技巧
如何查看实际返回的 Action
-
在控制器中添加日志
_logger.info("Returning action: %s", str(action))
-
使用浏览器开发者工具
- 打开网络(Network)标签
- 查看XHR请求的响应
- 搜索"actions"关键字
-
临时修改方法
def action_debug(self):
action = self.original_action()
_logger.warning("DEBUG ACTION: %s", action)
return action
常见问题排查
- 点击按钮无反应
- 检查是否返回了有效的 Action
- 确认没有抛出未捕获的异常
- 视图显示不正确
- 检查
view_mode和views参数 - 确认目标模型有相应的视图
- 检查
- 上下文未生效
- 检查
context是否正确传递 - 确认前端是否使用了上下文
- 检查
7. 高级应用:Action 组合
案例:创建事件后自动发送邀请
def action_create_and_send(self, values):
# 1. 创建事件
event = self.create(values)
# 2. 发送邀请
event.attendee_ids._send_invitation_emails()
# 3. 返回两个操作:
# a) 关闭当前窗口
# b) 显示成功通知
return {
'type': 'ir.actions.client',
'tag': 'multi_action',
'params': {
'actions': [
{'type': 'ir.actions.act_window_close'},
{
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '会议已创建',
'message': f'邀请已发送给 {len(event.partner_ids)} 位参会者',
'type': 'success'
}
}
]
}
}
注意:此示例需要自定义前端处理,标准Odoo不支持直接返回多个Action。实际应用中通常使用序列化操作或客户端动作链。
总结:Odoo Action 的核心原则
- 一个方法,一个 Action
一个方法应该只返回一个明确的Action,避免逻辑分支导致多种返回
- 语义明确
Action 类型应准确反映操作意图,不要混用类型
- 用户友好
每个Action都应提供清晰的用户反馈,避免"静默操作"
- 上下文感知
Action 应考虑当前上下文(单记录/多记录、用户权限等)
- 可预测性
相同操作在相同条件下应产生相同UI响应
理解并熟练运用Odoo的Action系统,是开发流畅、专业 的Odoo应用的关键。它不仅是技术实现,更是用户体验设计的重要组成部分。