【odoo14】【好书学习】第六章、管理模块数据

老韩头的开发日常【好书学习】系列

本章文字校对中

本章,我们将学习如果在模块安装时初始化数据。初始化默认值、添加元数据,比如添加View的描述、菜单及窗口动作信息等是非常用帮助的。另一个场景是提供演示数据,这些演示数据会在数据库被创建的时候随着勾选了“加载演示数据”一同创建。

本章我们将会包含如下诶:

本章代码可在原作者github下载


使用外部ID及命名空间

外部ID及XML ID用于标记记录。到目前为止,我们在视图、菜单及动作中接触了XML IDs。本节我们将进一步了解什么是XML ID。

步骤

  1. 更新my_library模块中的manifest文件
'data': [
	'data/data.xml',
],
  1. 创建一本书
<record id="book_cookbook" model="library.book">
	<field name="name"> Odoo 14 Development Cookbook </ field>
</record>
  1. 修改公司的名称
<record id="base.main_company" model="res.company">
	<field name="name">Packt publishing</field>
</record>

原理

XML ID是单条记录的引用。IDs是ir.model.data模型。这个模型包括模型的名称、ID及其他内容。
每次我们应用XML ID,odoo会检查字符串中是否包含命名空间。如果没有,它会添加当前模块名作为命名空间。并以此在ir.model.data中查找是否存在目标记录。如果存在,所列字段将会被更新。若不存在,将会创建。

重要提醒
除了更改其他模块定义的记录外,部分数据的一个广泛应用是使用快捷方式元素以方便的方式创建记录,并在该记录上写入一个字段,而快捷方式元素不支持该字段:

<act_window id="my_action" name="My action" model="res.partner" />
<record id="my_action" model="ir.actions.act_ window">
<field name="auto_search" eval="False" /> </record>

ref函数也将会添加当前模块作为命名空间。但如果生成的XML ID并不存在,将会报错。

小贴士
系统中的XML ID可在Settings|Technical|Sequence & Identifiers|External Identifiers中查看。

更多

我们后面将会需要在python代码中使用XML ID。可通过self.env.ref(XMLID)函数引用目标记录。
我们可以查找系统中任何数据的XML ID。如下:

通过xml文件载入数据

步骤

  1. 添加data/demo.xml文件
'demo': [
	'data/demo.xml',
],
  1. 添加内容
<odoo>
	<record id="author_pga" model="res.partner">
		<field name="name">Parth Gajjar</field> </record>
		<record id="author_af" model="res.partner">
		<field name="name">Alexandre Fayolle</field>
	</record>
	<record id="author_dr" model="res.partner">
		<field name="name">Daniel Reis</field>
		</record>
		<record id="author_hb" model="res.partner">
		<field name="name">Holger Brunn</field>
	</record>
	<record id="book_cookbook" model="library.book">
		<field name="name">Odoo Cookbook</field>
		<field name="short_name">cookbook</field>
		<field name="date_release">2016-03-01</field>
		<field name="author_ids"
		eval="[(6, 0, [ref('author_af'),
		ref('author_dr'), />
		ref('author_hb')])]"
		<field name="publisher_id" ref="res_partner_ packt" />
	</record>
</odoo>
  1. 在manifest添加data/data.xml
'data': [
	'data/data.xml', ...
],
  1. 添加内容
<odoo>
	<record id="res_partner_packt" model="res.partner">
	<field name="name">Packt Publishing</field>
	<field name="city">Birmingham</field>
	<field name="country_id" ref="base.uk" />
	</record>
</odoo>

原理

数据xml文件通过标签创建新记录。有两个强制属性,id和model。id,是数据的外部ID。model,是数据属于哪个模型。通过标签添加字段的值。
有两种方式将data的xml文件注册到模块中。分别是data和demo键。data键是在每次在加载模块的时候都会更新。demo是只有在启动了demo数据的时候才会加载。
步骤1,我们通过demo键注册了data xml文件。因为我们使用了demo的键,因此只有我们在启用了demo的时候才会加载。
步骤2,元素包含字符串值。如果你需要传文件的内容,可以通过在field中的file属性传递文件名。
为了设置引用,有两种方式。最简单的是通过ref属性,作用于many2one字段,通过引用目标记录的XML ID实现。对于one2many及many2many字段,我们使用eval属性。这是一通用属性,可以通过python代码计算字段值。比如为date字段赋值strftime('%Y-01-01')。X2many字段可被三元元组赋值,第一个元素是决定计算的方式。在eval属性中,我们通过ref获取XML ID数据的数据库ID。三元元组关系如下:

  • (0, False, {'key':value}): 创建一个新的记录并关联到目标模型上。由于这些记录没有XML ID,因此可能会出现重复的情况。因此,建议在创建记录的时候通过各自的模型进行创建。
  • (1, id, {'key':value}): 这会在一条已经存在的记录上执行写操作。同上,我们应该避免如此使用。
  • (2, id, False): 从数据库删除关联记录(id),第三个元素忽略。
  • (3, id, False): 解绑ID为id的关联记录,并不从数据库中删除。
  • (4, id, False): 添加关联一个在数据库中已经存在的记录。这是最常使用的。
  • (5, False, False): 取消所有的关联记录。
  • (6, False, [id, ...]): 取消所有关联记录,并关联新的记录。
    步骤3、4,类似1、2。

