第七章 Odoo 12开发之记录集 - 使用模型数据

2023-05-30,,

在上一篇文章中,我们概览了模型创建以及如何从模型中载入和导出数据。现在我们已有数据模型和相关数据,是时候学习如何编程与其进行交互 了。模型的 ORM(Object-Relational Mapping)提供了一些交互数据的方法,称为 API(Application Programming Interface)。这包括基本的增删改查(CRUD)操作,也包括一些其它操作,如数据导入导出,以及改善用户界面和体验的工具方法。它还包含一些我们在前面文章中所看到的装饰器。这些都让我们可以通过添加新的方法来调用 ORM 进行相关操作。

本文主要内容有:

使用 shell 命令交互式地学习 ORM API
理解执行环境和上下文
使用记录集和作用域(domain)查询数据
在记录集中访问数据
在记录中写入
编写记录集
使用底层 SQL 和数据库事务

开发准备

本文代码使用交互式 shell 命令行执行,无需使用前面章节的代码。

使用 shell 命令行

Python带有命令行界面,是研究其语法一个很好的方式。Odoo 也有类似的功能,可以交互式的测试命令的执行效果,这就是 shell 命令行。在命令行中执行以下命令并指定数据库即可使用:

 

1

~/odoo-dev/odoo/odoo-bin shell -d dev12

在odoo的目录下可以直接用以下命令执行,odoo12是指数据库名

python odoo-bin shell -d odoo12

此时在终端上可以看到正常的服务启动信息,等到出现>>>Python提示符时即为完成,可以输入命令了。

ℹ️Odoo 9中的修改
shell 功能在9.0中才添加。Odoo 8.0可使用社区模块来添加这一功能。只需下载并放入 addons 路径即可使用,下载请见应用市场。

此处 self 表示管理员用户的记录,可通过如下命令进行确认:

 

1
2
3
4
5
6

>>> self
res.users(1,)
>>> self._name
'res.users'
>>> self.login
'__system__'

在以上 shell 会话中,我们检查了自己的环境:

self命令表示res.users记录集,仅包含一条 id 为1的记录
查看self._name获得记录集模型名,你可能猜到了,是’res.users’
记录的 name 值为OdooBot
记录的 login 字段值为__system__

ℹ️Odoo 12中的修改
id 号为1的超级用户由原来的 admin 变成无法直接登录的内部系统用户。现在 admin 的 id 号为 2并且不是超级用户,但默认各应用会将其加入所有安全组。主要原因是避免用户使用超级用户账号来执行日常操作。这样的风险是该用户会跳过权限规则并导致数据的不一致,比如跨公司(cross-company)关联。现在超级用户仅用于检测问题或具体的跨公司操作。

和 Python 一样,可通过 Ctrl + D退出该命令行。此时会结束服务并返回到系统shell 命令行。

执行环境

Odoo shell 中包含一个 self 引用,类似于在res.users模型的方法中看到的那样。如我们所见,self 是一个记录集。记录集自带环境信息,包括浏览信息的用户以及其它上下文信息,如语言和时区。下面我们会学习执行环境中可用的属性、环境上下文的用处以及如何修改该上下文。

环境属性

我们可通过如下代码查看当前环境:

 

1
2

>>> self.env
<odoo.api.Environment object at 0x7f78a26026a0>

self.env 中的执行环境中有以下属性:

env.cr是正在使用的数据库游标(cursor)
env.user是当前用户的记录
env.uid是会话用户 id,与env.user.id相同
env.context是会话上下文的不可变字典

环境还提供对带有所有已安装模型注册表的访问,如self.env[‘res.partner’]返回一条对 partner 模型的引用。然后我们还可以对其使用search()或browse()方法来获取记录集:

 

1
2

>>> self.env['res.partner'].search([('name', 'like', 'Ad')])
res.partner(10, 35, 3)

上例中返回的res.partner模型记录集包含三条记录,id 分别为10, 35和3。记录集并没有按 id 排序,因为使用了相应模型的默认排序。就 partner 模型而言,默认的_order为display_name。

环境上下文

环境上下文是一个带有会话数据的字典,可用于客户端用户界面以及服务端 ORM 和业务逻辑中。在客户端中,它可以把信息从一个视图带到另一个视图中,比如前一个视图中活跃的记录 id,通过点击链接或按钮,可将默认值带入到下一个视图中。在服务端中,一些记录集的值会依赖于上下文提供的本地化设置。具体的例子有lang键影响可翻译字段的值。上下文还可为服务端代码提供信号。比如active_test键在设为 False 时,会改变ORM中search()方法的行为,它会忽略记录中的active标记,inactive(假删除)的记录也会被返回。

客户端的初始上下文长这样:

 

1

{'lang': 'en_US', 'tz': 'Europe/Brussels', 'uid': 2}

