登录odoo后,可以看到已经默认包含了将近60个应用,有些免费的应用直接安装即可使用,有些需要付费升级。odoo的应用是一类功能完整的模块,odoo中的一切都是通过模块来实现的。从技术上看,应用和模块的区别仅仅是配置文件中application字段是否设置为True,从而导致是否可在应用列表的默认过滤中呈现。
odoo包含了若干核心模块和超过3000个社区模块,囊括了项目管理、生产管理、库存管理、供应链管理、财务管理、销售管理、市场营销、人力资源管理、门户网站管理等,几乎可以满足企业运营管理的一切需求。今天就来探索如何自定义一个模块。
构建一个odoo模块
1. 模块的组成元素
一个odoo模块本质上就是一个Python模块,存放在一个目录中,包含__init__.py文件,用于暴露模块接口。一个模块包含了四类文件:业务对象文件、数据文件、控制器文件和静态web数据文件,其软件架构是典型的MVC三层模型:业务对象是一个python类,定义了数据模型,属于模型层(M);数据文件中包含了视图、工作流和配置数据、演示数据等,与静态web数据一起构成了视图层(V);控制器文件处理客户端发来的requests,属于控制层(C)。
odoo提供了一个脚手架命令:scaffold,可以辅助我们创建一个空模块。切换到odoo主目录,在终端执行:
- ./odoo-bin scaffold testmodule my-modules
可在odoo主目录下自动创建my-modules目录,并在该目录下生成一个testmodule文件夹,该文件夹就是我们自定义的一个模块,里面包含了该模块的基本文件结构。
__manifest__.py文件是该模块的配置文件,其基本内容如下:
{
'name': "A Module",
'version': '1.0',
'depends': ['base'],
'author': "Author Name",
'category': 'Category',
'description': """
Description text
""",
# data files always loaded at installation
'data': [
'views/mymodule_view.xml',
],
# data files containing optionally loaded demonstration data
'demo': [
'demo/demo_data.xml',
],
'application':True,
...
}
这里仅列出了部分属性,完整的属性清单可在官网查看。
2. 加载一个空白模块和删除模块
打开odoo的默认启动配置文件~/.odoorc,将刚刚生成的my-modules路径添加到addons_path中。重新启动odoo(双击ctrl+c),登录之后可激活开发者模式(左上角点击设置,下拉到最底部,可看到开发者模式选项)。激活开发者模式后,在顶部标题栏可以看到刷新本地模块的按钮。刷新模块后,可在应用页面搜索testmodule,则可找到刚添加的这个空白模块。点击模块信息,可以看到所列出的内容和刚才在__manifest__.py文件中配置的信息一致。
安装后可以选择升级或者卸载。若将这个模块对应的文件夹删除,该模块图标仍然留在应用列表中,因为数据库中还有相关信息未删除。用pgadmin4打开odoo数据库,对ir_module_module表执行SQL删除指令:delete from ir_module_module where name='testmodule',刷新应用列表,则已彻底删除。
3. 数据模型
odoo的数据模型是一个继承自Model的类,其可以直接转换为数据库对象,底层封装了与数据库交互的原始sql代码,通过ORM(object-relation-map)机制实现数据模型与关系数据库的映射。odoo的数据模型文件应该放在models子目录中(约定俗成,不放在其中也可以)。数据模型文件中包含了模型的属性、字段(字段属性)和ORM方法修饰器。ORM方法修饰器可以实现类似数据库的约束、视图和存储过程功能。
3.1 模型属性
如下定义了一个最简单的模型,__name属性定义数据模型名称,系统会按照一定规则根据该名称生成数据库表名。postgres数据库表名长度不能超过64位,为了避免自动生成的表名过长,可添加_table参数,指定表名。例如:
from odoo import models
class testmodule(models.Model):
_name = 'testmodule.onemodel'
_table = ‘testmodule_onemodel’
_description = '''说明文字
可以分行'''
数据模型的常用属性如下:
attribute | describe |
---|---|
_name | odoo的内部标识符 |
_table | 通过该属性指定表名 |
_descirption | 对用户友好的表标题 |
_order=“name, price desc” | 设置列表视图的排序字段 |
_log_access=False | 设置不自动创建审计追踪字段:create_uid,create_date,write_uid,write_date,这四个是odoo数据模型的保留字段 |
_auto=False | 用于设置不自动创建模型对应的数据表 |
_inhert | 继承模型的属性字段 |
3.2 模型字段
模型字段对应数据库表中的字段,也是通过数据模型的属性来表示,但这是一类继承自fields模块下Field基类的特殊类型对象,如下所示,为数据模型增加了两个字段name和value,它们的类型分别是char和integer类型:
class testmodule(models.Model):
_name = 'my_test_model'
_description = '''说明文字
可以分行'''
name = fields.Char()
value = fields.Integer()
模型字段的所有类型如下表所示:
type | describe |
---|---|
Char | 单行文本,唯一位置参数是string字段标签 |
Text | 多行文本,唯一位置参数是string字段标签 |
Selection(selection, string) | 是一个下拉选择列表,selection参数是一个键值对:[(“value”, “title”)] |
Html | 文本字段,出于安全考虑,该字段会针对HTML的特殊字符进行处理,但处理过程可被重载。 |
Integer | 唯一位置参数是string字段标签 |
Float(string,digits) | 第二个可选参数digits,该字段是一个指定字段精度的(x,y)元组,x是数字总长,y是小数位 |
Monetary(string,currency_field) | 与浮点字段类似,但带有货币的特殊处理。第二个参数currency_field用于指定货币类型,默认应传入curency_id字段 |
Date 和 Datetime | 日期和时间 |
Boolean | 布尔类型 |
Binary | 存储文件类二进制文件,唯一位置参数是string字段标签 |
在定义字段时,可以给定相关属性,常用的字段属性如下表所示:
attribute | describe |
---|---|
readonly = True | 使该字段不能在用户界面上编辑。 |
string | 字段默认标签,用于在用户界面中使用。 除了选择和关系字段,它是第一个位置 |
default | 为字段设置默认值。 它可以是一个静态字符串或数值,也可以是一个可调用的引用,一个命名函数或一个匿名函数(一个lambda表达式)。 |
help | 显示给用户的提示文本。 |
copy = False | 在使用ORM的重复记录功能copy()方法时,字段被忽略。默认情况下,非关系字段是可复制的。 |
group | 允许将字段的访问和可见性限制为仅某些组。 它需要以逗号分隔的安全组XML ID列表,例如groups =‘base.group_user,base.group_system’。 |
states | 传入依赖 state字段值的 UI 属性的字典映射值。可用属性有readonly,required和invisible,例如states={‘done’:[(‘readonly’,True)]} |
translate | 仅适用于Char,Text和Html字段,并使字段内容可翻译,为不同语言保存不同的值。 |
related | 关联字段 |
size | 设置最大允许长度,无特殊原因建议不要使用 |
trim | 默认值为True,自动去除周围的空格 |
deprecated=True | 当该字段被使用时,记录一条 warning 日志 |
3.3 ORM方法修饰器
odoo在api模块中封装了@api.depends、@api.constrains、@api.multi、@api.onchange等ORM方法装饰器,其本质是python的函数装饰器,python函数装饰器的详细介绍,可转到本文的番外篇查看。函数装饰器的作用是抽取出某些工作流程中较固定的公共操作,这样一来便于代码复用,二来便于升级。
3.3.1 @api.constrains
我们可以通过@api.constrains('字段名','字段名'...)对模型字段添加约束,当参数中指定的字段值发生改变时进行检查。比如下面这段代码,使用constrains装饰器对value字段进行限制,当记录中value字段的值发生变化时,_check_value就会被调用,遍历所有记录(self 是一个recordset, 对应多条记录)做调节判断,不满足就抛出ValidationError异常。
from odoo import models, fields, api, exceptions
class testmodule(models.Model):
_name = 'my_test_model'
_description = '''说明文字
可以分行'''
name = fields.Char()
description = fields.Text()
#########################
value = fields.Integer()
@api.constrains('value')
def _check_value(self):
for record in self:
if self.value > 100:
raise exceptions.ValidationError("value值大于100")
3.3.2 @api.depends
@api.depends('字段名'.'字段名'...)需要配合字段的compute属性使用。当字段指定了compute属性后,该字段值将依赖于其它字段计算得到,计算方法通过@api.depends装饰器包装。经过该装饰器包装后,当所指定的字段值发生变化时,将再次调用计算过程,并显示到界面。
from odoo import models, fields, api, exceptions
class testmodule(models.Model):
_name = 'my_test_model'
_description = '''说明文字
可以分行'''
name = fields.Char()
description = fields.Text()
value = fields.Integer()
@api.constrains('value')
def _check_value(self):
for record in self:
if self.value > 100:
raise exceptions.ValidationError("value值大于100")
#####################
value2 = fields.Float(compute="_value_pc", store=True)
@api.depends('value')
def _value_pc(self):
for record in self:
record.value2 = float(record.value) / 100
上面这段代码中,value2字段需要依赖其它字段计算得到,计算函数是_value_pc,该函数被@api.depends('value')装饰,将该计算过程注册到value字段发生改变的事件处理函数中。
3.3.3 其它ORM装饰器
通过scaffold脚手架生成的默认模块中,暂未涉及到其它几个ORM装饰器,为了尽快刷新模块观察发生的变化,这里先不对它们展开,等以后碰到了再研究,它们是@api.onchange、@api.model、@api.multi、@api.one。
4. 刷新模型,观察变化
重启odoo,在开发者模式下,点击应用列表页面标题栏的更新,将更新全部模块。模块更新后,点击左上角设置,在标题栏有一个”技术“下拉列表按钮,点开后选择数据库结构->模型,则可列出所有已安装模块的数据库模型。搜索”my_test_model“(这个是在模型中的_name字段定义的),可以找到我们刚才新建的数据模型。如下图所示,可看到除了我们定义的四个字段,还有一些odoo默认的保留字段。我们刚才定义value2时是通过compute属性进行依赖计算,可以看到其在数据库中是只读的。
后面将接着研究模块视图。