何在 Odoo 19 中为自定义模块添加章节和备注
在 Odoo 中,One2many 字段支持添加章节(Section) 和备注(Note),这两类元素可帮助用户将相关记录分组到有意义的类别中,其中备注还能用于在特定记录的上下文中补充额外信息或说明。例如,在销售订单行中,用户可通过章节和备注更好地组织与管理商品条目。

本文将介绍如何在自定义模型中添加章节和备注功能。操作流程将从创建新模型并添加 One2many 字段开始,逐步完成配置。
一、创建核心模型(Python 代码)
首先需定义两个关联模型:warranty.request(保修申请主模型)和 warranty.request.line(保修申请明细行模型,用于承载章节、备注及商品信息)。
1. 保修申请主模型(warranty.request)
该模型存储保修申请的核心信息(如客户、日期、状态等),并通过 One2many 字段关联明细行。
from odoo import models, fields, _
from datetime import datetime
class WarrantyRequest(models.Model):
_name = 'warranty.request' # 模型名称:保修申请
_description = 'Warranty Request' # 模型描述:保修申请
_inherit = ['mail.thread', 'mail.activity.mixin']
# 继承消息跟踪、活动混合类(支持消息通知)
partner_id = fields.Many2one('res.partner', string='客户')
# 关联客户模型
date = fields.Date(string='申请日期', default=datetime.today()) # 申请日期,默认当前日期
name = fields.Char(string="序列号", readonly=True, required=True, copy=False,default=_('New')) # 默认值为“New”,后续可通过序列自动生成
state = fields.Selection([('draft', '草稿'), ('to_approve', '待审批'), ('approved', '已审批'), ('cancelled', '已取消')],string='状态', default='draft', clickable=True) # 状态字段可点击切换
warranty_period = fields.Selection([('3months', '3 个月'),('6month', '6 个月'), ('1year', '1 年')], string='保修期限', default='3months')
warranty_expire_date = fields.Date(string="保修到期日") # 保修到期日期
warranty_line_ids = fields.One2many(
'warranty.request.line', # 关联的明细行模型
'warranty_id' # 明细行中关联主模型的字段
)
description = fields.Html(string='申请说明') # 富文本格式的申请说明
2.保修申请明细行模型(warranty.request.line)
该模型是实现 “章节 / 备注” 功能的核心,需包含 display_type 字段(用于区分 “章节”“备注” 或 “普通商品行”),并通过业务逻辑和 SQL 约束确保数据合法性。
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class WarrantyRequestLine(models.Model):
_name = "warranty.request.line" # 模型名称:保修申请明细行
_order = 'warranty_id, sequence, id' # 排序规则:先按主模型ID,再按序号,最后按明细行ID
# SQL约束:确保数据完整性
_sql_constraints = [
# 约束1:需核算行(普通商品行)必须填写必填字段
('accountable_required_fields',
"CHECK(display_type IS NOT NULL OR (product_id IS NOT NULL AND product_uom_qty IS NOT NULL))",
"需核算的保修申请明细行缺少必填字段(商品或数量)。"),
# 约束2:非核算行(章节/备注)禁止填写商品和数量
('non_accountable_null_fields',
"CHECK(display_type IS NULL OR (product_id IS NULL AND product_uom_qty = 0))",
"非核算的保修申请明细行(章节/备注)禁止填写商品或数量。"),
]
warranty_id = fields.Many2one('warranty.request') # 关联保修申请主模型(反向关联字段)
sequence = fields.Integer(string="序号", help="显示保修申请明细行列表时的排序序号", default=10) # 默认序号为10
product_id = fields.Many2one('product.product', check_company=True) # 检查商品所属公司与当前环境公司一致
name = fields.Text(string="描述", translate=True) # translate=True:支持多语言翻译
product_uom_id = fields.Many2one('uom.uom', '计量单位', compute='_compute_product_uom_id', store=True,readonly=False, precompute=True) # precompute=True:预计算(提升性能)
product_uom_qty = fields.Float(string='数量', required=True, digits='Product Unit of Measure', # 采用商品计量单位的精度配置
default=1)
display_type = fields.Selection([('line_section', "章节"), ('line_note', "备注")], default=False) # 默认无类型(即普通商品行)
# 计算字段逻辑:根据商品自动获取计量单位
@api.depends('product_id')
def _compute_product_uom_id(self):
for line in self:
line.product_uom_id = line.product_id.uom_id
# 重写创建方法:确保章节/备注行不包含商品和数量信息
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
# 若当前行是章节/备注(display_type有值)
if vals.get('display_type', self.default_get(['display_type'])['display_type']):
# 强制清空商品、数量、计量单位字段
vals.update(product_id=False, product_uom_qty=0, product_uom_id=False)
return super().create(vals_list)
# 重写写入方法:禁止修改行类型(章节/备注/普通行之间不可切换)
def write(self, values):
# 若修改了display_type,且存在行的原类型与新类型不一致
if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')):
raise UserError(_("无法修改保修申请明细行的类型。请删除当前行,重新创建对应类型的行。"))
return super().write(values)
二、配置视图(XML 代码)
需通过 XML 定义主模型的表单视图,重点为 One2many 字段(warranty_line_ids)配置章节与备注专用组件(section_and_note_one2many 组件),并添加 “添加产品”“添加章节”“添加备注” 的快捷按钮。
<record id="warranty_request_form_view" model="ir.ui.view">
<field name="name">warranty.request.form.view</field>
<!-- 视图名称 -->
<field name="model">warranty.request</field> <!-- 关联的主模型 -->
<field name="arch" type="xml">
<form> <!-- 表单视图根标签 -->
<sheet> <!-- 表单“工作表”区域(Odoo 标准布局) -->
<!-- 顶部基础信息分组 -->
<group>
<group>
<field name='partner_id' required="True" string="客户"/>
</group>
<group>
<field name='date' string="申请日期"/>
<field name='warranty_period' string="保修期限"/>
<field name='warranty_expire_date' string="保修到期日"/>
</group>
</group>
<!-- 标签页区域 -->
<notebook>
<!-- 标签1:商品明细(承载章节/备注/商品行) -->
<page id="product_page" name="商品明细">
<!-- One2many字段:配置章节与备注组件 -->
<field name="warranty_line_ids" widget="section_and_note_one2many">
<!-- 明细行列表视图(可编辑,新增行在底部) -->
<list string="商品明细行" editable="bottom">
<!-- 快捷操作按钮组(添加产品/章节/备注) -->
<control>
<create name="add_product_control" string="添加产品"/>
<!-- 默认创建“章节”行 -->
<create name="add_section_control" string="添加章节" context="{'default_display_type': 'line_section'}"/><!-- 默认创建“备注”行 -->
<create name="add_note_control" string="添加备注" context="{'default_display_type': 'line_note'}"/>
</control>
<!-- 隐藏display_type字段(仅用于逻辑判断,不显示给用户) -->
<field name="display_type" column_invisible="True"/>
<!-- 序号字段(支持拖拽排序) -->
<field name="sequence" widget="handle"/>
<!-- 商品字段:仅普通行必填(章节/备注行无需填写) -->
<field name="product_id" required="not display_type" string="产品"/>
<!-- 描述字段(章节/备注的标题/内容,普通行的商品描述) -->
<field name="name"/>
<!-- 数量字段:仅普通行必填 -->
<field name="product_uom_qty"/>
<!-- 计量单位字段:仅普通行必填 -->
<field name="product_uom_id" required="not display_type"/>
</list>
</field>
</page>
<!-- 标签2:申请说明 -->
<page id="description_page" name="申请说明">
<!-- 富文本说明字段:仅草稿状态可编辑 -->
<field name="description" readonly="state != 'draft'"/>
</page>
</notebook>
</sheet>
<!-- 消息聊天区(支持添加评论、附件等) -->
<chatter/>
</form>
</field>
</record>
这会产生以下视图:默认情况下,我们会得到“添加行”控制按钮。