重要提醒
需关注数据文件中记录的添加的先后顺序。这也是为什么你需要检查模块在空数据库中安装的情况,因为在开发的过程中,可能已经添加了相关数据。

更多

record基本上可以完成所有的场景,还有一些简写方式可便捷的创建数据。比如,菜单、模板、窗体动作。
field元素还可以执行function元素,执行相应的函数。

使用noupdate及forcecreate标识

大不多模块有不同类型的数据。一些数据是为了模块能够正常运行,一些不应该被用户修改,大多数数据可以被用户修改,只是为了方便,通常情况下会通过数据文件预加载。本节将详细讨论不同类型的数据。首先我们会在一个已经存在的记录中某个字段写入数据,然后我们将在模块更新重复创建一条数据。

步骤

我们可以在元素或元素中设置不同的属性,以控制数据加载时不同的行为。

  1. 在初始化的时候添加一条出版商,但是在后续的模块升级中并不更新。但如果该记录被删除,那么将会被重新创建。
<odoo noupdate="1">
    <record id="res_partner_packt" model="res.partner">
        <field name="name">Packt publishing</field>
        <field name="city">Birmingham</field>
        <field name="country_id" ref="base.uk"/>
    </record>
</odoo>
  1. 添加一条图书种类的记录,但是该记录在模块升级的时候并不进行修改,在数据被删除后也不会再次创建。forcecreate="false"
<odoo noupdate="1">
    <record id="book_category_all" model="library.book. category" forcecreate="false">
        <field name="name">All books</field>
    </record>
</odoo>

原理

有noupdate属性,该属性将最为新纪录创建时的列。
当odoo安装模块的时候,无论noupdate为false还是true,所有的记录会被写入。但是当对模块进行升级的时候,将会检查已经存在的XML ID记录是否有noupdate标志位。如果设置了,将会忽略本次的数据写入。如果有问题的记录被用户删除,情况就不是这样了,这就是为什么您可以通过将记录上的forceccreate标志设置为false来强制不在更新模式下重新创建noupdate记录。

重要提醒
在odoo8及之前的版本中,你将会看到标签,内部包含标签及其他标签。这种语法当前依旧有效,但已经被启用了。现在有相似的效果。他们都将包含xml数据。

更多

如果你想在右noupdate标志位的情况下更新数据,那么你可以在运行odoo服务的时候添加--init=your_addon 或者 -i your_addon参数。这将强制重载数据。请注意,如果模块绕过了XML ID机制(例如,通过标签调用Python代码创建记录),这可能会导致双记录和相关的安装错误。
以上操作方式,将可能绕过noupdate标志位,一定要确保这是你想要的效果。另一个解决方案是编写迁移脚本,将在 下面几节中介绍。

参考

odoo也使用XML IDs跟踪在模块更新的时候那些数据将被删除。如果一条记录在模块更新前已经有了XML ID,但是在更新的时候模块中又没有该XML ID的记录情况,那么该记录将被认为过时了,将会被删除。

通过CSV载入数据库

通过XML文件可以满足很多场景,但是这种格式在需要处理大量数据的时候并不是太方便。通过CSV格式的另一个优势是导出数据的时候也是采用CSV的格式。

步骤

权限控制(access-control lists ACL)就是通过CSV实现导入的。

  1. 添加security/ir.model.access.csv文件
'data': [
	...
	'security/ir.model.access.csv',
],
  1. 添加图书访问权限
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 
acl_library_book_user,ACL for books,model_library_book,base.group_user,1,0,0,0

原理

你只需要将需要的数据文件写在manifest的data列表中即可。odoo会根据文件的后缀是哪种类型的文件。csv文件的文件名必须是model的名称。比如,ir.model.access.cs。
对于标量值,可以使用带引号的字符串(如果需要,因为字符串本身包含引号或逗号)或不带引号的字符串。
当使用CSV文件编写many2one字段时,Odoo首先尝试将列值解释为XML ID。如果没有点,Odoo将当前模块名添加为名称空间,并在ir.model.data中查找结果。如果失败,将调用模型的name_search函数,并将列的值作为参数,第一个返回的结果将获胜。如果同样失败,则认为该行无效,并引发Odoo错误。

重要提醒
从CSV文件读取的数据总是noupdate=False,而且没有什么方便的方法来解决这个问题。这意味着您的附加组件的后续更新将总是覆盖用户所做的可能更改。
如果你需要加载大量数据,而noupdate对你来说是个问题,那就从init钩子加载CSV文件。

