如何构建自定义 Odoo 模块:OWL、ORM 和继承开发人员指南
当 Odoo 的 82 个官方模块和 40,000 多个社区模块不能满足您的具体业务需求时,自定义模块开发可以填补空白。本指南涵盖了 2026 年构建 Odoo 模块的基础知识,包括模块结构、OWL 前端框架、ORM 模式、继承机制以及符合 OCA(Odoo 社区协会)指南的最佳实践。
什么是自定义 Odoo 模块?
自定义 Odoo 模块 是一个独立的包,包含 Python 后端逻辑、XML 视图定义、JavaScript 前端组件和扩展 Odoo 功能的安全规则。模块可以添加全新功能、修改现有行为或将 Odoo 与外部系统集成。 Odoo 本身的每个部分都是一个模块,使得该架构具有本质上的可扩展性。
模块目录结构
组织良好的模块遵循以下标准结构:
my_custom_module/
├── __init__.py # Python package init
├── __manifest__.py # Module metadata and dependencies
├── models/
│ ├── __init__.py
│ └── my_model.py # Business logic and data models
├── views/
│ ├── my_model_views.xml # Form, tree, and kanban views
│ └── menu.xml # Menu items and actions
├── security/
│ ├── ir.model.access.csv # Access control list
│ └── security.xml # Record rules and groups
├── data/
│ └── data.xml # Default data records
├── static/
│ └── src/
│ ├── js/ # OWL components
│ ├── css/ # Stylesheets
│ └── xml/ # QWeb templates
├── wizard/ # Transient models for wizards
├── reports/ # QWeb report templates
└── tests/ # Unit tests
清单文件
__manifest__.py 文件定义模块的标识:
{
'name': 'My Custom Module',
'version': '18.0.1.0.0',
'category': 'Custom',
'summary': 'Short description of what the module does',
'description': """
Long description with details about
features and configuration.
""",
'author': 'ECOSIRE',
'website': 'https://ecosire.com',
'license': 'LGPL-3',
'depends': ['base', 'sale', 'stock'],
'data': [
'security/ir.model.access.csv',
'views/my_model_views.xml',
'views/menu.xml',
'data/data.xml',
],
'assets': {
'web.assets_backend': [
'my_custom_module/static/src/js/**/*',
'my_custom_module/static/src/css/**/*',
'my_custom_module/static/src/xml/**/*',
],
},
'installable': True,
'application': False,
'auto_install': False,
}
版本约定: {odoo_version}.{major}.{minor}.{patch}(例如,18.0.1.0.0)。
使用 ORM
Odoo 的对象关系映射(ORM)是所有后端开发的基础。模型映射到数据库表,ORM 提供 CRUD 操作、计算字段、约束和工作流管理。
定义模型
from odoo import models, fields, api
class ProjectTask(models.Model):
_name = 'my_module.task'
_description = 'Project Task'
_order = 'priority desc, create_date desc'
name = fields.Char(string='Task Name', required=True)
description = fields.Html(string='Description')
state = fields.Selection([
('draft', 'Draft'),
('in_progress', 'In Progress'),
('done', 'Done'),
('cancelled', 'Cancelled'),
], default='draft', tracking=True)
assigned_to = fields.Many2one('res.users', string='Assigned To')
deadline = fields.Date(string='Deadline')
priority = fields.Selection([
('0', 'Normal'),
('1', 'Important'),
('2', 'Urgent'),
], default='0')
tag_ids = fields.Many2many('my_module.tag', string='Tags')
progress = fields.Float(compute='_compute_progress', store=True)
字段类型参考
| 字段类型 | Python 类型 | 使用案例 |
|---|---|---|
| 查尔 | STR | 短文本(名称、参考文献) |
| 文字 | STR | 长纯文本 |
| 网页 | STR | 富文本内容 |
| 整数 | 整数 | 整数 |
| 浮动 | 浮动 | 小数 |
| 布尔 | 布尔 | 真/假标志 |
| 日期 | 日期 | 没有时间的日期 |
| 日期时间 | 日期时间 | 日期与时间 |
| 选择 | STR | 下拉选项 |
| 多对一 | 整数 | 链接到一条记录 |
| 一对多 | 列表 | Many2one 的反向 |
| 多对多 | 列表 | 链接到多个记录 |
| 二进制 | 字节 | 文件附件 |
计算字段和约束
@api.depends('subtask_ids.state')
def _compute_progress(self):
for task in self:
total = len(task.subtask_ids)
done = len(task.subtask_ids.filtered(
lambda t: t.state == 'done'
))
task.progress = (done / total * 100) if total else 0
@api.constrains('deadline')
def _check_deadline(self):
for task in self:
if task.deadline and task.deadline < fields.Date.today():
raise ValidationError(
"Deadline cannot be in the past."
)
继承机制
Odoo 提供了三种类型的继承,每种类型都有不同的用途:
1.类继承(扩展)
通过添加字段或覆盖方法来扩展现有模型。这是最常见的模式。
class SaleOrderExtend(models.Model):
_inherit = 'sale.order'
custom_reference = fields.Char(string='Custom Ref')
approved_by = fields.Many2one('res.users')
def action_confirm(self):
# Add custom logic before standard confirmation
for order in self:
if order.amount_total > 10000 and not order.approved_by:
raise UserError("Orders over $10,000 require approval.")
return super().action_confirm()
2.原型继承
创建一个新模型,复制现有模型中的所有字段和方法。
class CustomPartner(models.Model):
_name = 'my_module.partner'
_inherit = 'res.partner' # Copies structure
_description = 'Custom Partner'
3.委托继承
创建一个新模型,通过 Many2one 链接委托给现有模型。父模型的字段透明地显示在子模型上。
class LibraryMember(models.Model):
_name = 'library.member'
_inherits = {'res.partner': 'partner_id'}
partner_id = fields.Many2one('res.partner', required=True,
ondelete='cascade')
membership_date = fields.Date()
member_number = fields.Char()
OWL 框架(前端)
Odoo 18 使用 OWL(Odoo Web Library)作为其前端框架。 OWL 是一个基于组件的框架,类似于 React 或 Vue,但专为 Odoo 的需求而设计。
基本 OWL 组件
/** @odoo-module */
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
class TaskDashboard extends Component {
static template = "my_module.TaskDashboard";
setup() {
this.state = useState({
tasks: [],
filter: 'all',
});
this.loadTasks();
}
async loadTasks() {
this.state.tasks = await this.env.services.orm.searchRead(
"my_module.task",
[["state", "!=", "cancelled"]],
["name", "state", "assigned_to", "deadline"]
);
}
get filteredTasks() {
if (this.state.filter === 'all') return this.state.tasks;
return this.state.tasks.filter(
t => t.state === this.state.filter
);
}
}
QWeb 模板 (XML)
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="my_module.TaskDashboard">
<div class="o_task_dashboard">
<div class="task-filters">
<button t-on-click="() => state.filter = 'all'">All</button>
<button t-on-click="() => state.filter = 'in_progress'">
In Progress
</button>
</div>
<div class="task-list">
<t t-foreach="filteredTasks" t-as="task" t-key="task.id">
<div class="task-card">
<span t-esc="task.name"/>
</div>
</t>
</div>
</div>
</t>
</templates>
安全配置
每个模型都需要明确的访问规则。没有它们,任何用户都无法访问模型的数据。
访问控制列表 (ir.model.access.csv)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_task_user,task.user,model_my_module_task,base.group_user,1,1,1,0
access_task_manager,task.manager,model_my_module_task,my_module.group_manager,1,1,1,1
记录规则(security.xml)
<record id="task_own_rule" model="ir.rule">
<field name="name">Own Tasks Only</field>
<field name="model_id" ref="model_my_module_task"/>
<field name="domain_force">
[('assigned_to', '=', user.id)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
测试你的模块
Odoo 支持使用 unittest 框架和 Odoo 特定测试类的 Python 单元测试:
from odoo.tests.common import TransactionCase
class TestTask(TransactionCase):
def setUp(self):
super().setUp()
self.task = self.env['my_module.task'].create({
'name': 'Test Task',
'state': 'draft',
})
def test_task_creation(self):
self.assertEqual(self.task.state, 'draft')
self.assertEqual(self.task.progress, 0)
def test_deadline_constraint(self):
with self.assertRaises(ValidationError):
self.task.write({
'deadline': fields.Date.subtract(
fields.Date.today(), days=1
),
})
使用以下命令运行测试: odoo-bin -d test_db --test-enable -i my_custom_module --stop-after-init
常见问题
问:构建自定义 Odoo 模块需要多长时间? 简单的模块(新字段、基本视图)需要 1-3 天。中等模块(新模型、工作流程、报告)需要 1-3 周。复杂的模块(多模型系统、外部集成、自定义 OWL 组件)需要 4-12 周。对于需要自定义模块但缺乏内部 Odoo 开发人员的企业,请从我们的团队中聘请经验丰富的 Odoo 开发人员。
问:我应该修改核心 Odoo 代码还是创建一个单独的模块? 始终创建一个单独的模块。修改核心代码会破坏可升级性并在版本更新期间产生合并冲突。使用继承来扩展自定义模块中的现有模型和视图。
问:OCA 指南是什么? Odoo 社区协会 (OCA) 发布了模块质量的编码标准,包括命名约定、文档要求、测试覆盖率期望和代码风格规则。遵循 OCA 指南可确保您的模块可维护并与更广泛的社区生态系统兼容。
获得专业帮助
构建自定义 Odoo 模块需要 Python、JavaScript、PostgreSQL 和 Odoo 框架约定方面的专业知识。无论您需要简单的现场扩展还是复杂的多模块系统,ECOSIRE 的 Odoo 定制服务 都能提供根据 OCA 标准构建的生产就绪模块。
联系我们 讨论您的自定义模块需求并获得开发估算。
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
AI 支持的客户细分:从 RFM 到预测聚类
了解 AI 如何将客户细分从静态 RFM 分析转变为动态预测聚类。使用 Python、Odoo 和真实 ROI 数据的实施指南。
用于供应链优化的人工智能:可见性、预测和自动化
利用人工智能改变供应链运营:需求感知、供应商风险评分、路线优化、仓库自动化和中断预测。 2026年指南。
B2B电子商务战略:2026年打造在线批发业务
通过批发定价、帐户管理、信用条款、打孔目录和 Odoo B2B 门户配置策略来掌握 B2B 电子商务。