补充:服务端查看上下文命令为self.context_get()或self.env.context

其中 lang 键为用户语言,tz 为时区信息,uid 为当前用户 id。记录中的内容随当前依赖的上下文可能会不同:

translated字段根据活跃的 lang 语言不同值也会不同
datetimep字段根据活跃的的 tz 时区不同时间会不同

在上一个视图中点击链接或按钮打开表单时,一个active_id键会被加入上下文,它带有原表单我们所在位置记录的 id。以列表视图为例,active_ids上下文键中包含上一个列表中所选择的记录 id 列表。

在客户端中,上下文可用于使用default_或default_search_前缀在目录视图上设置默认值或启动默认过滤器。举例如下:

设置当前用户为user_id字段默认值,使用{‘default_user_id’: uid}
在目标视图上默认启动filter_my_books过滤器,使用{‘default_search_filter_my_tasks’: 1}

修改记录集执行环境

记录集执行环境是不可变的,因此不能被修改,但我们可以创建一个变更环境并使用它来执行操作。我们通过如下方法来实现:

env.sudo(user)中传入一条用户记录并返回该用户的环境。如未传入用户,则使用__system__超级用户root,这时可绕过安全规则执行指定操作。
env.with_context(<dictionary>) 替换原上下文为新的上下文
env.with_context(key=value,…)修改当前上下文,为一些键设置值

此外还有一个env.ref()函数,传入一个外部标识符字符串并返回它的记录,请参见:

 

1
2

>>> self.env.ref('base.user_root')
res.users(1,)

使用记录集和作用域(domain)查询数据

在方法或 shell 会话中,self表示当前模型,并且我们仅能访问该模型的记录。要访问其它模型就需要使用self.env。例如self.env[‘res.partner’]返回一条对 Partner 模型的引用(也是一个空记录集)。我们可以使用search()或browse()来获取记录集,其中search()方法使用域表达式来定义记录选择范围。

创建记录集

search()方法接收一个域表达式并返回符合条件记录的记录集。空域[] 将返回所有记录。

ℹ️如果模型有特殊字段 active,默认只有active=True的记录才在选择范围内

还可以使用以下关键字参数:

order是一个数据库查询语句中ORDER BY使用的字符串,通常是一个逗号分隔的字段名列表。每个字段都可接DESC关键字,用于表示倒序排列。
limit设置获取记录的最大条数
offset忽略前 n 前记录,可配合limit使用来一次查询指定范围记录

有时我们只要知道满足某一条件的记录条数,这时可使用search_count()来返回记录条数而非记录集。这节约了先获取记录列表再记数的开销,在还没有获取记录集且仅想知道记录条数时这样会更高效。

browse()方法接收一个 ID 列表或单个ID并返回这些记录的记录集。在我们知道 ID 并想要获取记录时这就非常方便了。

一些使用示例如下:

 

1
2
3
4

>>> self.env['res.partner'].search([('name', 'like', 'Pac')])
res.partner(42, 62)
>>> self.env['res.partner'].browse([42, 62])
res.partner(42, 62)

域表达式

域(domain)用于过滤数据记录。它使用一个特殊语法来供 Odoo ORM解析,生成数据库查询中的 WHERE 表达式。域表达式是一组条件组成的列表,每个条件都是一个(‘字段名’, ‘运算符’, ‘值’)组成的元组,例如,[(‘is_done’,’=’,False)]是仅带有一个条件的有效域表达式。以下是对各个元素的说明:

字段名:是一个待过滤字段,可使用点号标记来表示关联模型中的字段
值:在 Python 表达式中运行。可使用字面值,如数字、布尔值、字符串和列表,也可使用运行上下文中的字段和标识符。针对域其实有两种运行上下文:
在窗口操作或字段属性等客户端中使用时,可使用原生字段值来渲染当前可用视图,但不能对其使用点标记符
在服务端使用时,如安全记录规则或服务端 Python 代码中,可以对字段使用点标记符,因为当前记录是一个对象
运算符:可以是以下中的一个
常用比较运算符有<, >, <= , >=, =和!=。
‘=like’和’=ilike’匹配某一模式,这里下划线_匹配单个字符,百分号%匹配任意一组字符。
‘like’匹配’%value%’模式,’ilike’与其相似但忽略大小写。还可以使用’not like’和’not ilike’运算符。
‘child of’在配置支持层级关联的模型中查找层级关系中的子级值。
‘in’ 和’not in’用于查看给定列表的包含,所以其值为一个列表。用于to-many关联字段时,in运算符和contains运算符一样。
‘not in’是in的反向运算,用于查看不在列表中的值。

域表达式是一个列表并且包含多个条件元组。默认这些条件使用AND逻辑运算符连接,也就是说它仅返回满足所有条件的记录。也可以使用显式逻辑运算符 – ‘&‘符号表示 AND 运算符(默认值),管道运算符’|‘表示OR运算符。这两个运算符会作用于接下来的两项,递归执行。后面我们会一起来详细了解。

