Odoo 三类模型怎么选:Model、TransientModel、AbstractModel 实战指南
Odoo 三类模型选型指南:Model、TransientModel、AbstractModel 怎么选,选错会怎样
很多 Odoo 项目后期越做越难维护,根本原因不是字段不会加、视图不会改,而是模型职责一开始就放错了位置:该长期留痕的做成临时向导,该复用的能力写成复制粘贴,该放在业务模型里的规则被塞进按钮方法里。
文章目录
- Odoo 三类模型选型指南:`Model`、`TransientModel`、`AbstractModel` 怎么选,选错会怎样
-
- 0. 一句话先对齐
- 1. 底层原理:为什么 Odoo 要分三类模型
- 2. `models.Model`:业务事实层
- 3. `models.TransientModel`:交互参数层(Wizard)
- 4. `models.AbstractModel`:能力复用层(Mixin)
- 5. 三类模型协同的正确方式(实战架构)
- 6. 常见反模式(深坑)
- 7. 选择判断矩阵(团队评审可直接用)
- 8. 进阶补充:几个容易被忽略的底层开关
- 9. 多公司(multi-company)场景下的模型设计要点
- 10. 故障排查清单(模型选型相关)
- 11. 继承机制深水区:`_inherit` vs `_inherits`
- 12. 事务、缓存与并发:模型层必须理解的运行时语义
- 13. 架构评审清单(高级版)
- 14. 三类模型在 Odoo 升级迁移中的风险地图
- 15. 总结
阅读提示(CSDN 发布版):
本文偏中高级实践,重点不是“语法怎么写”,而是“模型选型如何影响权限、事务、迁移和长期治理成本”。你可以先看第 0、5、7、14、15 节,再回头读细节。
0. 一句话先对齐
models.Model:业务实体,长期数据,面向流程与审计。models.TransientModel:交互参数,短生命周期,面向向导。models.AbstractModel:能力抽象,不是业务实体,面向复用。
三者差别表面是 ORM 继承写法,实质上是数据生命周期、权限边界、事务语义和长期治理成本的差别。
0.1 快速导航(5 分钟速读)
- 想快速决策怎么选:看 第 5 节 + 第 7 节
- 想避免线上事故:看 第 6 节 + 第 12 节
- 想过架构评审 / 升级迁移:看 第 13 节 + 第 14 节

