Odoo 三类模型选型指南:ModelTransientModelAbstractModel 怎么选,选错会怎样

很多 Odoo 项目后期越做越难维护,根本原因不是字段不会加、视图不会改,而是模型职责一开始就放错了位置:该长期留痕的做成临时向导,该复用的能力写成复制粘贴,该放在业务模型里的规则被塞进按钮方法里。

文章目录

阅读提示(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 个场景

  1. 批量审批弹窗:把审批结论落到 TransientModel,后续审计找不到。
  2. 导入中间数据:把 staging 表当正式业务 Model,导致口径污染。
  3. 通用编码/归档能力:在多个模型复制粘贴,而不是抽到 AbstractModel

2. models.Model:业务事实层

2.1 什么时候必须用 Model

满足任一条基本就应使用:

  1. 需要长期保存并可追溯
  2. 要参与审批/状态机
  3. 要被报表、统计、对账消费
  4. 需要完整权限模型(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. 三类模型协同的正确方式(实战架构)

推荐分层:

  1. Model:业务核心规则与事实数据
  2. TransientModel:入口交互(参数收集)
  3. AbstractModel:跨模型复用能力

可理解为:

  • 事实层(Model)
  • 交互层(Transient)
  • 能力层(Abstract)

这个分层能显著降低“向导里写满业务逻辑”“复制粘贴方法四处飘”的问题。

5.1 快速判断流程图(文本版)

  1. 这份数据是否需要长期留存、可追溯、可报表?
    • 是 -> Model
    • 否 -> 继续
  2. 是否只是一次性交互参数(弹窗、批量动作输入)?
    • 是 -> TransientModel
    • 否 -> 继续
  3. 是否是多个模型都需要的通用能力?
    • 是 -> AbstractModel
    • 否 -> 回到业务对象边界重新建模

5.2 一张图记住(简版)

  • 业务事实 -> Model
  • 交互参数 -> TransientModel
  • 复用能力 -> AbstractModel

6. 常见反模式(深坑)

反模式 1:用 TransientModel 存长期审批记录

  • 结果:数据被清理,审计断链。
  • 正解:审批记录必须建 Model

反模式 2:把向导当权限边界

  • 结果:用户可绕过向导直接 RPC 调模型,导致越权。
  • 正解:权限校验在业务模型方法中落地。

反模式 3:AbstractModel 塞业务流程

  • 结果:继承树复杂,某个模型不适配也被迫继承。
  • 正解:抽象层只放“普适能力”,业务流程留在具体 Model

反模式 4:把“导入中间表”当正式业务模型

  • 结果:报表口径混乱、冗余数据持续膨胀。
  • 正解:中间态可用 TransientModel 或清理策略明确的 staging Model(有 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 或设计评审时,可以用下面的问题快速判断模型方案是否健康:

  1. 这个对象的数据生命周期是什么?为什么不是 TransientModel/Model 的另一种?
  2. 这个规则为什么放在这里?模型层有没有统一事实来源?
  3. 并发下会不会重复写入?SQL 约束在哪里?
  4. 多公司场景是否有 company 边界和 ir.rule 兜底?
  5. 这个抽象是否真的可复用,还是把业务耦合塞进了 AbstractModel
  6. 是否需要 _inherits?不用它能否更简单地完成目标?
  7. 这个方法是否会在事务里阻塞外部依赖?

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 的迁移风险热点

  1. 字段语义变更风险

    • 表现:字段还在,但业务含义变化(例如状态枚举新增/废弃)。
    • 后果:代码不报错,但流程和报表口径悄悄偏移。
  2. 约束增强风险

    • 表现:新版本 SQL 约束更严格,历史脏数据无法写入/升级。
    • 动作:迁移前跑数据体检(重复键、空值、非法状态)。
  3. 计算字段依赖不完整风险

    • 表现:升级后 store=True 字段值陈旧。
    • 动作:复核 @api.depends,并做关键指标前后对账。

14.3 TransientModel 的迁移风险热点

  1. 上下文耦合风险

    • 表现:向导读错 active_ids,批量改错对象。
    • 动作:向导入口强校验 active_model,业务写入前二次校验目标模型。
  2. 权限绕过风险

    • 表现:向导代码含 sudo(),升级后行为扩大。
    • 动作:把关键权限校验下沉到 Model 业务方法,不依赖 UI 入口。

14.4 AbstractModel 的迁移风险热点

  1. Mixin 漂移风险

    • 表现:抽象方法被多模块 override,super() 链在新版本顺序变化。
    • 动作:为关键 Mixin 方法加单测,验证调用顺序与结果。
  2. 职责膨胀风险

    • 表现:AbstractModel 里塞了过多业务流程,升级时波及面失控。
    • 动作:抽象层只保留“稳定能力”,流程逻辑回归业务模型。

14.5 迁移阶段检查矩阵(可直接执行)

阶段 检查重点 目标
迁移前(Pre-check) Model 约束体检、向导入口清单、Mixin 继承图 避免脚本中断与静默偏差
迁移中(Dry-run) 关键单据链回放、向导批量操作演练、权限回归 发现上下文与权限问题
迁移后(Post-check) 报表口径比对、审计字段抽样、异常日志复盘 防止“能跑但不对”

14.6 一个真实感迁移场景(合成)

  • 场景:v14 → v17,核心工单模型 + 多个向导 + 通用 Mixin。
  • 问题:升级后批量转派向导误改跨公司记录,且工单 KPI 与旧系统差异 2%。
  • 根因:
    1. 向导未校验 active_model,且使用了 sudo()
    2. KPI store 字段依赖漏写。
  • 修复:
    • 向导改为“入口校验 + 模型层权限兜底”;
    • 补全 @api.depends 并重算历史数据;
    • 加入迁移后 KPI 对账脚本。

15. 总结

Odoo 三类模型的本质是生命周期管理

  • Model 负责业务事实
  • TransientModel 负责交互参数
  • AbstractModel 负责能力复用

模型类型选对,后续权限、报表、迁移、性能和代码治理都会更稳。
模型类型选错,问题不会立刻出现,但会在升级、审计和数据治理阶段一次性爆发。

  • Model 决定业务事实能不能沉淀。
  • TransientModel 决定交互是否轻量而不污染业务数据。
  • AbstractModel 决定复用是否可控而不耦合失控。

Odoo 三类模型看起来是 ORM 语法,实际上决定的是系统未来几年还能不能维护。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