ℹ️域表达式使用了更为正式的定义方式:前缀标记法,也称波兰表达式(Polish notation):运算符放在运算项之前。AND和OR是二元运算符,而NOT是一元运算符。

感叹号’!’表示NOT运算符,可用于下一项的运算,因此要放执行的否定项之前。例如[‘!’, (‘is_done’,’=’,True)]将过滤出所有未完成(not-don e)的记录。

下一项本身也可以是一个作用其后续项的运算符,形成一个嵌套条件。下例可以有助于我们进行理解。在服务端记录规则中,可以找到类似下面这样的域表达式:

 

1
2
3
4
5
6

['|',
    ('message_follower_ids', 'in', [user.partner_id.id]),
    '|',
        ('user_id', '=', user.id),
        ('user_id', '=', False)
]

这个域过滤出当前用户在follower列表中并且是负责人用户,或者没有负责人用户的用户集。第一个’|’或运算符作用于 follower 条件以及下一个条件的结果。下一个条件是后面两个条件的并集:用户ID是当前会话用户或未进行设置。下图是上例域表达式的抽象语法树表示:

在记录集中访问数据

一旦获取了数据集,就可以查看其中包含的数据了。下面的几个部分中我们就来看看如何访问记录集中的数据。我们可以获取单条记录的字段值,称为单例(singleton)。关联字段带有特殊属性,我们可通过点号标记来查看关联记录。最后我们一起思考处理日期和时间记录并进行格式转换。

访问记录中数据

记录集的一个特例是仅有一条记录,称为单例。单例仍是记录集,在需要记录集的地方均可使用。与多元素记录集不同,单例可使用点号标记访问它的字段,如:

 

1
2

>>> print(self.name)
OdooBot

下个例子中我们看看同一个 self 单例和记录集相同的行为,我们可对其进行遍历。它只有一条记录,所以只会打印出一个名称:

 

1
2
3
4

>>> for rec in self:
...     print(rec.name)
...
OdooBot

尝试访问有多条记录的记录集字段值会产生错误,所以在不确定操作的是否为单例数据集时就会产生问题。对于设计仅操作单例的方法,可在开头处使用self.ensure_one(),如果 self 不是单例时将抛出错误。

ℹ️空记录也是单例。这样很方便,因为访问字段会返回 None 而非抛出错误。对于关联字段同样如此,使用点号标记访问关联记录也不会抛出错误。

访问关联字段

如前面所见,模型可包含关联字段:many-to-one, one-to-many和many-to-many。这些字段类型的值为记录集。

对于many-to-one,其值可以是单例或空记录集。两种情况下都可以直接访问字段值。如下例中的命令是正确并安全的:

 

1
2
3
4
5
6
7
8

>>> self.company_id
res.company(1,)
>>> self.company_id.name
'YourCompany'
>>> self.company_id.currency_id
res.currency(1,)
>>> self.company_id.currency_id.name
'EUR'

为避免麻烦,空记录可像单例一样操作,访问其字段值不会返回错误而是返回 False。所以我们可以使用点号标记来遍历字段,而无需担心因其值为空而报错,如:

 

1
2
3
4

>>> self.company_id.parent_id
res.company()
>>> self.company_id.parent_id.name
False

访问时间和日期值

在记录集中,日期和日期时间值以原生 Python 对象展示,例如,在查询上次 admin 用户登录日期时:

 

1
2

>>> self.browse(2).login_date
datetime.datetime(2019, 1, 8, 9, 2, 54, 45546)

因为日期和日期时间是 Python 对象,它们可使用这些对象的所有功能。

ℹ️Odoo 12中的修改
date和datetime字段值以 Python 对象表示,而此前 Odoo 版本中它们以文本字符串表示。这些字段类型值仍可像此前 Odoo 版本中那样使用文本表示。

日期和时间在数据库中以原生的世界标准时间(UTC) 格式存储,不受时区影响。 在记录集中看到的datetime值也是 UTC格式,在客户端中向用户展示时,datetime值会根据当前会话的时间设置来转换成用户的时区。这一设置存储在上下文的tz键中,如{‘tz’: ‘Europe/Brussels’}。这一转换由客户端负责,而不是由服务端完成。

例如在布鲁塞尔(UTC+1)的用户输入12:00 AM数据库中会存储为10:00 AM UTC,而在纽约(UTC-4) 的用户查看时则为06:00 AM。

补充:请不要怀疑作者的数学是不是体育老师教的

第七章 Odoo 12开发之记录集 - 使用模型数据的相关教程结束。

《第七章 Odoo 12开发之记录集 - 使用模型数据.doc》

下载本文的Word格式文档,以方便收藏与打印。