第二章 模型(Models)
篇别:第一篇 基础知识
本章学习目标
- 能正确声明 Model / AbstractModel / TransientModel。
- 能组合 字段、约束、索引 声明方式(含 Odoo 19 习惯用法)。
- 能说出跨版本 模型重构 时自定义模块的应对步骤。
2.1 模型基础
2.1.1 知识要点
models.Model:持久化业务实体,_name全局唯一(点分命名,如library.book)。models.AbstractModel:_abstract = True,不创建业务表,供继承复用。models.TransientModel:临时表,适合向导;系统会清理过期数据。
2.1.2 案例
案例 A:最小模型
from odoo import models, fields
class Book(models.Model):
_name = 'library.book'
_description = '图书'
name = fields.Char(required=True)
isbn = fields.Char()
案例 B:抽象基类供多模型继承
class TimestampMixin(models.AbstractModel):
_name = 'library.timestamp.mixin'
_description = '时间戳混入'
create_date = fields.Datetime(readonly=True)
# 子模型 _inherit 此 mixin 即可获得统一字段(若需自定义 create_date 需另设计)
说明:create_date 通常由框架提供,此处仅示意 mixin 模式。
2.1.3 截图占位

2.1.4 本节练习
- 填空:普通业务单据应继承自 Odoo 的 __ 类。
- 判断:
_description会影响数据库表名。( ) - 实操:新建模型
training.course,字段name、hours(浮点)。
参考答案提示:1. models.Model。2. 错(表名由 _name 等规则生成)。3. 见案例 A 扩展。
2.2 字段定义
2.2.1 知识要点
- 标量:
Char、Text、Integer、Float、Boolean、Date、Datetime、Monetary、Html、Binary、Selection。 - 关系:
Many2one(多对一)、One2many(一对多,反向字段)、Many2many。 - 计算:
compute=+@api.depends;可选store、inverse、related。
2.2.2 案例
案例 A:订单行与产品
class OrderLine(models.Model):
_name = 'training.order.line'
order_id = fields.Many2one('training.order', required=True, ondelete='cascade')
product_id = fields.Many2one('product.product', string='产品')
qty = fields.Float(default=1.0)
class Order(models.Model):
_name = 'training.order'
line_ids = fields.One2many('training.order.line', 'order_id', string='明细')
案例 B:计算总价
amount_total = fields.Monetary(compute='_compute_total', currency_field='currency_id', store=True)
@api.depends('line_ids.subtotal')
def _compute_total(self):
for order in self:
order.amount_total = sum(order.line_ids.mapped('subtotal'))
2.2.3 截图占位

2.2.4 本节练习
- 选择题:
One2many必须配合对方模型上的( )字段。(A)Many2many (B)Many2one (C)Reference - 简答:
Monetary为什么需要currency_field? - 实操:为
training.course增加Many2one指向res.partner(讲师)。
参考答案提示:1. B。2. 金额需按币种格式化与换算。3. instructor_id = fields.Many2one('res.partner')。
2.3 约束与索引(补充)
2.3.1 知识要点
- SQL 约束:
_sql_constraints或新版本推荐的模型级约束声明(以当前源码odoo.models为准)。 - Python 约束:
@api.constrains做跨字段逻辑校验。 - 索引:
index=True或模型级索引定义,优化常用 domain 字段。
2.3.2 案例
案例:唯一 ISBN
_sql_constraints = [
('book_isbn_uniq', 'unique(isbn)', 'ISBN 不能重复!'),
]
@api.constrains('start_date', 'end_date')
def _check_dates(self):
for rec in self:
if rec.end_date and rec.start_date and rec.end_date < rec.start_date:
raise ValidationError('结束日期不能早于开始日期')
2.3.3 截图占位

2.3.4 本节练习
- 简答:何时优先用 SQL 约束而非
@api.constrains? - 实操:为
training.course的code字段加唯一约束。
参考答案提示:1. 简单唯一/检查、需数据库层强制与并发安全时。2. _sql_constraints 或 ORM 等价 API。
2.4 模型重构指南(新增)
2.4.1 知识要点
大版本升级时官方可能 合并模型、重命名字段(HR、库存 UoM、会计 EDI 等)。自定义模块应:
- 阅读 Release Notes 与升级指南。
- 使用
upgrade_code/ 迁移脚本 替换废弃符号。 - 避免依赖已删除的
_name。
2.4.2 案例
案例:字段改名迁移思路
- 旧字段
product_uom→ 新product_uom_id。 - 在
migrations/19.0.1.0.0/pre-migrate.py(或项目约定路径)中用 SQLALTER TABLE ... RENAME COLUMN,或在post_init中数据拷贝(大表慎用)。
2.4.3 截图占位

2.4.4 本节练习
- 简答:升级前为什么要先在 数据库副本 上跑
-u? - 拓展:在 Changelog 中找一条与你业务相关的废弃项并写出应对句。
参考答案提示:1. 避免直接破坏生产数据与不可回滚结构变更。
本章综合练习
- 实现:
training.session(TransientModel)批量创建training.course。 - 说明:
store=True的计算字段在搜索与性能上的利弊。
本章对应白皮书目录:第二章 模型。