更多

用CSV文件导入one2many和many2many字段是可能的,但有点棘手。通常,您最好分别创建记录,然后使用XML文件设置关系,或者使用第二个CSV文件设置关系。
如果您确实需要在同一个文件中创建相关记录,请对列进行排序
使所有标量字段都在左边,链接模型的字段在右边,列头由链接字段的名称和链接模型的字段组成,用冒号分隔:

"id","name","model_id:id","perm_read","perm_read", "group_ id:name"
"access_library_book_user","ACL for books","model_library_ book",1,
"my group"

这会创建一个名为my group的组;通过向右侧添加列,可以在组记录中写入更多字段。如果需要链接多个记录,请重复该行,并根据需要更改右边的列。由于Odoo用前一行的值填充空列,所以不需要复制所有数据——只需添加即可
为您想要填充的链接模型的字段保存空值的一行。对于x2m字段,只需列出要链接的记录的XML id。


插件的更新及数据迁移

我们的模块中最初设计的一些模型可能存在一定的缺陷,因此我们需要对模型进行改造。为了避免改造具有侵入性,Odoo支持模块进行版本控制,并在必要的时候执行数据迁移。

步骤

我们假设在模块的早期版本中,date_release字段是字符型,用于用户填写必要的日期信息。而现在我们觉得这个字段可能需要进行比较和聚合,因此我们需要将这个字段调整为Date类型。
Odoo在字段的类型变更场景下已经做得很好了。但是在此处,让我们自行实现字段的迁移工作。

  1. 修改版本信息
'version': '13.0.1.0.1',
  1. 在migrations/13.0.1.0.1/pre-Name.py文件中提供预迁移代码:
def migrate(cr, version):
    cr.execute('ALTER TABLE library_book RENAME COLUMN date_release TO date_release_char')
  1. 在migrations/13.0.1.0.1/post-Name.py中提供迁移后的代码:
from odoo import fields
from datetime import date
def migrate(cr, version):
    cr.execute('SELECT id, date_release_char FROM library_book')
    for record_id, old_date in cr.fetchall():
        new_date = None
        try:
            new_date = fields.Date.to_date(old_date)
        except ValueError:
            if len(old_date) == 4 and old_date.isdigit():
                new_date = date(int(old_date), 1, 1)
        if new_date:
            cr.execute('UPDATE library_book SET date_release=%s',(new_date,))

在Odoo的自动迁移中,date_release列将会被重命名为date_release_moved,并创建一个新的列,但由于不存在从字符字段到日期字段的自动转换。因此,新的date_release的值将为空。

原理

在数据迁移中,我们首先要变更模块的版本号,这是Odoo判断是否执行迁移的依据。在模块升级时,Odoo将会在ir_module_module表中更新模块的版本号。如果模块的版本号低于4位,那么Odoo会默认加上Odoo的主版本及次版本号作为版本号的前缀写入表中。
上文提到的两个数据迁移文件并不需要注册。当模块在更新的时候,Odoo会自动匹配模块的版本信息,若manifest中的版本号更高一些,Odoo将自动搜索migrations文件夹。
然后,在找到的文件夹中,Odoo搜索名称以pre-开头的Python文件,并执行名为migrate的函数。该函数共有两个参数,数据库游标作为第一个参数,当前安装的版本作为第二个参数。
在所有预迁移函数成功运行之后,Odoo加载模型和插件中声明的数据,这将会引起数据库的更改。比如上例中重命名date_release字段,Odoo将使用该名称创建一个正确类型的新列。
之后Odoo将会搜索post-Name文件并执行。在我们的例子中,我们需要确认字段的值是否符合要求,若不符合,我们需要将值置为NULL。除非有必要,否则不要去遍历整张表。

重要提醒
如果只是想重命名列,我们并不需要迁移脚本。在这种情况下,可以将目标字段的oldname属性设置为原始名称,Odoo将自动处理剩下的工作。

更多

在pro-和post-迁移中,我们使用了cursor游标。但是若在现阶段Odoo的环境,使用模型可能会导致一些意想不到的问题。因为在pre-migrate阶段模型还没有加载,而且在post-migrate阶段中,也仅仅是加载了自己模块而已。若我们并不依赖于其他的模块,那么可通过如下内容实现:

from odoo import api, SUPERUSER_ID
def migrate(cr, version):
	env = api.Environment(cr, SUPERUSER_ID, {})
	# env holds all currently loaded models

参考

在编写迁移脚本时,若经常会遇到重复的任务,例如如检查列或表是否存在,重命名列,或将一些旧值映射到新值。我们可以考虑使用第三方扩展模块 https://github.com/OCA/openupgradelib。

通过xml文件删除数据

通过xml文件调用方法

posted @ 2021-03-02 20:44  老韩头的开发日常  阅读(700)  评论(0编辑  收藏  举报