1.基本结构定义
from odoo import api, fields, models
class ProductCategory(models.Model):
_name = 'product.category'
_description = '产品分类'
_parent_store = True
_parent_path = True # Odoo 18新特性
name = fields.Char(string='名称', required=True)
parent_id = fields.Many2one('product.category', string='上级分类')
child_ids = fields.One2many('product.category', 'parent_id', string='下级分类')
parent_path = fields.Char(index=True) # 新的父级路径字段
2.新特性 - parent_path
# 替代了早期版本的parent_left/parent_right
parent_path = fields.Char(index=True)
# parent_path存储格式示例: "1/2/3/"
# 表示当前记录的完整父级路径
3.树形视图定义
<record id="view_category_tree" model="ir.ui.view">
<field name="name">product.category.tree</field>
<field name="model">product.category</field>
<field name="arch" type="xml">
<tree>
<field name="display_name"/>
<field name="parent_id"/>
<field name="sequence" widget="handle"/>
</tree>
</field>
</record>
4.常用功能实现
class ProductCategory(models.Model):
_name = 'product.category'
_description = '产品分类'
_parent_store = True
_parent_path = True
_order = 'sequence, name'
# 基本字段
name = fields.Char(required=True)
sequence = fields.Integer(default=10)
parent_id = fields.Many2one('product.category', index=True)
child_ids = fields.One2many('product.category', 'parent_id')
parent_path = fields.Char(index=True)
# 计算完整名称
complete_name = fields.Char(
compute='_compute_complete_name',
store=True,
recursive=True
)
@api.depends('name', 'parent_id.complete_name')
def _compute_complete_name(self):
for record in self:
if record.parent_id:
record.complete_name = f'{record.parent_id.complete_name} / {record.name}'
else:
record.complete_name = record.name
# 检查循环引用
@api.constrains('parent_id')
def _check_hierarchy(self):
if not self._check_recursion():
raise ValidationError('Error! You cannot create recursive categories.')
5.高级查询操作
def action_find_categories(self):
# 获取所有子分类
child_categories = self.env['product.category'].search([
('parent_path', 'like', f'{self.parent_path}%')
])
# 获取所有父分类
parent_ids = [int(x) for x in self.parent_path.split('/')[:-1]]
parent_categories = self.env['product.category'].browse(parent_ids)
# 获取同级分类
sibling_categories = self.env['product.category'].search([
('parent_id', '=', self.parent_id.id),
('id', '!=', self.id)
])
6.安全权限设置
<record id="category_comp_rule" model="ir.rule">
<field name="name">Product Category Multi-Company</field>
<field name="model_id" ref="model_product_category"/>
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
</record>
7.新增功能示例
class ProductCategory(models.Model):
_name = 'product.category'
# 统计子分类数量
child_count = fields.Integer(
compute='_compute_child_count',
string='子分类数量'
)
# 分类层级
level = fields.Integer(
compute='_compute_level',
string='层级'
)
@api.depends('child_ids')
def _compute_child_count(self):
for record in self:
record.child_count = len(record.child_ids)
@api.depends('parent_path')
def _compute_level(self):
for record in self:
record.level = len(record.parent_path.split('/')) - 1
8.实用工具方法
def get_hierarchy_data(self):
"""获取完整的层级数据"""
return {
'id': self.id,
'name': self.name,
'level': self.level,
'child_count': self.child_count,
'children': [child.get_hierarchy_data() for child in self.child_ids],
}
def move_to_category(self, target_category_id):
"""移动分类到指定位置"""
self.ensure_one()
if target_category_id:
target = self.browse(target_category_id)
if target.parent_path.startswith(self.parent_path):
raise ValidationError('不能移动到子分类下')
self.parent_id = target_category_id
9.性能优化建议
- 使用parent_path索引提高查询效率
- 适当使用预加载(prefetch)
- 大数据量时考虑分批处理
- 合理使用递归计算字段
- 避免过深的层级结构
10.注意事项
- parent_path字段会自动维护
- 注意处理循环引用问题
- 考虑多公司情况下的数据隔离
- 注意树形结构的深度限制
- 合理使用序列号控制显示顺序