三、实现章节与备注的关键步骤
若需在自定义 One2many 模型中支持章节与备注,需严格遵循以下 4 个步骤。
步骤 1:在明细行模型中添加 display_type 字段
在 One2many 关联的明细行模型(如 warranty.request.line)中,必须添加 display_type 选择字段,用于区分行类型(章节 / 备注 / 普通行);同时需添加 name 字段,用于存储章节标题或备注内容:
display_type = fields.Selection([('line_section', "章节"), ('line_note', "备注")], default=False)
name = fields.Text(string="描述", translate=True)
步骤 2:重写 create 和 write 方法
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('display_type', self.default_get(['display_type'])['display_type']):
vals.update(product_id=False, product_uom_qty=0, product_uom_id=False)
return super().create(vals_list)
·create 方法:确保创建章节 / 备注行时,自动清空商品(product_id)、数量(product_uom_qty)等核算字段,避免数据冲突。
def write(self, values):
if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')):
raise UserError(_("无法修改保修申请明细行的类型。请删除当前行,重新创建对应类型的行。"))
return super().write(values)
·write 方法:禁止修改行类型(如 “章节” 不可改为 “备注” 或 “普通行”),需删除原行后重新创建,保证数据逻辑一致性。
步骤 3:添加 SQL 约束
通过强制规定保修请求行要么是显示行(设置了 display_type),要么是可核算行(设置了 product_id 和 quantity),但不能同时是两者,从而确保数据完整性。
_sql_constraints = [
('accountable_required_fields',
"CHECK(display_type IS NOT NULL OR (product_id IS NOT NULL AND product_uom_qty IS NOT NULL))",
"需核算的保修申请明细行缺少必填字段(商品或数量)。"),
('non_accountable_null_fields',
"CHECK(display_type IS NULL OR (product_id IS NULL AND product_uom_qty = 0))",
"非核算的保修申请明细行(章节/备注)禁止填写商品或数量。"),
]
·non_accountable_null_fields 约束:保证对于设置了 display_type 的行,product_id 必须为空,并且数量必须为零。
·non_accountable_null_fields 约束:确保当设置了 display_type 时,product_id 必须保持为空,并且数量必须设置为零。
步骤 4:在视图中配置组件与控制按钮
1、配置专用组件:为 One2many 字段添加 widget=“section_and_note_one2many”,启用章节与备注的可视化样式(如章节标题加粗、备注行斜体)。
<field name="warranty_line_ids" widget="section_and_note_one2many">
2、添加快捷按钮:在 标签内通过 定义 “添加产品”“添加章节”“添加备注” 按钮,并通过 context 预设 display_type 值,实现点击即创建对应类型的行。
这些控件使用户能够快速添加具有为章节和备注预置上下文的新记录。
<control>
<create name="add_product_control" string="添加产品"/>
<!-- 默认创建“章节”行 -->
<create name="add_section_control" string="添加章节" context="{'default_display_type': 'line_section'}"/>
<!-- 默认创建“备注”行 -->
<create name="add_note_control" string="添加备注" context="{'default_display_type': 'line_note'}"/>
</control>
·产品控件:没有任何特定上下文,这将生成标准的产品行。
·章节控件:默认将 display_type 设置为 “line_section”。
·备注控件:默认将 display_type 设置为 “line_note”。
3、隐藏 display_type 字段:通过 column_invisible=“True” 隐藏该字段,避免用户误操作。
<field name="display_type" column_invisible="True"/>
4、动态设置必填规则:为 product_id、product_uom_qty 等字段设置 required=“not display_type”,仅普通行强制必填。
<field name="name"/> <field name="product_uom_qty"/> <field name="product_uom_id" required="not display_type"/>
确保 product_id 和 quantity 字段仅在未设置 display_type 时为必填。
在 One2many 树形视图中,用户可以添加章节和备注,如下面的截图所示。

四、章节与备注的优势
在 Odoo 中,在 One2Many 字段中使用章节和备注提供了多种优势,可以增强组织性、用户体验和整体效率。章节允许用户在表单或视图中轻松分离和识别不同的类别或数据集,从而提高信息的可读性。同时,备注提供了一种包含与特定记录直接相关的额外细节或评论的方式。