第三章:模型与ORM深入
好,咱们进入第三章。这一章可以说是Odoo开发的核心中的核心。模型与ORM,说白了就是你跟数据库打交道的全部手段。我见过太多新手,写了一大堆业务逻辑,结果模型定义一塌糊涂,后面维护起来简直想哭。嗯,咱们今天就把这块彻底讲透。
3.1 模型继承机制
Odoo的模型继承,是我个人觉得最巧妙的设计之一。它不像传统开发那样,你得去改别人的源码。你想想看,要是你买了一套ERP,想加个字段就得改核心代码,那升级的时候怎么办?
Odoo提供了三种继承方式,咱们一个一个说。
3.1.1 经典继承(_inherit)
这是最常用的方式。你想在已有的模型上加字段、改方法,就用它。
from odoo import models, fields
class ResPartner(models.Model):
_inherit = 'res.partner'
is_vip = fields.Boolean(string='VIP客户')
vip_level = fields.Integer(string='VIP等级', default=1)
你看,就这么简单。我曾在项目中遇到一个需求:要在客户表单上加个「最后下单时间」。用_inherit,五分钟搞定,完全不影响原有逻辑。
3.1.2 原型继承(_name + _inherit)
这种继承会创建一个全新的模型。它复制了父模型的所有字段和方法,但有自己的数据表。
class SaleOrder(models.Model):
_name = 'sale.order.custom'
_inherit = 'sale.order'
_description = '自定义销售订单'
custom_field = fields.Char('自定义字段')
为什么要这么干?我记得有一次,客户想要一个跟销售订单几乎一样,但业务逻辑完全不同的单据。用原型继承,既保留了原有功能,又独立存储,互不干扰。
3.1.3 委托继承(_inherits)
这个稍微复杂点,但非常强大。它允许一个模型「委托」给另一个模型,共享数据。
class CustomUser(models.Model):
_name = 'custom.user'
_inherits = {'res.users': 'user_id'}
user_id = fields.Many2one('res.users', required=True, ondelete='cascade')
department = fields.Char('部门')
说白了,就是你在custom.user里操作,数据会同步到res.users。我一般只在需要扩展系统核心模型,但又不想破坏原有结构时用这个。
3.2 字段类型详解
字段类型这块,看起来简单,但用好了能省不少事。咱们挑几个重点说说。
3.2.1 基础字段
| 字段类型 | 说明 | 我的经验 |
|---|---|---|
| Char | 字符串,最长255字符 | 记得加size限制,不然用户能输出一篇作文 |
| Integer | 整数 | 做数量、序号时首选 |
| Float | 浮点数 | 涉及金额时用Float,但要注意精度 |
| Boolean | 布尔值 | 开关、状态标记,简单直接 |
| Date | 日期 | 默认用fields.Date.today() |
举个例子,一个完整的模型定义大概长这样:
class ProductTemplate(models.Model):
_name = 'product.template'
name = fields.Char('产品名称', required=True, size=100)
code = fields.Char('产品编码', size=20)
price = fields.Float('价格', digits=(10, 2))
is_active = fields.Boolean('启用', default=True)
release_date = fields.Date('发布日期', default=fields.Date.today)
3.2.2 关联字段
这才是Odoo的精髓。三个关联字段,搞懂了,你就能把数据串起来。
Many2one(多对一):最常见。比如一个订单属于一个客户。
partner_id = fields.Many2one('res.partner', string='客户', required=True)
One2many(一对多):一个订单有多个订单行。
order_line_ids = fields.One2many('sale.order.line', 'order_id', string='订单行')
Many2many(多对多):一个产品可以属于多个分类,一个分类也可以有多个产品。
category_ids = fields.Many2many('product.category', string='产品分类')
3.3 约束与默认值
约束,就是给数据上规矩。默认值,就是给用户省事。这两样用好了,数据质量能提升一大截。
3.3.1 SQL约束
直接在数据库层面做限制,效率最高。
_sql_constraints = [
('name_uniq', 'unique(name)', '产品名称不能重复!'),
('code_uniq', 'unique(code)', '产品编码已存在!'),
]
我一般把唯一性约束、非空约束放在SQL层面。为什么?因为数据库检查比Python快得多,而且不会因为代码bug而漏掉。
3.3.2 Python约束
复杂的业务逻辑,用Python约束。比如价格不能为负数:
from odoo.exceptions import ValidationError
@api.constrains('price')
def _check_price(self):
for record in self:
if record.price < 0:
raise ValidationError('价格不能为负数!')
3.3.3 默认值设置
默认值有三种常见写法:
- 静态默认值:default=True,default=0
- 函数默认值:default=_compute_default
- 关联默认值:default=lambda self: self.env.user.id
举个例子:
class SaleOrder(models.Model):
_name = 'sale.order'
user_id = fields.Many2one('res.users', string='销售员',
default=lambda self: self.env.user)
date_order = fields.Date('订单日期',
default=fields.Date.context_today)
state = fields.Selection([
('draft', '草稿'),
('confirm', '已确认'),
('done', '已完成')
], string='状态', default='draft')
你看,销售员默认是当前登录用户,日期默认是今天,状态默认是草稿。用户打开表单,什么都不用填,直接就能保存。这才是好的用户体验。
核心总结:
- 继承用_inherit,别改核心代码
- 字段类型选对了,后面少改八遍
- 约束写在数据库层面,业务逻辑用Python
- 默认值让用户少点一下,就是成功
好了,这一章的内容就到这儿。下一章咱们聊聊视图和界面,那才是用户真正看到的东西。到时候我会分享一些让界面「看起来就很专业」的小技巧。嗯,期待一下。