2020-12-08 odoo探索日志:自定义模块

登录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文件夹,该文件夹就是我们自定义的一个模块,里面包含了该模块的基本文件结构。
    模块文件结构.png

__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文件中配置的信息一致。


空白模块.png

安装后可以选择升级或者卸载。若将这个模块对应的文件夹删除,该模块图标仍然留在应用列表中,因为数据库中还有相关信息未删除。用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属性进行依赖计算,可以看到其在数据库中是只读的。


新增模型.png

后面将接着研究模块视图。

你可能感兴趣的:(2020-12-08 odoo探索日志:自定义模块)