本章我们更进一步学习模型层,以及如何使用模型来设计支撑应用的数据结构。我们会探讨可用的模型类型,以及在使用这些类型时如何定义强制进行数据验证的约束。
模型由支持不同数据类型的数据字段组成,一些字段类型支持定义模型间的关联。对于字段更高级的使用包含使用具体的业务逻辑自动计算的值。
本文的主要内容有:
- 学习项目 - 优化图书应用
- 创建模型
- 创建字段
- 模型间的关联
- 计算字段
- 模型约束
- Odoo的 base 模型总览
通过这些内容,我们将学习如何为Odoo项目创建大型数据结构。在学完本章后,读者对于架构数据模型相关的功能应该会有清晰的认知。
开发准备
本文代码基于第三章 Odoo 15开发之创建第一个 Odoo 应用中所创建的代码。相关代码参见 GitHub 仓库的ch06/目录。
请将其添加至插件路径中,并安装好library_app模块。
学习项目 - 优化图书应用
在第三章 Odoo 15开发之创建第一个 Odoo 应用中,我们创建了一个library_app插件模块,实现了一个简单的library.book模型用于展示图书目录。本章中,我们将回到该模块来丰富存储的图书数据。
我们使用如下结构添加一个分类层级,用于图书分类:
- Name:分类标题
- Parent:所属父级分类
- Subcategories:将此作为父级分类的子分类
- Featured book或author: 此分类中所选图书或作者
我们会添加一些字段来展示 Odoo中字段的数据类型。我们还会为图书模型添加一些验证约束:
- 标题和出版日期应唯一
- 输入的ISBN应为有效
下面我们就更深度地学习Odoo模型,学习可以使用的所有选项。
创建模型
模型是 Odoo 框架的核心。它们描述应用的数据结构,是服务端应用和数据库存储之间的桥梁。可围绕模型实现业务逻辑来为应用添加功能,用户界面所提供的用户体验也建立在模型之上。
下面几节我们将学习模型的通用属性,用于影响行为,以及几种模型类型:普通(regular)模型、临时(transient)模型和抽象(abstract)模型。
模型属性
模型类可以使用控制行为的一些其它属性。以下是最常用的属性:
- _name:它是我们创建的 Odoo 模型的内部标识符,新建模型时为必填。
- _description:它是对用户友好的标题,指向单个模型记录,如Book。可选但推荐添加。如未设置,在加载过程中服务端日志会显示警告。
- _order:设置浏览模型记录时或列表视图的默认排序。其值为 SQL 语句中 order by 使用的字符串,所以可以传入符合 SQL 语法的任意值,它有智能模式并支持可翻译字段及多对一字段名。
我们的图书模型中已使用了_name 和_description属性。可以添加一个_order属性来默认以图书名排序,然后按出版日期倒序排(新出版在前)。
class Book(models.Model):
_name = 'library.book'
_description = 'Book'
_order = 'name, date_published desc'
在高级用例中还会用到如下属性:
- _rec_name:设置记录显示名的字段。默认为name字段,因此我们通常选择它作为记录标题。
- _table:是模型对应的数据表名。通常由ORM自动设置,替换模型名称中的点为下划线,但是我们可以通过该属性指定表名。
- _log_access=False:用于设置不自动创建审计追踪字段,也即create_uid、create_date、write_uid和write_date。
- _auto=False:用于设置不自动创建模型对应的数据表。这时应使用init()方法来编写创建数据库对象、数据表或视图的具体逻辑。通常用于支持只读报表的视图。
例如,以下代码为 library.book模型设置了一些默认值:
_recname = "name"
_table = "library_book"
_log_access = True
_auto = True
注:还有用于继承模块的_inherit和_inherits属性,在本文后续会深入学习。在第四章 Odoo 15开发之模块继承中已详细讲解过。
在使用_auto = False时,我们是在重载创建数据库对象的过程,所以应编写相应逻辑。常用的场景是报表 ,基于收集报表所需数据的数据库视图。
以下是从sale内核模块中抽取的示例,来自sale/report/sale_report.py文件:
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute(
"CREATE or REPLACE VIEW %s as (%s)"
% (self._table, self._query())
)
以上代码使用了Python模块tools,需要使用from odoo import tools
导入。
模型和 Python 类
Odoo 模型使用Python类,在前面的代码中,有一个继承了 models.Model类的 Python 类:Book,用于定义名为library.book的Odoo模型。
Odoo的模型保存在中央注册表(central registry)中,可通过环境对象获取,通常为self.env。中央注册表保存对所有模型的引用,过使用类字典语法访问。
例如,可在方法内使用self.env['library.book']或self.env.get(["library.book"])来获对图书模型的引用。
可以看出模型名非常重要,它是访问模型注册表的关键。
模型名必须全局唯一。因此使用模块所属应用的每个单词作为模型名称的第一个单词是一种良好实践。就Library应用而言,所有的模型名应使用library前缀。内核模型的其它例子有project、crm或sale。
小贴士:模型名应使用单数形式library.book,不应使用library.books。规范是以点号连接一组小写的单词。第一个单词标识所属主应用,如library.book或library.book.category。例如官方插件中有project.project、project.task和project.task.type。
此外,Python类名为Python文件本地所有,声明与Odoo框架没有关联。所用的名称仅在该文件中具有意义,关联度不高。Python有关类名按照PEP8的规范是使用驼峰命名(CamelCase)。
存在几种模型类型。最常用的一种是models.Model类,用于数据库持久化存储模型。接下来我们学习其它几种模型类型。
临时(Transient)模型和抽象模型
大多数据 Odoo 模型中的类会继承models.Model类。这类模型在数据库中持久化存储,会为模型创建数据表并存储记录直至删除。一般使用它就够了。
但有时我们并不需要持久化的数据库存储,这时就可以使用下面两类模型:
- 临时模型继承models.TransientModel类,用于向导式的用户交互。这类数据会存储在数据库中,但仅是临时性的。会定时运行清空 job 来清除这些表中的老数据。比如Settings > Translations > Import Translation 菜单打开一个对框窗口,使用临时模型存储用户选项并实现向导逻辑。在第八章 Odoo 15开发之业务逻辑 - 业务流程的支持中会有讨论临时模型的示例。
- 抽象模型继承models.AbstractModel类,它不含数据存储。抽象模型用作可复用的功能集,与使用 Odoo 继承功能的其它模型相配合。例如mail.thread是 Discuss 应用中的一个抽象模型,用于为其它模型添加消息和关注功能。使用抽象模型的 mixin 类以及前述的 mail.thread示例在第四章 Odoo 15开发之模块继承中进行了讨论。
查看已有模型
通过 Python 类创建的模型和字段可通过用户界面查看。启用开发者模式,访问菜单Settings > Technical > Database Structure > Models,这里有数据库中的所有模型。
点击列表中的模型会打开详情表单,如下图所示:
图6.1:通过Technical菜单查看图书模型
这是一个查看模型结构很好的工具,因为在这里可以看到不同模块所有的修改结果。表单视图右上角 In Apps字段中可以看到对其施加影响的模块列表。本例中library.book模型受library_app和library_member两个模块的影响。
小贴士:如第一章 使用开发者模式快速入门 Odoo 15中所见,模型表单是可编辑的。通过这里是可以创建并修改模型、字段和视图的。可在此处创建原型然后在插件模块中实现。
下方区域中还有几个包含其它信息的标签:
- Fields显示模型的字段
- Access Rights显示授予不同权限组的访问控制规则
- Record Rules