第五章 Odoo 12开发之导入、导出以及模块数据
大多数Odoo 模块的定义,如用户界面和安全规则,实际是存储在对应数据表中的数据记录。模块中的 XML 和 CSV 文件不是 Odoo 应用运行时使用,而是载入数据表的手段。正是因为这个原因,Odoo 模块的一个重要部分是在文件中放入数据以在插件安装时将其载入数据库。
模块可以包含初始数据和演示数据,可通过数据文件将它们加入模块。此外,了解 Odoo 数据的格式对于在项目实施上下文中导入导出业务数据也非常重要。
本文的主要内容有:
- 理解外部标识符的概念
- 导入导出数据文件
- 使用 CSV 文件
- 添加模块数据
- 使用 XML 数据文件
开发准备
本文要求读者可以运行Odoo 服务并已安装前面我们此前开发的图书应用。相关代码请见GitHub 仓库。你可能也同时安装了第四章 Odoo 12 开发之模块继承中创建的library_member模块,但本文并不要求使用该模型。
本文的更新后的代码请见GitHub 仓库。
理解外部标识符的概念
外部标识符,也称为XML ID,是用于唯一标识 Odoo 中特定记录的有可读性的字符串标识符。在Odoo 中加载数据时它们就很重要了,这样可以对已有数据记录进行修改或在其它数据记录中引用它。
首先我们将讨论外部标识符的工作原理以及如何对其进行检查。然后我们会学习如何使用网页客户端来查找指定数据记录的外部标识符,在创建插件模块或继承已有模块时需要经常用到。
外部标识符的工作原理
记录在数据库中的真实标识符是自动分配的序列号,在安装模块时没法预先知道将要分配的具体ID的。外部标识符让我们无需知道真实的数据库 ID便可以引用一条相关记录。XML ID 为数据库 ID 提供了一个方便的别名,藉于此我们可以在任何时刻引用某一指定记录。
Odoo 模块数据文件中使用XML ID来定义记录。其中一个原因是避免在升级模块时创建重复的记录,在升级时会再次将数据文件加载到数据库中。我们要检测已有记录来进行更新,而不是重复创建记录。另一个原因是使用XML ID来支持交叉数据:即需引用其它数据记录的数据记录。因为我们无法知道真实数据库 ID,使用XML ID来由 Odoo 框架来进行相应的转换。
Odoo 处理由外部标识符向所分配的真实数据库 ID 的转换。背后的机制相当简单:Odoo 维护一张外部标识符和对应数据库 ID 的映射表:ir.model.data model。
我们需启用开发者模式才能访问下文中的菜单。可通过在右上角头像左侧查看是否有调试图标,如果没有需在 Settings菜单页启用,具体方法可参照第一章 使用开发者模式快速入门 Odoo 12中的内容。
通过菜单访问Settings > Technical > Sequences & Identifiers > External Identifiers可查看已有映射。例如访问外部标识符列表并过滤出library_app模块,将可以看到该模块生成的外部标识符:
可以看到外部标识符有Complete ID标签。注意其组成部分为:模块名+.+标识符名,如library_app.action_library_book。
外部标识符仅需在 Odoo 模块内唯一,两个模块中使用相同标识符不会产生冲突。全局唯一标识符是由模块名和外部标识符共同组成的,在上图Complete ID项中可以看到。
在数据文件中使用外部标识符,我们可以选择完整的标识符或仅外部标识符部分。通常仅使用外部标识符会更简单,但使用完整标识符时我们可以引用其它模块中的数据记录。做引用时不要忘记在模块依赖中加入这些模块以确保在我们的记录之前加载这些记录。
小贴士:有时即便引用相同模块中的XML ID也需使用完整标识符
在上图列表最上方可以看到library_app.action_library_book完整标识符。这是我们在模块中创建的菜单操作,在相应的菜单项中引用。点击进入表单视图查看详情。图中可以看出library_app模块中的action_library_book外部标识符映射到ir.actions.act_window模型中的记录 ID,此处为85:
除了作为其它应用引用记录的一种方式外,外部标识符还可以避免重复导入带来的重复数据。一旦外部标识符已存在,则会在原有记录上更新,避免了重复数据的新建。
查找外部标识符
在为我们的模块写入数据记录时,经常需要查找已有外部标识符来作引用。一种方式是访问菜单Settings > Technical > Sequences & Identifiers > External Identifiers,前面已经演示过。另一种方法是使用开发者菜单。在第一章 使用开发者模式快速入门 Odoo 12中介绍了如何激开发者模式。
要查找一个数据记录的外部标识符,我们应打开对应的表单视图,在开发者菜单中选择View Metadata选项。此时会显示一个带有记录数据库 ID 和外部标识符(也称作XML ID)的对话框。比如要查看 demo 用户 ID,需通过 Settings > Users & Companies > Users 进入用户表单视图,然后点击开发者工具菜单中的View Metadata选项。此时可以看到XML ID是base.user_demo,数据库 ID 是6:
查看表单、列表、搜索或 action 视图中的外部标识符,都可以使用开发者菜单。下面我们通过Edit View选项来打开相应视图的详情表单。此时可以查看到External ID字段,其值即为外部标识符。例如在下图中,可以看到图书表单视图的External ID为library_app.view_form_book:
导入导出 CSV 数据文件
导出数据文件并查看文件结构的简易方式是使用内置的导出功能。通过生成 CSV 文件,我们可以了解手动导入系统所需的格式,或编辑该文件批量导入,甚至是使用它生成我们插件模块的演示数据。
下面我们一起来学习从 Odoo 用户界面导入和导出的基础知识。
导出数据
数据导出是表单视图中的标准功能。要使用该功能, 需要勾选左侧的复选框来选择需导出的行,然后在上方的 Action 菜单中点击 Export 选项。首先我们要在图书应用中添加一些带有出版商和作者的图书。下例中我使用此前添加的书籍。
我们还需要安装 Contacts 应用,这样可以看到 Partner 的列表视图,可从该处导出记录。注意其默认视图为带有名片的看板视图,需要先切换为列表视图:
可通过勾选列头的筛选框来选择所有匹配当前搜索条件的记录。
ℹ️Odoo 9中的修改
在 Odoo 更早的版本中,只有屏幕上显示(当页)的记录能被导出。Odoo 9做出了修改,勾选列头的复选框可导出当前过滤的所有匹配记录,而不仅仅是当前显示。这对导出屏幕上无法展示全的大量记录非常有用。
点击 Export 选项进入Export Data 对话表单,可选择导出方式。我们比较关注的是导出方式可以让我们通过手动或插件模块来导入该文件:
在对话表单最上方,有两个选项:
- What do you want do do?(老版本中为Export type),选择Import-Compatible Export选项,这样导出数据在以后导入时格式更友好。
- Export formats:可选择CSV或Excel,我们将选择 CSV 格式来更好理解原始导出格式,在很多表单应用中都能被读取。
下一步选取要导出的列,本例中简化操作,仅选择External ID和Name。如果我们点击Export To File按钮,就会下载带有导出数据的文件。最终的 CSV 内容类似:
补充:伸手党请注意这里及后续的 ID 字段都与导出的系统有关,不应直接使用
第一行中包含列名,导入时会使用它们自动匹配目录列。导出内容有两列:
- id:为每条记录分配的外部 ID,如果不存在,会在模块名处使用__export__ 作为前缀自动生成一条新ID。
- name: 联系人/Partner 名称
带有外部 ID 使我们可以编辑导出数据并重新导入来把修改更新到记录中。
小贴士:由于会自动生成记录 id,导出或导入功能可用于批量编辑 Odoo 数据:将数据导出至 CSV,使用表单软件批量编辑数据,再导入 Odoo。
导入数据
首先应确认开启了导入功能,默认是开启的。如果没有,进入Settings > General Settings,在 Users 版块下勾选Import & Export选项即可。启用该选项后,列表视图上方 Create 按钮旁就会显示一个 Import按钮。
注意:Import & Export 设置安装base_import模块,该模块用于提供这一功能。
下面我们尝试批量编辑Contact或Partner数据。使用电子表单或文本编辑器打开CSV并修改几个值。将 id 栏留空即可新增行。前文已经提到第一列 id 作为每行的唯一标识符,这让已有记录可以被更新,而不会因重新导入数据重复创建。我们在导出表中编辑任意字段在导入时对应记录就会被更新。
对于要加入 CSV 文件的新行,我们可以自己添加外部标识符或将 id 列留空。两种方式都会创建新的记录。作为示例,我们添加一行id 留空、name 为Phillip K. Dick,来在数据库中新建这一记录。在 CSV文件中进行保存,点击 Import(Create 按钮旁),然后点击 Load File 按钮选择磁盘中 CSV 的路径就出会出现如下导入助手:
点击右上角的Test Import按钮,检查数据正确性。由于导入的文件是在 Odoo 中导出文件基础上修改的,正常会有效并且各列会自动与数据库中对应字段匹配。因编辑所使用的软件各异,有可能需对分隔符和编码进行处理。现在可以点击 Import 按钮,修改和新建记录就会被载入到 Odoo 中。
CSV 数据文件中的关联记录
前面的示例非常简单,一旦我们开使用关联多张表的关联字段时,数据文件就会变得更为复杂。我们处理过图书中的 Partner 记录,下面就看一下如何在图书 CSV 文件中表示对这些 Partner 的引用。具体来说,有一个出版商(publisher_id字段)的many-to-one(或外键)关联,以及一个作者(author_ids字段)的many-to-many关联。
CSV 文件的表头行中关联列应在名称后添加一个/id。它将使用外部标识符来引用关联记录。本例中,我们将在publisher_id/id字段中加载图书出版商,使用关联 Partner 的外部 ID 作为其值。
注意:可使用/.id来进行替代来使用数据库中的真实 ID(自动分配的数字 id),但极少使用到。除非有特别原因,否则请使用外部 ID 而非数据库ID。同时要记住数据库 ID 针对具体的数据库,所以如果导入到非原始数据库中这种操作大多数情况下都会失败。
CSV 数据文件中也可导入many-to-many字段,这和添加带双引号并由逗号分隔的外部 ID 列表一样简单。例如,要载入图书作者,将需要一个author_ids/id列,并使用一个关联 Partner外部 ID 的逗号分隔列表作为其值:
One-to-many 字段通常是表头和行或父子关系,对于这类关系有特别的支持方式:对于同一条父记录可以有多个关联行。此处我们在 Partner 模型中有一个 one-to-many字段的例子:公司可带有多个联系人。如果从 Partner 模型中导出数据并包含Contacts/Name 字段,就可以看到要导入此类型数据的格式(Contacts 中选择Azure Interior:默认应为第一条,并执行前述的导出步骤):
id | name | child_ids/id | child_ids/name |
base.res_partner_12 | Azure Interior | base.res_partner_address_15 | Brandon Freeman |
base.res_partner_address_28 | Colleen Diaz | ||
base.res_partner_address_16 | Nicole Ford |
id和 name 列为父记录的,child_ids两列为子记录的。注意第一行记录以下父记录部分留空。上表中CSV 文件形式显示为:
可以看到id和name这两列第一行有值,后两行都为空。其中的父记录为联系人的公司信息。另两行的前缀都是child_ids/并且在三行中都有数据。这些是父公司的联系人信息。第一行包含公司和第一个联系人,其余行仅包含联系人这一子信息。
添加模块数据
模块使用数据文件来加载默认数据、演示数据、用户界面定义和其它需存入数据库的配置。可以选择使用 CSV 或 XML 文件。
ℹ️Odoo 12中的修改
Odoo 11及之前版本支持YAML格式文件,但在 Odoo 12移除了相关支持。相关使用示例可参考 Odoo 11官方模块l10n_be,更多YAML格式相关信息,可访问http://yaml.org/。
模块所使用的 CSV 和我们前述使用导入功能时用的文件是一样的。在模块中使用这些文件时,文件名须与要导入数据的模型名一致。例如,导入library.book模型的 CSV 数据文件名应为library.book.csv。CSV 数据文件经常用作导入ir.model.access模型来获取权限定义,通常放在security/子目录下并命名为ir.model.access.csv。
演示数据
Odoo插件模块可安装演示数据,推荐支持该安装。为模块提示使用示例和测试用的数据集会非常有用。模块的演示数据通过__manifest__.py文件中的 demo 属性来声明。和 data 属性一样,后接一个包含模块相对路径的文件名列表。我们应为library.book模块添加一些演示数据,一个简易方式是从安装了模块的开发数据库中导出数据。
按惯例数据文件放在data/子目录下,应以data/library.book.csv保存在library_app模块下。因这个数据为模块所有,应在导出的数据中将标识符的前缀__export__去除。
例如res.partner.csv文件可能长这样:
那么图书演示数据文件library.book.csv就应该是这样的:
注意文件中同一条数据因显示原因可能在不同行中,但实际是不能换行的。还应记得在__manifest__.py的 demo 属性中声明数据文件:
文件会以声明的顺序来加载,这个很重要,因为文件可能会因为未被安装而无法引用其记录。只要启用了安装演示数据,下次更新时,这些内容就会被导入。
ℹ️数据文件会在模块升级时重新导入,但演示文件则并非如此,它们仅在安装时导入。
当然 XML 文件也可用于加载或初始化数据,还可使用普通 CSV 文件所不具备的功能。
使用 XML 数据文件
CSV 文件是一种展示数据方便简洁的格式,但 XML 文件更为强大,可在加载过程中提供更多的控制。比如,其文件名无需与所导入到的模型名称一致。因为XML格式通过文件内的XML元素可以提供更丰富的信息、更多的内容。
在前面的文章中我们已经使用过XML数据文件。视图和菜单项这类用户界面组件实际上都是存储在系统模型中的记录。模块中的XML文件是将这些记录加载到实例数据库的方式。我们将在library_app模块中添加一个数据文件data/book_demo.xml来作为展示,文件内容如下:
老规矩,新的数据文件应在__manifest__.py中声明:
类似 CSV 文件,该文件也会将数据加载到图书模型中。
XML文件包含一个外层<odoo>元素,内部可包含多个<record>元素与对应 CSV 数据行。
ℹ️数据文件中的外层<odoo>元素在9.0中才引入用于替换此前的<openerp>标签。现在仍支持外层元素内的<data>标签,为可选项。事实上现在<odoo>和<data>是等价的,我们可以在数据文件中使用任意一个作为外层元素。
<record>元素有两个强制属性: model 和作为记录外部标识符的 id,每个字段使用一个<field>标签来进行写入。注意此处字段名内不可使用斜杠标记,如不可使用<field name=”publisher_id/id”>。应使用 ref 属性来引用外部标识符,一会儿就会讨论到关联 to-many 字段。
你可能注意到在外层元素中使用了noupdate=”1″属性。这防止了在模块升级时数据记录的载入,不至于在后续编辑中丢失数据。
noupdate 数据属性
升级模块时,会重新加载数据并重写模块记录。要谨记这可能意味着在升级模块时会重写任何对模块数据的手动更改。
小贴士:值得注意的是,手动对视图所做的自定义修改会在下一次模块升级时丢失。避免这一问题正确的方法是创建继承视图来引入要做的修改。
这种重写行为是默认的,但可以修改有些数据仅在安装时导入,后续模块更新时则予以忽略,这正是通过<odoo>或<data>元素中的noupdate=”1″来实现的。
这对于需初始化配置且预期需自定义的数据来说非常有用,因为这些手动修改在模块更新时是安全的。例如在记录访问规则中经常使用,可以适应具体的实施需求。
在同一 XML 文件中可以有多个<data>版块。可通过这个来分隔仅需导入一次的数据(noupdate=”1″)和需在每次更新时重新导入的数据(noupdate=”0″)。noupdate=”0″是默认值,所以可以省略不写。注意还必须要有一个外层 XML 元素,就这个例子而言,使用两个<data>标签,并在外层包裹一个<odoo>或<data>元素。
小贴士:noupdate属性在开发模块时可能会引起不适,因为会忽略后续修改。一个解决方案是,使用-i 参数重新安装模块而不是使用-u 参数进行更新。命令行中使用-i 参数重新安装会忽略数据记录中的noupdate标记。
noupdate标记存储在每条记录的外部标识符信息中。可通过 Technical 菜单中的External Identifiers表单手动编辑,勾选Non Updatable 复选框即可。
ℹ️Odoo 12中的修改
点击开发者菜单中的View Metadata时,现在在弹出的对话框中 XML ID 下面还会显示No Update的值。并且在该处可通过点击来修改该标记的值。
在 XML 中定义记录
在 XML 文件中,每个<record>元素有两个基本属性:id 和 model,并包含为对应列设置的值。 id 属性对应记录外部标识符,model 对应目标模型。<field>元素有几种分配值的方法,下面一起来看看。
直接为字段设置值
<field>元素的 name 属性标识要写入的字段。写入的值是元素内容:字段开、闭标签之间的文本。对于 date 和datetime,带有返回 date 或 datetime 对象表达式的 eval 属性可进行设置。返回的”YYYY-mm-dd”和”YYYY-mm-dd HH:MM:SS”字符串会进行转化。对于布尔字段,”0″ and “False”都会转换成 False,而任意非空值都会转换成 True。
小贴士:Odoo 10中的修改
Odoo 10中改进了从数据文件中读取布尔值 False的方式。在老版本中,包含”0″ and “False”在内的非空值都会转换成 True,直至 Odoo 9,布尔值仍需使用 eval 属性进行设置,如 eval=”False”。
通过表达式设置值
设置字段值更复杂的方式是通过 eval 属性,它会运行 Python 表达式并将结果分配给字段。表达式通过 Python 内置的以及一些其它可创建表达式标识符的上下文求值。
可使用如下 Python 模块来处理日期:time, datetime, timedelta和relativedelta。通过它们可以计算日期值,在演示和测试数据经常会用到,以让日期和模块安装日期较近。关于 Python 模块更多这类知识,请参考官方文档。
比如,把值设为前一天,可使用如下代码:
求值上下文还可使用ref()函数,用于将外部标识符转换为对应的数据库 ID。这可用于为关联字段设置值。比如,可以使用它为publisher_id设置值:
在 many-to-one 关联字段上设置值
对于many-to-one关联字段,要写入的是关联记录的数据库 ID。在 XML 文件中,我们一般会知道记录的XML ID,然后就需要把它转换成实际的数据库 ID。
一种方式是像前文那样使用带有 ref()函数的 eval 属性。更简单的替代方式是使用在元素中可用的ref 属性,使用它设置publisher_id many-to-one字段的值,我们可以这么写:
在 to-many 关联字段上设置值
对于one-to-many和many-to-many字段,设置的不是单个 ID,而是一组关联 ID。并且还进行几种操作-我们可能需要将当前的关联记录列表替换成另外一个,或为其添加几次记录,甚至是删除其中的一些记录。
要让to-many字段支持写操作,我们要在 eval 属性中使用一种特殊的语法。我们使用一个元组组成的列表来写入to-many字段。每个元组有三个元素,构成一个写入命令,根据第一个元素中的代码进行对应操作。要重写图书作者列表,要使用如下代码:
要往当前图书作者列表添加关联记录,需要添加如下代码:
上述的例子非常常见。这里仅使用了一个命令,但在外层列中可以串联多条命令。添加(4)和 替换(6)是最常用的命令。在进行添加(4)时,不需要使用最后一个元素,因此在以上代码中省略了。
完整的可用命令如下:
- (0, _ , {‘field’: value})新建一条记录并将其与之关联
- (1, id, {‘field’: value})更新已关联记录的值
- (2, id, _)移除关联并删除 id 关联的记录
- (3, id, _)移除关联但不删除 id 关联的记录。通常使用它来删除many-to-many字段的关联记录
- (4, id, _)关联已存在记录,仅适用于many-to-many字段
- (5, _, _)删除所有关联,但不删除关联记录
- (6, _, [ids])替换已关联记录列表为此处的列表
上述下划线_字符代表非关联值,通常填入 o 或 False。
小贴士:后面的非关联值可以放心地省略掉,如(4, id, _) 可使用(4, id)
常用模型的简写
如果回到第三章 Odoo 12 开发之创建第一个 Odoo 应用,我们在 XML 中还发现<record>之外的元素,如<act_window>和<menuitem>。这些是常用模型的简写方式,是比常用的<record>更为简练的符号。它们用于向 base 模型加载数据、组成用户界面,在第十章 Odoo 12开发之后台视图 – 设计用户界面会作更详细的探讨。
为便于查看,以下是可用的简写元素以及加载数据的对应模型:
- <act_window>是窗口操作模型ir.actions.act_window
- <menuitem>是菜单项模型ir.ui.menu
- <report>是报表操作模型ir.actions.report.xml
- <template>是存储在ir.ui.view模型中的 QWeb 模板
ℹ️Odoo 11中的修改
<url>标签已被淘汰并删除。此前的版本中它用作为 URL 操作模型ir.actions.act_url加载记录。
应当注意在用于修改已有记录时,简写元素会覆盖所有字段。这与仅写入所提供字段的<record>基础元素不同。因此在需修改用户界面元素指定字段时,应使用<record>元素。
XML 文件中的其它操作
截至目前我们了解了如何使用 XML 文件添加和更新数据。但也可以通过 XML 文件删除数据以及执行指定模型方法。对更复杂的数据场景会非常有用。
删除记录
我们可以使用<delete>元素删除数据记录,使用 ID 或搜索域来定位要删除的记录。例如,使用搜索域查找记录并删除:
如果知道要删除记录的具体 ID,可使用 id 属性。上例还可以写成这样:
调用模型方法
XML 文件还可以通过<function>元素在加载过程中执行任意方法,可用于设定演示和测试数据。比如 Odoo 捆绑的 Notes 应用,使用它来设定演示数据:
这会调用res.users模型中的_init_data_user_note_stages方法,不传任何参数。由参数列表eval传递,此处为空列表。
总结
本文中我们学习了如何在文件文中展示数据。可用作手动向 Odoo 导入数据,或放在插件模块中作为默认或演示数据。通过学习我们可以通过网页界面导出并导入 CSV 数据文件了,以及通过外部 ID 来检测并更新数据库中已有的记录。也可用作批量编辑数据,只需编辑导出的 CSV 文件再重新导入即可。
我们还详细学习了 XML 数据文件的结构以及所提供功能。不仅可以为字段设置值,还可以执行删除记录和调用方法一类的操作。
下一篇文章中,我们将集中学习如何使用记录来与模型中所含数据协作。这些工具可供我们实现应用的业务逻辑和规则。
☞☞☞第六章 Odoo 12开发之模型 – 结构化应用数据
学霸专区
- XML ID 与外部 ID 的区别是什么?
- 插件模块中可使用什么类型的数据文件?
- 以下 XML 片段有什么问题?
- 一个插件模块中的数据文件是否可以覆盖另一个模块中创建的记录?
- 插件模块升级时,是否所有数据记录都会被重写为模块默认值?
扩展阅读
Odoo 官方文档中提供了有关数据文件的更多资料。