1. 底层原理:为什么 Odoo 要分三类模型
1.1 ORM 视角:模型 = 数据契约
在 Odoo 中,模型不仅定义字段,还定义:
- 数据是否要长期存在(生命周期)
- 是否参与业务状态流转(状态机)
- 是否被规则系统审计(访问、变更、消息追踪)
- 是否只是某次交互的参数容器
如果模型类型和职责不一致,后续会出现:
- 升级后大量“无用历史记录”拖慢库
- 权限不可解释(用户看到不该看的向导数据)
- 报表口径混乱(临时数据被当业务事实)
小结:模型不是“字段容器”,而是业务契约。如果契约错了,后面所有层都会补锅。
1.2 三类模型对应的架构职责
| 维度 | Model |
TransientModel |
AbstractModel |
|---|---|---|---|
| 数据生命周期 | 长期 | 短期(自动清理) | 无实体数据(能力层) |
| 业务语义 | 强 | 弱(交互参数) | 无业务语义(复用语义) |
| 适配对象 | 单据/主数据 | Wizard 弹窗 | Mixin、公用方法 |
| 典型错误 | 过度耦合 | 滥存业务数据 | 放进强耦合流程 |
1.3 很多人第一次就会选错的 3 个场景
- 批量审批弹窗:把审批结论落到
TransientModel,后续审计找不到。 - 导入中间数据:把 staging 表当正式业务
Model,导致口径污染。 - 通用编码/归档能力:在多个模型复制粘贴,而不是抽到
AbstractModel。
2. models.Model:业务事实层
2.1 什么时候必须用 Model
满足任一条基本就应使用:
- 需要长期保存并可追溯
- 要参与审批/状态机
- 要被报表、统计、对账消费
- 需要完整权限模型(
groups+ir.rule)
2.2 设计实践(不止字段)
- 状态字段:尽量有
state,让流程清晰。 - 约束:用 SQL constraint 或
@api.constrains保证业务不变量。 - 审计:关键模型可结合
mail.thread,保留变更痕迹。 - 性能:高频筛选字段考虑
index=True。
2.3 案例 A:设备巡检计划(业务实体)
from odoo import api, fields, models
from odoo.exceptions import ValidationError
class DeviceInspectionPlan(models.Model):
_name = "device.inspection.plan"
_description = "设备巡检计划"
name = fields.Char(required=True)
device_id = fields.Many2one("maintenance.equipment", required=True)
owner_id = fields.Many2one("res.users", required=True)
next_date = fields.Date(required=True)
state = fields.Selection(
[("draft", "草稿"), ("running", "执行中"), ("done", "已完成")],
default="draft",
required=True,
)
_sql_constraints = [
("plan_name_device_uniq", "unique(name, device_id)", "同一设备下计划名称不能重复"),
]
@api.constrains("next_date")
def _check_next_date(self):
for rec in self:
if rec.next_date and rec.next_date < fields.Date.today():
raise ValidationError("下次巡检日期不能早于今天。")
这个案例的关键点
- 这是业务事实,必须持久化。
- 规则(唯一性、日期校验)写在模型层,避免“前端一套、导入一套”。
小结:
Model的核心是“可追溯 + 可约束 + 可统计”。
3. models.TransientModel:交互参数层(Wizard)
3.1 为什么它必须是“短命”
向导本质上只是一次交互动作的参数容器,它服务于业务动作,但不应该冒充业务事实本身。
如果你把它当业务数据存档,会导致:
- 数据表膨胀(大量历史向导垃圾)
- 审计误导(看起来像业务单据,实际只是临时参数)
- 报表污染(误统计)
另外要强调:TransientModel 的“短命”不是团队习惯,而是框架契约——系统会清理此类临时记录,因此天然不适合作为长期追溯载体。
3.2 设计实践
- 通过
context读取active_model/active_ids。 - 向导里只做“参数收集 + 调用业务模型方法”,不要把核心业务逻辑全塞向导。
- 权限校验应落在目标业务模型方法中,不要假设向导天然安全。
3.3 案例 B:批量改负责人向导
from odoo import fields, models
from odoo.exceptions import UserError
class ChangeOwnerWizard(models.TransientModel):
_name = "device.change.owner.wizard"
_description = "批量修改负责人"
user_id = fields.Many2one("res.users", required=True)
def action_apply(self):
self.ensure_one()
active_ids = self.env.context.get("active_ids", [])
if not active_ids:
raise UserError("未选择记录。")
plans = self.env["device.inspection.plan"].browse(active_ids)
# 关键:业务权限/约束应在 Model 层 write 或业务方法里兜底
plans.write({"owner_id": self.user_id.id})
return {"type": "ir.actions.act_window_close"}
这个案例的关键点
- 向导不负责持久业务语义,只负责“发起批量动作”。
- 真正的业务约束应在
device.inspection.plan层保证。
小结:向导是“入口”,不是“真相源”。
4. models.AbstractModel:能力复用层(Mixin)
4.1 它解决的核心问题
不是“少写几行代码”,而是:
- 统一多个模型的行为边界
- 降低重复实现导致的规则漂移
- 让审计与维护有可预测结构
4.2 设计实践
- 抽象模型应尽量保持“低耦合、高复用”。
- 放“通用能力”:编码生成、归档启停、通用校验、通用动作。
- 不要放“只适用于单个业务域”的强流程逻辑。
4.3 AbstractModel 适合放什么,不适合放什么
适合放:
- 通用归档/启用
- 编码生成与格式化
- 通用校验辅助方法
- 通用状态切换工具
不适合放:
- 某个业务域独有审批流
- 强依赖特定模型字段才能运行的方法
- 需要知道具体单据状态机细节的流程逻辑
4.4 案例 C:通用“归档 + 编码”能力
from odoo import fields, models
class BusinessBaseMixin(models.AbstractModel):
_name = "business.base.mixin"
_description = "业务基础能力 Mixin"
code = fields.Char(copy=False, index=True)
active = fields.Boolean(default=True)
def action_archive(self):
self.write({"active": False})
def action_activate(self):
self.write({"active": True})
具体模型继承:
class DeviceInspectionPlan(models.Model):
_name = "device.inspection.plan"
_inherit = ["business.base.mixin"]
这个案例的关键点
- 归档行为在多个模型上一致。
- 后续改规则(比如归档前检查)时只改一处。
小结:
AbstractModel不是省几行代码,而是降低规则漂移。
5. 三类模型协同的正确方式(实战架构)
推荐分层:
Model:业务核心规则与事实数据TransientModel:入口交互(参数收集)AbstractModel:跨模型复用能力
可理解为:
- 事实层(Model)
- 交互层(Transient)
- 能力层(Abstract)
这个分层能显著降低“向导里写满业务逻辑”“复制粘贴方法四处飘”的问题。
5.1 快速判断流程图(文本版)
- 这份数据是否需要长期留存、可追溯、可报表?
- 是 ->
Model - 否 -> 继续
- 是 ->
- 是否只是一次性交互参数(弹窗、批量动作输入)?
- 是 ->
TransientModel - 否 -> 继续
- 是 ->
- 是否是多个模型都需要的通用能力?
- 是 ->
AbstractModel - 否 -> 回到业务对象边界重新建模
- 是 ->
5.2 一张图记住(简版)
- 业务事实 ->
Model - 交互参数 ->
TransientModel - 复用能力 ->
AbstractModel
6. 常见反模式(深坑)
反模式 1:用 TransientModel 存长期审批记录
- 结果:数据被清理,审计断链。
- 正解:审批记录必须建
Model。
反模式 2:把向导当权限边界
- 结果:用户可绕过向导直接 RPC 调模型,导致越权。
- 正解:权限校验在业务模型方法中落地。
反模式 3:AbstractModel 塞业务流程
- 结果:继承树复杂,某个模型不适配也被迫继承。
- 正解:抽象层只放“普适能力”,业务流程留在具体
Model。
反模式 4:把“导入中间表”当正式业务模型
- 结果:报表口径混乱、冗余数据持续膨胀。
- 正解:中间态可用
TransientModel或清理策略明确的 stagingModel(有 TTL/归档策略)。
反模式 5:错误示范——把审批意见写进向导
class ApproveWizard(models.TransientModel):
_name = "approve.wizard"
note = fields.Text()
approve_user_id = fields.Many2one("res.users")
看起来“审批完成了”,但审批意见存在临时模型,清理后审计链断掉。审批意见与结论应沉淀在业务 Model。
这类问题最危险:开发环境看起来“功能可用”,上线后才发现“证据链缺失”。
7. 选择判断矩阵(团队评审可直接用)
| 评审问题 | 选择 |
|---|---|
| 数据是否要在 3 个月后还能追溯? | Model |
| 只是本次弹窗操作参数? | TransientModel |
| 是否有 2 个以上模型重复同一能力? | AbstractModel |
| 是否涉及审批、审计、报表口径? | Model(必要时 + Abstract 复用) |
8. 进阶补充:几个容易被忽略的底层开关
8.1 _auto:是否让 ORM 自动建表
- 默认
_auto = True:模型对应数据库物理表(常见于Model/TransientModel)。 _auto = False:ORM 不自动建表,常用于SQL 视图模型或特殊场景(通常配合init建视图)。
实战建议:业务主模型不要随意
_auto=False,否则迁移、升级、权限与统计链路复杂度会显著提高。
8.2 _log_access:审计字段记录
- 典型审计字段:
create_uid/create_date/write_uid/write_date。 - 审计敏感模型建议开启(默认一般开启),方便排查“谁改了什么”。
如果你在某些临时模型上关闭或弱化审计,务必确认它不承担合规责任。
8.3 _rec_name:记录显示名称
- 决定 many2one 下拉默认显示字段。
- 如果不设,常回落到
name;某些业务模型没有name时建议显式声明_rec_name。
8.4 SQL 约束与 Python 约束如何配合
- SQL 约束:强一致性、并发安全(唯一性、非空等底线约束)。
@api.constrains:适合复杂业务校验(跨字段语义、业务规则)。
实战策略:关键底线先用 SQL 约束兜底,再用 Python 约束给出可读错误信息。
8.5 这节怎么落地到日常开发
- 新建模型 PR:必须说明
_rec_name、约束策略、是否需要审计字段 - 评审并发敏感逻辑:先看 SQL 约束,再看 Python 校验
- 涉及报表口径字段:优先评估是否
store=True,并补依赖说明
9. 多公司(multi-company)场景下的模型设计要点
9.1 Model
- 明确是否需要
company_id;需要隔离的数据必须有公司维度。 - 规则通过
ir.rule限制读取范围,避免跨公司误读。
9.2 TransientModel
- 向导中的默认值、可选记录应带公司上下文;批量动作要防止跨公司误操作。
active_ids来源必须可校验,不能盲写。
9.3 AbstractModel
- 抽象能力尽量不写死单公司逻辑,避免继承到多公司模型后出现隐式假设。
9.4 一个常见事故模式
- 向导用
sudo()+ 未校验active_ids,导致跨公司批量修改。 - 正解:业务写入前在目标
Model层做公司一致性与权限校验。
10. 故障排查清单(模型选型相关)
| 现象 | 高概率原因 | 优先检查 |
|---|---|---|
| 向导数据“消失” | 把业务数据放到了 TransientModel |
模型继承类型、数据生命周期 |
| 记录显示成 ID | _rec_name 未配置且无 name |
模型显示字段设置 |
| 重复单据并发出现 | 仅 Python 校验,无 SQL 唯一约束 | _sql_constraints |
| 多公司误修改 | 向导层绕过权限,模型层无兜底 | sudo()、ir.rule、company 校验 |
| 代码复用后耦合爆炸 | AbstractModel 放了特定业务流程 |
抽象层职责边界 |
11. 继承机制深水区:_inherit vs _inherits
这一节是很多中高级开发也会混淆的点。三类模型选型正确后,若继承策略错了,复杂度仍然会爆炸。
Model / TransientModel / AbstractModel解决的是“对象生命周期与职责边界”问题;_inherit / _inherits解决的是“字段与代码复用方式”问题。两组概念相关,但不是同一维度。
11.1 _inherit(类继承 / 原模型扩展)
- 语义:在同一模型语义上追加字段与方法。
- 常见用途:给
sale.order增字段、改流程、加约束。 - 风险:多个模块同时 override 同一方法时,调用顺序和
super()链容易出问题。
11.2 _inherits(组合继承 / 委托)
- 语义:通过
Many2one组合另一个模型字段,形成“主从组合”。 - 优点:能复用另一模型字段而不直接改它。
- 成本:读写链更复杂,删除策略、权限、导入导出与报表都要额外考虑。
11.3 何时用哪个(经验准则)
| 场景 | 推荐 |
|---|---|
| 你只是扩展同一业务对象 | _inherit |
| 你在做“一个新对象 + 复用已有对象字段集” | _inherits(谨慎) |
| 团队经验不足、上线时间紧 | 优先 _inherit,避免过早 _inherits |
11.4 三类模型与继承的搭配建议
Model:_inherit最常见;_inherits仅在明确组合模型价值时使用。TransientModel:通常只做_inherit扩展向导行为,少做复杂_inherits。AbstractModel:用于被_inherit混入多个模型,减少重复实现。
12. 事务、缓存与并发:模型层必须理解的运行时语义
如果前面几节解决的是“该怎么选模型”,这一节开始解决的是“模型选对后,为什么上线仍会出问题”。
12.1 事务边界
- Odoo 请求通常在事务中执行;异常会回滚。
- 如果你在方法里混入外部调用(HTTP、文件 IO)且无超时,事务会被拖长,放大锁冲突。
设计原则:重外部调用尽量异步化,或先落业务状态再回调。
12.2 ORM 缓存与计算字段
- 计算字段、预取、缓存会提升性能,但也带来“看起来更新了、实际未持久化”的认知误差。
store=True字段必须依赖写全;否则数据陈旧比慢查询更危险。
12.3 并发写入与唯一性
- Python 层校验在并发下不可靠,关键唯一性必须 SQL 约束兜底。
- 向导批量写建议在业务模型方法统一入口处理,避免多个入口产生竞态。
12.4 选型错误与性能维护成本的关系
TransientModel滥用:临时表累积与清理压力上升,排障变难。Model滥建:主库冗余数据上升,查询与迁移成本增加。AbstractModel过耦合:继承链复杂,变更影响面扩大,回归成本上升。
12.5 给团队的一条硬规则
业务规则最终约束必须落在
Model;helper/service 可以组织流程,但不能成为业务真相的唯一来源。
13. 架构评审清单(高级版)
在 MR 或设计评审时,可以用下面的问题快速判断模型方案是否健康:
- 这个对象的数据生命周期是什么?为什么不是
TransientModel/Model的另一种? - 这个规则为什么放在这里?模型层有没有统一事实来源?
- 并发下会不会重复写入?SQL 约束在哪里?
- 多公司场景是否有 company 边界和
ir.rule兜底? - 这个抽象是否真的可复用,还是把业务耦合塞进了
AbstractModel? - 是否需要
_inherits?不用它能否更简单地完成目标? - 这个方法是否会在事务里阻塞外部依赖?
13.1 Code Review 追问清单(可直接粘贴到 MR 模板)
- 为什么这个对象不是
Model(或不是TransientModel)? - 权限校验为什么放在这里?是否在业务模型层有兜底?
- 这个 mixin 是否依赖了具体业务字段?
- 并发下会不会重复写入?SQL 唯一约束在哪里?
- 是否存在
sudo()扩权且未说明风险边界?
13.2 CSDN 读者可直接复用的评审模板片段
把下面这段贴到 MR 模板里即可:
### 模型设计评审
- [ ] 该对象为何选择 `Model/TransientModel/AbstractModel`?
- [ ] 关键业务规则是否在 `Model` 层兜底?
- [ ] 是否存在并发写入风险,SQL 约束是否已覆盖?
- [ ] 是否有 `sudo()`,边界与风险是否说明?
- [ ] 多公司与权限规则是否验证?
14. 三类模型在 Odoo 升级迁移中的风险地图
这一节给你一个可执行视角:不是哪里会报错,而是哪里最可能在迁移时“静默出错”。
14.1 风险总览(按模型类型)
| 模型类型 | 典型迁移风险 | 风险级别 | 触发条件 |
|---|---|---|---|
Model |
字段/约束变化导致写入失败或口径变化 | 高 | 大版本字段重构、SQL 约束收紧、第三方模块改写 |
TransientModel |
向导上下文变化导致批量动作误写 | 中高 | active_model/active_ids 语义变化、按钮绑定变化 |
AbstractModel |
继承链断裂或 Mixin 行为漂移 | 中 | 多模块 _inherit 顺序变化、方法签名变化 |
14.2 Model 的迁移风险热点
-
字段语义变更风险
- 表现:字段还在,但业务含义变化(例如状态枚举新增/废弃)。
- 后果:代码不报错,但流程和报表口径悄悄偏移。
-
约束增强风险
- 表现:新版本 SQL 约束更严格,历史脏数据无法写入/升级。
- 动作:迁移前跑数据体检(重复键、空值、非法状态)。
-
计算字段依赖不完整风险
- 表现:升级后
store=True字段值陈旧。 - 动作:复核
@api.depends,并做关键指标前后对账。
- 表现:升级后
14.3 TransientModel 的迁移风险热点
-
上下文耦合风险
- 表现:向导读错
active_ids,批量改错对象。 - 动作:向导入口强校验
active_model,业务写入前二次校验目标模型。
- 表现:向导读错
-
权限绕过风险
- 表现:向导代码含
sudo(),升级后行为扩大。 - 动作:把关键权限校验下沉到
Model业务方法,不依赖 UI 入口。
- 表现:向导代码含
14.4 AbstractModel 的迁移风险热点
-
Mixin 漂移风险
- 表现:抽象方法被多模块 override,
super()链在新版本顺序变化。 - 动作:为关键 Mixin 方法加单测,验证调用顺序与结果。
- 表现:抽象方法被多模块 override,
-
职责膨胀风险
- 表现:AbstractModel 里塞了过多业务流程,升级时波及面失控。
- 动作:抽象层只保留“稳定能力”,流程逻辑回归业务模型。
14.5 迁移阶段检查矩阵(可直接执行)
| 阶段 | 检查重点 | 目标 |
|---|---|---|
| 迁移前(Pre-check) | Model 约束体检、向导入口清单、Mixin 继承图 |
避免脚本中断与静默偏差 |
| 迁移中(Dry-run) | 关键单据链回放、向导批量操作演练、权限回归 | 发现上下文与权限问题 |
| 迁移后(Post-check) | 报表口径比对、审计字段抽样、异常日志复盘 | 防止“能跑但不对” |
14.6 一个真实感迁移场景(合成)
- 场景:v14 → v17,核心工单模型 + 多个向导 + 通用 Mixin。
- 问题:升级后批量转派向导误改跨公司记录,且工单 KPI 与旧系统差异 2%。
- 根因:
- 向导未校验
active_model,且使用了sudo(); - KPI
store字段依赖漏写。
- 向导未校验
- 修复:
- 向导改为“入口校验 + 模型层权限兜底”;
- 补全
@api.depends并重算历史数据; - 加入迁移后 KPI 对账脚本。
15. 总结
Odoo 三类模型的本质是生命周期管理:
Model负责业务事实TransientModel负责交互参数AbstractModel负责能力复用
模型类型选对,后续权限、报表、迁移、性能和代码治理都会更稳。
模型类型选错,问题不会立刻出现,但会在升级、审计和数据治理阶段一次性爆发。
Model决定业务事实能不能沉淀。TransientModel决定交互是否轻量而不污染业务数据。AbstractModel决定复用是否可控而不耦合失控。
Odoo 三类模型看起来是 ORM 语法,实际上决定的是系统未来几年还能不能维护。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)