在 Odoo 开发的广阔世界里,一切都围绕着数据展开。无论是创建一个销售订单,更新客户信息,还是读取产品库存,我们始终在与数据库中的记录打交道。为了规范和简化这些交互,Odoo 提供了一套强大而优雅的工具:API 装饰器 和 ORM 方法。
理解并熟练运用这两者,是衡量一位 Odoo 开发者水平的关键标尺。本文将深入解析 Odoo 17 中最核心的 API 装饰器和标准的 CRUD(创建、读取、更新、删除)操作。
self
是什么?在开始之前,必须理解 self
。在 Odoo 的模型方法中,self
不是指单个对象,而是一个 记录集(Recordset)——一个包含了零个、一个或多个记录的有序集合。例如,self
可能代表一个选中的销售订单,也可能代表所有状态为“草稿”的订单。理解这一点是掌握后续内容的基础。
API 装饰器(Decorators)是附加在方法定义之上的特殊标记(如 @api.model
)。它们不会改变方法内部的代码,但会改变方法的调用方式、self
的内容以及 Odoo 框架如何与之交互。
@api.model
self
的内容:一个不包含任何记录的空记录集(例如 sale.order()
)。方法内部不能依赖任何具体的记录数据。create()
方法。示例:重写 create
,在创建伙伴时自动添加一个标签。
from odoo import models, fields, api
class ResPartner(models.Model):
_inherit = 'res.partner'
@api.model
def create(self, vals):
# 即使在创建单个伙伴时,self 也是空的 res.partner()
# vals 是一个包含所有待创建字段值的字典
tag_id = self.env.ref('my_module.default_partner_tag', raise_if_not_found=False)
if tag_id:
vals.setdefault('category_id', [(4, tag_id.id)])
# 调用原始的 create 方法
return super(ResPartner, self).create(vals)
@api.onchange('field_name_1', 'field_name_2', ...)
self
的内容:一个虚拟的、仅存在于内存中的单条记录。它的值是用户在表单上看到的值。{'warning': ...}
或 {'domain': ...}
。示例:当选择一个客户时,自动填充其默认的送货地址。
class SaleOrder(models.Model):
_inherit = 'sale.order'
@api.onchange('partner_id')
def _onchange_partner_id(self):
# self 是表单上正在编辑的单个销售订单的虚拟记录
if self.partner_id:
self.partner_shipping_id = self.partner_id.with_context(
is_delivery=True
).address_get(['delivery'])['delivery']
@api.depends('field_name_1', 'field_name_2', ...)
self
的内容:一个包含一个或多个需要重新计算该字段值的记录集。self
(通常是 for record in self:
) 并为每条记录的计算字段赋值。示例:计算订单行的总金额。
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
# 定义计算字段
price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal')
# 定义计算方法,并用 @api.depends 装饰
@api.depends('product_uom_qty', 'price_unit', 'discount')
def _compute_amount(self):
# self 可能包含多条订单行
for line in self:
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
line.price_subtotal = price * line.product_uom_qty
@api.constrains('field_name_1', ...)
self
的内容:包含了被创建或被修改的记录的记录集。create
或 write
)时触发。odoo.exceptions.ValidationError
异常。示例:确保课程的结束日期不能早于开始日期。
from odoo.exceptions import ValidationError
class Course(models.Model):
_name = 'my_module.course'
start_date = fields.Date()
end_date = fields.Date()
@api.constrains('start_date', 'end_date')
def _check_dates(self):
for course in self:
if course.start_date and course.end_date and course.start_date > course.end_date:
raise ValidationError("课程的结束日期不能早于开始日期!")
Odoo 的对象关系映射(ORM)将数据库操作封装成了简单直观的方法。所有这些方法都通过 self.env['model.name']
来访问。
create()
self.env['model.name'].create(vals)
vals
: 一个字典,键是字段名,值是字段值。# 创建一个新客户
partner_vals = {'name': '张三', 'email': '[email protected]'}
new_partner = self.env['res.partner'].create(partner_vals)
print(f"创建成功,新客户ID: {new_partner.id}")
search()
, browse()
, search_read()
1. search(domain, limit=None, order=None)
domain
: 一个由元组组成的列表,用于过滤记录。例如 [('state', '=', 'sale')]
。# 查找所有已确认的销售订单
sale_orders = self.env['sale.order'].search([('state', '=', 'sale')])
for order in sale_orders:
print(order.name)
2. browse(ids)
ids
: 一个整数列表或元组。# 获取 ID 为 1 和 5 的产品
products = self.env['product.product'].browse([1, 5])
3. search_read(domain, fields=None, ...)
search()
和 read()
的高效结合。直接搜索并返回记录的数据。# 查找所有客户的名称和邮箱
partners_data = self.env['res.partner'].search_read(
domain=[('is_company', '=', True)],
fields=['name', 'email']
)
# partners_data 的结果: [{'id': 1, 'name': '公司A', 'email': '...'}, ...]
write()
recordset.write(vals)
vals
: 一个包含要更新字段和新值的字典。True
。write
是在记录集上调用的,可以同时更新多条记录。# 将单个订单状态更新为“完成”
order = self.env['sale.order'].browse(10)
order.write({'state': 'done'})
# 将所有草稿状态的订单指派给当前用户
draft_orders = self.env['sale.order'].search([('state', '=', 'draft')])
draft_orders.write({'user_id': self.env.user.id})
unlink()
recordset.unlink()
True
。# 找到所有已取消的销售订单并删除它们
cancelled_orders = self.env['sale.order'].search([('state', '=', 'cancel')])
if cancelled_orders:
cancelled_orders.unlink()
在实际开发中,API 装饰器和 CRUD 方法总是协同工作。一个典型的业务逻辑可能是:
@api.onchange
在用户界面提供即时反馈。create()
或 write()
被调用。@api.constrains
装饰的方法在数据写入前进行最终验证。@api.depends
装饰的方法在相关数据更新后,自动重新计算字段值。search()
找到一批记录,然后调用 write()
或 unlink()
对它们进行批量处理。掌握 Odoo 17 的 API 和 CRUD,就等于掌握了与 Odoo 数据库对话的语言。这不仅能让你高效地实现业务需求,更能让你编写出结构清晰、性能优越、易于维护的健壮代码,从而真正释放 Odoo 框架的全部潜力。