Odoo18官方入门教程
官方教程是学习Odoo的最佳选择。
本文档以odoo官方的入门教程为教材,通过一个小案例,循序渐进的介绍了Odoo的基本概念,并且演示了如何开发一个全新的模块。
B站视频网址:https://space.bilibili.com/333178740/
0、前言
Odoo 是一个全球流行的开源企业管理套件,它为中小企业的数字化转型提供了一个全面而强大的解决方案。以下是选择 Odoo 作为中小企业数字化转型的几个理由:
-
开源性质:Odoo 是一个开源的 ERP 软件,这意味着企业可以自由选择部署方式(如云端或本地服务器),并根据自身需求进行定制开发。这为企业提供了更大的灵活性和控制权。
-
模块化设计:Odoo 采用模块化设计,用户可以按需选用不同模块组合成完整解决方案。这使得企业能够轻松扩展功能,并降低了实施和维护成本。
-
易于集成:Odoo 支持与其他系统无缝对接,例如电商平台、物流企业、第三方支付渠道以及 CRM、钉钉、企业微信等。这有助于提高数据流通性和工作效率。
-
用户友好界面:Odoo 提供直观易操作的界面,降低了使用门槛。即使没有专业技术背景的员工也能快速上手使用。
-
强大社区支持:作为一款受欢迎的开源软件,Odoo 拥有庞大且活跃的社区资源。用户可以从中获得技术支持、教程以及插件等资源,帮助解决问题并加速项目进度。
-
适应多种行业需求:Odoo 覆盖广泛行业领域,包括零售、制造、服务等多个领域,并针对各类企业规模进行优化配置。因此无论是初创公司还是大型企业都可找到适合自己需要的解决方案。
-
成熟稳定且不断更新升级:Odoo 已经历过多次版本迭代,在功能和稳定性上都表现出较高水平;同时官方团队会根据市场反馈及时推出新功能和修复漏洞,保证其产品始终处于竞争力状态。
-
成本效益:Odoo 的开源特性意味着企业可以以较低的成本获得强大的 ERP 功能,这对于预算有限的中小企业来说尤其有吸引力。
-
快速部署与实施:中小企业可以快速部署 Odoo 并进行快速迭代,这有助于企业迅速响应市场变化。
-
一站式解决方案:Odoo 提供了从前端销售到后端生产、库存、财务等全方位的管理功能,企业无需购买多个软件即可实现全面的数字化管理。
-
支持全球化及私有化部署:Odoo 支持全球化部署,也支持私有化部署,这使得企业可以根据自己的需求和条件选择最合适的部署方式。
综上所述,Odoo 的开源性、模块化、易用性、社区支持和成本效益等特点,使其成为中小企业数字化转型的理想选择。
1、架构概述
1.1 三层架构
odoo是标准的三层架构:
-
表示层是 HTML5、JavaScript 和 CSS 的组合。
-
逻辑层完全用 Python 编写。
-
而数据层仅支持 PostgreSQL 作为 RDBMS。
1.2 Odoo模块
服务器和客户端扩展都打包为模块,可选择加载到数据库中。模块是针对单一目的的功能和数据的集合。
Odoo 中的一切都以模块开始和结束。
Odoo 模块由其清单声明。
这是一个简化的模块目录:
module
├── models
│ ├── *.py
│ └── __init__.py
├── data
│ └── *.xml
├── __init__.py
└── __manifest__.py
1.3 Odoo版本
分社区版和企业版,二者使用的底层框架是一样的,只是addons不同。
2、新应用
最简单的应用,新建一个目录estate,只需要两个文件,记得要把estate所在的目录包含到addons_path中
estate/__init__.py
estate/__manifest__.py
__init__.py 可有保留为空
__manifest__.py 清单文件内容如下
{
"name": "estate",
"depends": ["base"],
}
这样odoo就能识别出这个应用了,记得要在开发者模式下更新一下应用程序列表。
3、模型和基本字段
3.1 模型
Odoo 的一个关键组件是ORM层。该层避免了手动编写大多数SQL ,并提供了可扩展性和安全性服务2。
业务对象被声明为扩展的 Python 类 Model,将其集成到自动持久性系统中。
可以通过在模型定义中设置属性来配置模型。最重要的属性是 _name,它是必需的,它定义了 Odoo 系统中模型的名称。以下是模型的最小定义:
from odoo import models
class TestModel(models.Model):
_name = "test.model"
上面这个模型虽然没有定义任何字段,但是odoo的ORM依然会自动生成对应的数据库表, 包含五个字段,分别是:
id(Id)
模型记录的唯一标识符。
create_date(Datetime)
记录的创建日期。
create_uid(Many2one)
创建记录的用户。
write_date(Datetime)
记录的最后修改日期。
write_uid(Many2one)
最后修改记录的用户。
Odoo会为每个模型都会自动添加上述字段。
3.2 字段
字段有两大类:“简单”字段,即直接存储在模型表中的原子值;“关系”字段,即链接记录(相同或不同模型的记录)。
简单字段示例 有Boolean、Float、 Char、Text和。DateSelection
某些属性在所有字段上都可用,以下是最常见的属性:
-
string(str,默认:字段名称)UI 中字段的标签(用户可见)。
-
required(bool, 默认:False)如果
True,则字段不能为空。它必须具有默认值,或者在创建记录时始终赋值。 -
help(str, 默认:'')在 UI 中为用户提供长格式的帮助工具提示。
-
index(bool, 默认:False)请求 Odoo在该列上创建数据库索引。
4、安全性-简介
4.1 数据文件
数据文件最终都会在安装或者升级模块的时候导入进数据库中,成为数据库模型中的记录。
而且每一行记录都有一个外部ID, 这是一个字符串, 而内部ID是一个整数。 二者在模型ir_model_data 中保存了映射关系。其中res_id 为内部id, name为外部id, model为模型。
SELECT * FROM public.ir_model_data where model='ir.model.access' ORDER BY id ASC
数据文件有两种类型:
xml
导入性能比较慢,适合结构复杂,数量比较少的数据导入。 比如我们常见的菜单、动作、视图都是通过xml来定义的。
csv
导入性能比较快,适合结构比较简单的数据导入。注意文件名一定要和模型名称一致。
4.2 访问权限
ir.model.access 对应表级别的访问权限, 用户组对应模型上的读、写、增、删 四种权限。
5、一些可玩的UI
核心概念:菜单 > 动作 > 视图 菜单和视图都是可见的,二者通过动作来连接,动作有很多类型。常见的是ir.actions.act_window
5.1 菜单
菜单总是遵循一定的架构,实际上菜单有三个级别:
-
根菜单,显示在应用程序切换器中(Odoo 社区应用程序切换器是一个下拉菜单)
-
一级菜单,显示在顶栏
-
动作菜单
菜单的基本定义:
<menuitem id="test_menu_root" name="Test">
<menuitem id="test_first_level_menu" name="First Level">
<menuitem id="test_model_menu_action" action="test_model_action"/>
</menuitem>
</menuitem>
5.2 动作Action
动作的基本定义:
<record id="test_model_action" model="ir.actions.act_window">
<field name="name">Test action</field>
<field name="res_model">test_model</field>
<field name="view_mode">list,form</field>
</record>
可以通过三种方式触发动作:
-
通过点击菜单项(链接到特定动作)
-
通过单击视图中的按钮(如果这些按钮与动作相关)
-
作为对象上的上下文动作
5.3 视图
如果不创建视图,odoo框架会自动为模型创建默认视图。 不过我们通常情况下不会使用默认视图。
视图有很多种,比如常见的列表视图、表单视图、搜索视图、看板视图。每一种都是从不同的角度来展现同一个模型的数据。
练习难点:
date_availability = fields.Date(string="可售日期",default=lambda self: fields.Datetime.now()+timedelta(days=90))
保留字段:
state 状态字段,多用于UI增强,或者简单的审批流。
active 是否存档,实现假删除的效果
6、基本视图
视图在 XML 文件中定义,它们是 ir.ui.view模型的实例。
确保视图的id 是唯一的!
6.1 列表视图
以表格的形式展现模型的多条记录,带有过滤和分组功能,可以实现简单的数据分析。
从odoo18开始,列表视图的根元素从之前的tree改成了list
列表视图的定义案例
<record id="estate_property_view_tree" model="ir.ui.view">
<field name="name">estate_property_view_tree</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<list >
<field name="state"/>
<field name="name"/>
<field name="postcode"/>
</list>
</field>
</record>
根属性:
string: 只有在对应的action没有name,并且以对话框的形式打开的时候有效。target = new
create: 是否可以增加记录。(默认True)
edit: 是否可以编辑记录(默认True)
delete: 是否可以删除记录(默认True)
import:是否可以导入数据(默认True)
export_xlsx: 是否可以导出excel数据
editable: 直接在列表中编辑数据,有两种取值 top 或者bottom,如果edit设置为false,该项失效。
multi_edit: 是否允许编辑多行,只能设置成“1”。
open_form_view: 在行末尾显示一个按钮来打开form视图。(默认False),如果视图不允许编辑则无效。
default_group_by: 默认的分组字段。如果在动作和search视图中没有设置的话。
default_order: 逗号分割的排序字段, 会覆盖模型中用_order定义的排序。
decoration-style: 设置显示的风格,style可以是info, warning, danger, muted, primary, and success.
limit: 分页条数,默认80.
groups_limit: 分组的分页条数,列表视图默认80,form视图中的x2many字段是40
expand : 当视图被分组的时候,是否展开第一层分组。(默认False)
6.2 表单视图
显示并编辑单条记录,根节点是form。
根元素form有如下属性:
-
string 打开对话框并且action中没有设置name的情况下,显示这个值。
create 是否创建记录(默认值:True)
-
edit 是否编辑记录(默认值:True)
-
duplicate 是否可有复制记录(默认值:True)
-
delete 是否可有删除记录(默认值:True)
js_class 自定义js组件,用来自定义表单视图。
-
disable_autofocus 禁用对视图中第一个字段的自动聚焦。(默认值:false)
语义部分
语义组件与 Odoo 系统相关并允许与其交互。
表单视图接受以下子语义组件:字段、标签、 按钮、 Chatter 小部件和 附件预览小部件。
6.3 搜索视图
Search domains
难点是domain,特别是复杂的domain。 用的是前缀表示法,也叫波兰表示式。
7、模型之间的关系
7.1、多对一
通过many2one字段表示, 本质是父表和子表的。 字段一般以id结尾
property_type_id = fields.Many2one("estate.property.type",string="房屋类型")
7.2、多对多
通过Many2many字段指定。
可有指定中间表,也可以不指定。 显示的时候可以制定小部件widget=many2many_tags
7.3、一对多
通过One2many字段指定,是一种虚拟关系,存在的前提是有一个Many2one字段。
8、计算字段和Onchange
8.1 计算字段
概念: 顾名思义,计算字段是通过其他字段计算出来的,而不是直接从数据库中存取的。
from odoo import api
total = fields.Float(compute='_compute_total')
@api.depends('value', 'tax')
def _compute_total(self):
for record in self:
record.total = record.value + record.value * record.tax
-
计算字段使用depends来设置依赖的字段。
-
计算资源通过compute属性指定方法名,方法名一般以_compute开头。
-
计算字段对应的方法中,要使用for循环来遍历每一条记录。
-
计算字段可以设置逆向方法,通过inverse参数来制定。
-
可以在同一个方法中计算多个字段,只需对所有字段使用相同的方法并设置所有字段
-
虽然可以对多个字段使用相同的计算方法,但不建议对逆方法执行相同的操作。
-
计算字段默认不在数据库中存储并且在视图上是只读的,如果希望存储到数据中通过设置store=True来实现。
-
请注意,compute和inverse调用的时机不同,当保存记录时调用
inverse方法,而当其依赖项发生变化时,将调用compute方法。 -
计算字段好用,但不要滥用。如果记录条数很多,可能会引发性能问题。
8.2 onchanges
onchange只在表单视图中触发,所以不用循环self,self始终是单个记录。
onchange还可以返回警告消息
@api.onchange('provider', 'check_validity')
def onchange_check_validity(self):
if self.provider == 'authorize' and self.check_validity:
self.check_validity = False
return {'warning': {
'title': _("Warning"),
'message': ('This option is not supported for Authorize.net')}}
有时候计算字段和onchange能实现同样的效果,建议使用计算字段。因为onchange只在表单视图中触发。
9、准备采取行动了吗?
button有两种用法
9.1 调用模型中的方法,name为方法名。
<button name="action_do_something" type="object" string="Do Something"/>
9.2执行一个动作,name为动作ID,注意格式。
<button type="action" name="%(test.test_model_action)d" string="My Action"/>
10、约束
10.1 SQL约束
_sql_constraints = [
('check_expected_price', 'CHECK(expected_price >0)','房产预期价格必须严格为正数.'),
('check_selling_price', 'CHECK(selling_price >0)','房产售价必须为正数.'),
]
每个sql约束都是一个三元组(约束名称,约束定义,不满足约束时的提示信息), sql约束会通过odoo的ORM映射到PG数据库中,所以sql约束性能比较高。
能用sql约束尽量用sql约束。
10.2 Python约束
当约束比较复杂,用sql约束难以实现的时候使用Python约束, Python约束使用@api.constrains 装饰器,装饰器可以指定字段, 装饰的方法触发的条件就是当这些字段发生变化的时候。
from odoo.exceptions import ValidationError
...
@api.constrains('date_end')
def _check_date_end(self):
for record in self:
if record.date_end < fields.Date.today():
raise ValidationError("The end date cannot be set in the past")
11、添加点缀
本章比较长,主要是讲解如何增强UI,提高用户体验。
11.1、内连视图
特指One2many字段,不使用默认的视图,直接在表单视图中定义。
<form>
<field name="description"/>
<field name="line_ids">
<list>
<field name="field_1"/>
<field name="field_2"/>
</list>
</field>
</form>
11.2、小部件
每种字段类型都有一组小部件,可用于微调其显示。
比如state字段,通过在表单视图顶部显示一排按钮来显示。
<field name="state" widget="statusbar" statusbar_visible="open,posted,confirm"/>
还有我们用过的widget=“many2many_tags”
11.3、列表顺序
可有在模型上制定默认的排序
_order = "id desc"
也可以在视图中指定排序,优先级高于模型中指定的
<list default_order="date desc">
<field name="date"/>
<field name="author_id"/>
</list>
还可以手工排序
sequence`字段与小部件结合使用`handle
<field name="sequence" widget="handle"/>
11.4、属性和选项
Form视图
每一个字段都有一组widget来显示,每一个widget都可以设置属性和options。
<field name="datefield" options="{'min_date': 'today', 'max_date': '2023-12-31'}" />
<field name="category_ids" widget="many2many_tags" options="{'color_field': 'color'}" />
列表视图
可有根据不同的条件显示不同行的颜色
<list decoration-success="is_partner==True">
<field name="name"/>
</list>
搜索视图
指定默认的过滤器,需要和action配合使用
过滤器:
<filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
action:
<record id="crm_opportunity_report_action" model="ir.actions.act_window">
<field name="name">Pipeline Analysis</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">pivot,graph,tree,form</field>
<field name="context">{'search_default_opportunity': True, 'search_default_current': True}</field>
</record>
格式就是在动作的定义中指定上下文 search_default_${name},${name}为过滤器名称。
11.5 、统计按钮
oe_stat_button,一般显示在表单视图的上方。
在源码中搜索相关的例子:
<div class="oe_button_box" name="button_box">
<button name="action_lost_leads" type="object"
class="oe_stat_button" icon="fa-star">
<div class="o_stat_info">
<field name="leads_count" class="o_stat_value"/>
<span class="o_stat_text"> Leads</span>
</div>
</button>
</div>
o_stat_info 中的内容是可以自己定义的。
关联字段
通过related字段指定。
description = fields.Char(related="partner_id.name")
12、继承
Odoo是一个模块化的,通用的,扩展性很强的Erp框架,那么它的扩展性是如何实现的? 就是通过继承。
12.1、python继承
所有的Model都继承了models.Model, 可有重写对应的增删改查方法。
注意: 最后一定要返回跟原方法同类型的值。
class TestModel(models.Model):
_name = "test_model"
_description = "Test Model"
...
@api.model
def create(self, vals):
# Do some business logic, modify vals...
...
# Then call super to execute the parent method
return super().create(vals)
一般不会重写unlink()方法,而是通过ondelete装饰器,添加自己的逻辑,装饰的方法再unlink中调用,如果不符合逻辑,可有抛出异常中断删除的流程。
@api.ondelete(at_uninstall=False)
def _unlink_except_new_canceled(self):
for record in self:
if record.state not in ['new','canceled']:
raise odoo.exceptions.UserError("只有新建和取消状态的房产才能删除")
at_uninstall 参数指定卸载模型的时候是否执行这个逻辑。
12.2、模型继承
模型继承有三种方式,前两种是传统方式,第三种是委托继承。
1、传统继承,不产生新表
name可以省略,也可以和父模型一样。
class ResUsers(models.Model):
_inherit = "res.users"
property_ids = fields.One2many("estate.property","seller_id",string="我的房产",domain=['|',('state', '=', 'new'),('state', '=', 'offer_received')])
2、传统继承,产生新表
class ResUsersNew(models.Model):
_name = "res.users.new"
_inherit = ["res.users"]
property_ids = fields.One2many("estate.property","seller_id",string="我的房产",domain=['|',('state', '=', 'new'),('state', '=', 'offer_received')])
_name 不同于父表
3、委托继承
class ProductProduct(models.Model):
_name = "product.product"
_description = "Product Variant"
_inherits = {'product.template': 'product_tmpl_id'}
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'is_favorite desc, default_code, name, id'
_inherit 是一个数组
_inherits 是一个字典, 需要指定对应的模型以及many2one字段。数据存在多个表中,但是通过ORM是透明的,感觉像一个表一样。
12.3、视图继承
<record id="inherited_model_view_form" model="ir.ui.view">
<field name="name">inherited.model.form.inherit.test</field>
<field name="model">inherited.model</field>
<field name="inherit_id" ref="inherited.inherited_model_view_form"/>
<field name="arch" type="xml">
<!-- find field description and add the field
new_field after it -->
<xpath expr="//field[@name='description']" position="after">
<field name="new_field"/>
</xpath>
</field>
</record>
两种定位方式:
<xpath expr="//field[@name='description']" position="after">
<field name="idea_ids" />
</xpath>
<field name="description" position="after">
<field name="idea_ids" />
</field>
position有5种选项:inside replace before after attributes
13、与其他模块交互
通过self.env[model_name] 可有和任何模型交互。
14、QWEB简史
Qweb是Odoo的模板引擎, 在很多场景中都会使用它,比如kanban视图。
<kanban>
<templates>
<t t-name="kanban-box">
<div>
<field name="name"/>
</div>
</t>
</templates>
</kanban>
-
<templates>:定义QWeb Templates模板列表。看板视图必须定义至少一个根模板kanban-box,该模板将为每条记录渲染一次。 -
<t t-name="kanban-box">:<t>是 QWeb 指令的占位符元素。在本例中,它用于将name模板的设置为kanban-box -
<field name="name"/>:这会将该name字段添加到视图中。 -
record:一个以所有请求字段为属性的对象。每个字段有两个属性value和raw_value。前者根据当前用户参数格式化,后者是来自 的直接值read()。
15、最后的话
编码规范是必要的。
16、后续课程安排
恭喜各位,如果这个入门课程你们完全掌握了,那么你们odoo算是入门了,可有做一些简单的模块。
但是距离真正做企业应用还有很长一段距离。
比如说odoo的前段框架owl,我们完全没讲。 权限这块也没讲。 如何集成第三方的库,比如Echart做大屏展示,如何集成微信等。
这些进阶课会随着您在odoocc的信任等级提高逐步解锁,详情见:
感谢大家的支持,我们的入门课程到此结束。祝愿各位趁着数字化转型的东风,事业一帆风顺!
上一篇:


