Alan Hou的个人博客

Odoo 14开发者指南第三章 创建Odoo插件模块

全书完整目录请见:Odoo 14开发者指南(Cookbook)第四版

现在我们已经有了开发环境并且知道如何管理Odoo服务实例和数据库,可以学习如何创建Odoo插件模块了。

本章我们的主要目标是理解一个插件模块的结构是什么样的以及对其进行补充的典型增量工作流。本章中各节所讨论的各种组件会在后续章节中进行扩充讲解。

本章中,我们将讲解如下内容:

技术准备

本章中,我们将预设你已安装了Odoo并且按照第一章 安装Odoo开发环境中各节进行了操作。读者还应熟悉第二章 管理Odoo服务端实例中所描述的发现和安装额外插件模块的知识。

本章中所使用的代码可以从如下GitHub仓库中进行下载:https://github.com/alanhou/odoo14-cookbook/tree/main/Chapter03。

Odoo的插件模块是什么?

除框架代码以外,Odoo的所有基础代码都以模块的形式组合在一起。这些模块可以随时从数据库中安装或卸载。这些模块有两大目的。要么是添加新应用/业务逻辑,要么是修改已有应用。简言之,Odoo中的一切都始于模块也终于模块。

Odoo由不同规模的公司所使用,每个公司都有不同的业务流和要求。处理这一问题,Odoo将应用的功能拆分到了不同的模块中。这些模块可按需在数据库中进行加载。基本上,用户可以在任何时间点启用/禁用这些功能。因此,同一软件可以按不同的要求进行调整。查看下面Odoo模块的截屏;该列中第一个模块是主应用,其它的模块为该应用添加功能而设计。让模块列表按应用分类进行分组,进入Apps菜单并按Category分组:

图3.1 – 按category对应用分组

如果计划在Odoo中开发新应用,应为不同功能设置边界。这有助于将你的应用切分为不同的插件模块。既然你已经知道了Odoo中插件模块的用途,我们可以开始构建自己的插件模块了。

创建和安装一个新的插件模块

这一节中,我们将新建一个模块,让其在我们的Odoo实例中可用并进行安装。

准备工作

我们需要准备好一个Odoo实例来开始我们的开发。

如果按照第一章 安装Odoo开发环境从源码轻松安装Odoo一节进行操作的话,Odoo应该在~/odoo-dev/odoo下。为方便讲解,我们假定Odoo安装在该路径下,但是你可以使用其它你自己喜欢的路径。

我们还需要一个位置来安装自己的Odoo模块。就本节而言,我们将使用odoo的同级目录:~/odoo-dev/local-addons。

如何操作…

本章的示例中,我们将创建一个小型的插件模块来管理图书馆中的一系列图书。

如下步骤将创建并安装一个新的插件模块:

  1. 进入到工作目录即你要操作并放置新建的自定义模块的插件目录中:
  2. 为新模块选择一个技术名称并使用该名称作为模块名创建目录。本例中,我们使用my_library:

    ℹ️模块的技术名称必须是有效的Python标识符,需以字母开头,仅包含字母、数字和下划线。建议在模块名称中只使用小写字母。

  3. 通过添加__init__.py文件来让Python模块可导入:
  4. 为Odoo添加一个最小化的模块声明来让其成为一个插件模块。在my_library文件夹下创建__manifest__.py文件并添加如下行:
  5. 启动Odoo实例,将我们的插件目录添加到插件路径中:

    ℹ️如果在该Odoo命令中添加了 –save 选项,插件路径会被保存到配置文件中。下次你启动服务时,如未提供插件路径选项的话,就会使用它。

  6. 让这个新模块在Odoo中可用;使用管理员登录Odoo,启动开发者模式,然后在Apps顶级菜单中选择Update Apps List。现在Odoo应该就识别到了我们的模块了:


    图3.2 – 更新应用列表对话框
  7. 选择顶部的Apps菜单,在右上方的搜索栏中,删除默认的应用过滤器并搜索my_library,点击它的Install按钮,就完成了安装。

运行原理…

Odoo模块是一个包含代码文件及其它资源的目录。所使用的目录名为模块的技术名称。模块声明中的 name 键对应其标题。

__manifest__.py是模块的声明文件。它包含一个带有模块元数据的Python字典,有分类、版本、所依赖的模块以及一系列它要加载的数据文件。在这一节中,我们使用了最简化的声明文件,但在真实的模块中,我们需要使用其它的重要键名。这将在下一节完成插件模块的声明是进行讨论。

模块目录必须是Python可导入的,因此即便内容为空也要添加一个__init__.py 文件。要载入模块,Odoo服务要导入它。这会导致__init__.py文件中的代码被执行,它作为运行该模块Python代码的一个入口。因此,它通常会包含加载模块Python文件及子模块的一些导入语句。

已识别模块可通过命令行使用–init或-i选项直接安装。例如,如果希望安装crm和website应用,可使用-i crm,website。这一模块列表在提供插件路径新建数据库时获取到的模块进行初始化设置。可通过Update Module List菜单更新已有数据库。

完成插件模块的声明

声明文件对于Odoo模块非常重要。它包含插件模块重要的元数据并声明了应加载的数据文件。

准备工作

我们应当有一个包含__manifest__.py声明文件的模块来进行操作。你可能要按前一节的步骤来提供一个可以操作的模块。

如何操作…

我们为这个插件模块添加声明文件和一个图标:

  1. 以最相关的键名创建一个声明文件,编辑模块的__manifest__.py文件至如下内容:
  2. 为该模块添加一个图标,选择一张PNG图片并将其拷贝至static/description/icon.png

译者注:

1、此时如直接进行更新操作会出现报错,因为 data 和 demo 相关文件尚未添加

2、description 下面的内容无需进行缩进,否则显示样式会存在问题

运行原理…

声明文件中的内容是一个常规的Python字典,包含键和值。我们的示例声明中包含了最为相关的一些键名:

  • name:这是该模块的标题。
  • summary:这是一个单行描述的副标题。
  • description:这是一个以普通文本或重构文本(RST)格式编写的长描述。通常放在三个引号中,Python中使用三个引号来界定多行文本。RST的快速入门指南请见http://docutils.sourceforge.net/docs/user/rst/quickstart.html。
  • author:是一个作者姓名的字符串。如果有多个作者的话,一般使用逗号来进行分隔,但注意它仍应是一个字符串,而非Python列表。
  • website:这个 URL 可供人们访问来了解模块或作者的更多信息。
  • category:用于按照兴趣领域组织模块。标准的分类名称列表请见https://github.com/odoo/odoo/blob/14.0/odoo/addons/base/data/ir_module_category_data.xml。但也可以定义这些名称以外的新分类名称。
  • version:这是该模块的版本号。可供Odoo应用商店用于检测已安装模块的新版本。如果版本号没有以Odoo目标版本号(如14.0)开头,会进行自动添加。但是,如果你显式的声明Odoo目标版本号信息量会更充足,比如用14.0.1.0.0或14.0.1.0来替代1.0.0或1.0。
  • depends:这是该模块所直接依赖的模块技术名称列表。如果你的模块不依赖于任何其它插件模块,那么应至少添加一个base模块。别忘记包含这个模块所引用的XML ID、视图或模块的定义模型。那样可确保它们以正确的顺序进行加载,避免难以调试的错误。
  • data:这是在模块安装或升级时需加载数据文件的相对路径列表。这些路径相对于模块的根目录。通常,这些是XML和CSV文件,但也可以使用YAML格式的数据文件。这些内容会在第六章 管理模块数据中深入讨论。
  • demo:这是加载演示数据文件的相对路径列表。仅在创建数据库时启用了Demo Data标记时才会进行加载。

模块图标使用的图像是位于static/description/icon.png的一个PNG文件。

小贴士:Odoo的大版本一般会有较大的变化,因此如不进行转化或迁移操作的话,一个大版本中构建的模块不大可能与下一个版本进行兼容。因此,在安装模块前确定Odoo的目标版本号就非常的重要了。

扩展内容

也可以使用一个单独的描述文件来替代模块声明中的长描述。自8.0版起,可通过后缀名为 .txt、.rst或.md(Markdown)的README文件来进行替换。此外,可在模块中包含一个description/index.html文件。

这一HTML描述会覆盖掉声明文件中所定义的描述。

还有一些常用的其它键名:

还有一些应用商店列表使用到的特殊键:

组织插件模块文件结构

一个插件模块包含代码及其它资源文件,如XML文件和图像。大多数这些文件,我们都可以自由选择其在插件目录中的存放位置。

但是,Odoo使用了一些模块结构的惯例,建议遵循这些惯例。

准备工作

我们应当有一个插件模块目录,其中仅包含__init__.py和__manifest__.py文件。本节中,我们假定这个目录为local-addons/my_library。

如何操作…

执行如下步骤来为该插件模块创建基本骨架结构:

  1. 为代码文件创建目录:
  2. 编辑模块的顶级 __init__.py文件,这样子目录中的代码会被加载到:

    这会给我们一个包含最常用入手目录结构,类似下面这样:

运行原理…

为大家普及一下背景,Odoo插件模块可以有三种类型文件:

插件文件在如下目录中进行组织:

📝在向模块添加新文件时,不要忘记在__manifest__.py(数据文件)或__init__.py(代码文件)文件中进行声明,否则会忽略这些文件而不进行加载。

添加模型

模型中定义的数据结构会用于我们的业务应用。这一节向读者展示如何为模块添加基本模型。

在我们的示例中,希望对图书馆的书籍进行管理。那么,我们需要创建一个代表书籍的模型。每本书包含一个书名及一名或多名作者。

准备工作

我们需要有一个模块来进行操作。如果你按照本章第一节创建和安装一个新的插件模块操作的话,则会有一个名为my_library的空模块。我们将使用它来做进一步讲解。

如何操作…

要添加新模型,我们需要添加一个Python文件来描述它,然后升级插件模块(如未安装则执行安装)。以下使用的路径为我们的插件模块目录中的相对路径(例如,~/odoo-dev/local-addons/my_library/):

  1. 为模块添加一个 Python 文件models/library_book.py,代码如下:
  2. 在模块中添加一个Python初始化文件models/__init__.py来加载代码文件,内容如下:
  3. 编辑模块的Python初始化文件来在模块中加载models/目录:
  4. 从命令行或用户界面中的Apps菜单中升级该Odoo模块。如果你在升级模块时仔细查看服务日志的话,会看到如下行:

然后,就可以在我们的Odoo实例中使用这一新的library.book模型了。有两种方式来查看我们的模型是否在数据库中进行了添加。

第一种方式是可以在用户界面中进行查看。激活开发者工具,然后打开菜单Settings>Technical>Database Structure>Models。然后在那里搜索library.book模型。

第二种方式是查看PostgreSQL数据库中的表数据。可以在数据库中搜索library_book数据表。在下面的代码示例中,我们使用了test-14.0作为数据库。但是你可以修改为你自己的数据库名:

运行原理…

第1步中我们在新建的模块中创建一个了一个Python文件。

Odoo框架有着其自己的ORM框架。这一ORM框架对PostgreSQL数据库进行了抽象。通过继承Odoo Python类Model,我们可以创建自己的模型(数据表)。在定义新模型时,也将其加入到了中央模型仓库中。这让其它模块在之后对其进行修改变得更为容易。

模型有一些以下划线为前缀的通用属性。最重要的一个是_name,它提供了一个唯一内部标识符来在整个Odoo实例中进行使用。ORM会根据这个属性来生成数据表。本节中,我们使用了_name = ‘library.book’。基于这一属性,该ORM框架会创建一个名为library_book的新数据表。注意ORM在创建新表的名称时会将_name属性中的. 替换为 _ 。

模型字段以类属性的方式进行定义。我们首先以Char类型定义了name字段。模型中有了这一字段会非常方便,因为默认在其它模型中引用该模型时,它会用作记录描述。

我们还使用了一个关联字段的示例author_ids。这在图书和其作者之前建立了一个多对多的关联。一本书可以有多个作者,而每个作者也可以编著多本书。

有关模型有很多可以讨论的地方,我们将在第四章 应用模型中进行深度讲解。

接下来,我们必须让我们的模块可以识别到这一新的Python文件。通过__init__.py文件来实现。因为将代码放在了models/子目录内,我们需要前述的__init__.py 文件来导入该目录,其中又包含另一个__init__.py文件,在其中导入那里的每一个代码文件(本例中只有一个文件)。

对Odoo模型的修改通过升级该模块来实现。Odoo服务会处理由模型类到数据库结构变化的转换。

虽然此处没有提供示例,业务逻辑也可以在这些Python文件中进行添加,或通过对模型类添加新的方法,或继承已有方法,如create() 或 write()。这在第五章 基本服务端开发中进行讨论。

添加菜单项和视图

在有了我们的数据结构所需的模型之后,我们希望用户可以通过用户界面与它们进行交互。本节基于上一节的图书模型进行创建,添加菜单项来显示一个包含列表和表单视图的用户界面。

准备工作

需要有实现library.book模型的插件模块,这在上一节中已经提供到。使用的路径为相对插件模块所处位置的相对路径(如~/odoo-dev/local-addons/my_library/)。

如何实现…

要添加视图,我们将添加一个XML文件,其中包含对于模块的定义。因其是一个新模型,我们还应添加一个菜单选项来让用户可以访问它。

注意下述步骤的顺序也是很重要的,因为其中一些会引用到在之前步骤中定义的ID:

  1. 创建一个XML文件views/library_book.xml来添加描述用户界面的数据记录:
  2. 在插件模块声明文件__manifest__.py中添加新的数据文件,通过views/library_book.xml来进行添加:
  3. 在library_book.xml文件中添加打开视图的动作:
  4. 在library_book.xml文件中添加菜单项,让其对用户可见:
  5. 在library_book.xml文件中添加一个自定义表单视图:
  6. 在library_book.xml文件中添加自定义树状(列表)视图:
  7. 在library_book.xml文件中添加自定义搜索选项:

在Odoo中新增模型时,用户默认没有访问权限。必须要为新模型定义访问权限才能进行访问。本例中我们还没有定义访问权限,因而用户无法访问到这个新模型。没有权限菜单和视图也无法展示。所幸的是有一个快捷方式。通过切换为超级用户模式,可以无需访问权限查看这些菜单。

注:从版本号12开始, admin 用户必须要获取相应的访问权限才能在用户界面中访问我们的模型。

以超级用户访问 Odoo

通过将admin转换为超级用户superuser,你就可以不受访问权限的限制,因此无需授予访问权限即可访问所有菜单和视图。要将admin用户转换为superuser,先激活开发者模式。然后在开发者工具选项中,点击Become Superuser选项。

以下截图供您参考:

图3.3 – 启动超级用户模式

在成为超级用户之后,你的菜单会显示一个条状背景,如下图所示:

图3.4 – 超级用户模式

这时如果你试着升级模块,则会看到一个新的菜单选项(可能会需要刷新浏览器)。点击Books菜单会打开图书模型的列表视图,如下图所示:

图3.5 – 访问图书的菜单

运行原理…

在底层中,用户界面由存储在特定模型中的记录所定义。前两步中创建了一个空的XML文件用于定义待加载的记录,然后将它们添加到模型的数据文件列表中来供安装。

数据文件可以放在该模型目录中的任意位置,但按照习惯用户界面的数据文件在views/子目录中定义。通常,这些文件的名称是基于模型的名称的。本例中,我们为library.book模型创建用户界面,因此我们创建了views/library_book.xml文件。

下一步是定义在客户端用户界面主区域中显示的窗口动作。该动作有一个由 res_model定义的目标模型,name属性用于在用户打开动作时向用户所显示的标题。这些都是基本属性。窗口动作还支持其它属性,让我们对视图渲染方式拥有更多的控制,比如显示什么视图,为可用的记录添加过滤器或设置默认值。这些会在第九章 后端视图中进行讨论。

通常,数据记录使用<record>标签定义,在本例中我们为ir.actions.act_window模型创建了一条记录。这会创建窗口动作。

类似地,菜单项存储在ir.ui.menu模型中,我们可以使用<record>标签来进行创建。但是在Odoo中有一个名为<menuitem>的快捷标签,因此我们在本例中进行了使用。

以下是菜单项的主要属性:

至此,我们还没有在模型中定义任何视图。但是,如果这时你升级了该模型,Odoo会自动地实时为你创建视图。可是,我们一定会想要控制视图的展示内容,因此,在接下来的两步中创建了一个表单视图和一个树状视图。

这两个视图以 ir.ui.view 模型上的记录进行定义。我们所使用的属性如下:

表单视图在顶级<form>元素中定义,它的画布是一个两列网格。在表单内,<group>元素用于在垂直方向上编排字段。两个组会生成包含<field>元素所定义字段的两列。字段根据数据类型使用其默认组件,但可以借助widget属性来指定具体使用的组件。

树状视图要简单些,它们以包含用于各列中显示的<field>元素的顶级<tree>元素定义。

最后,我们添加了搜索视图来在右上角的搜索框中扩展搜索选项。在顶级的<search>标签中,可以包含<field>和<filter>元素。字段元素是在搜索视图中可用于搜索的其它字段。过滤器元素为可通过点击激活的预置过滤条件。这些话题会在第九章 后端视图中进行讨论。

添加访问权限

在添加新的数据模型时,需要定义谁可以创建、读取、更新和删除记录。在创建一个全新的应用时,这可能还包含新用户组的定义。因此,如果用户没有访问权限的话,Odoo应不会显示该菜单和视图。在上一节中,我们将admin用户转换为超级用户来访问这个菜单。在学完本节后,你就可以直接以admin用户来访问我们图书模块的菜单和视图了。

这一节使用上一节中定义的图书模型,会定义一个用户安全组来控制谁能够访问或修改图书记录。

准备工作

我们会需要上一节中实现了library.book模型的插件模块,因为在这一节我们会为其添加权限规则。使用的是插件模块所在位置(例如~/odoo-dev/local-addons/my_library/)的相对路径。

如何操作…

我们要在本节中添加的安全规则如下:

要进行实现,我们需要执行如下步骤:

  1. 创建一个名为security/groups.xml的文件并添加如下内容:
  2. 添加一个名为security/ir.model.access.csv的文件并加入如下内容:
  3. 在__manifest__.py中添加这两个数据文件:

在实例中更新插件后即可使用新定义的权限规则了。

运行原理…

我们提供了两个新数据文件并添加到了插件模块的声明文件中,这样安装或升级该模块时会在数据库中加载它们:

小贴士:声明文件中data版块内文件的顺序非常重要,创建权限组的文件必须要在列出安全权限的文件之前加载,因为安全权限的定义依赖于组的存在。因为视图可具体到权限组,我们推荐将组的定义文件放在该列表中会更为保险。

其它内容

使用脚手架命令来创建模块

在新建 Odoo 模块时,需要设置一些范本代码。为帮助大家快速新建模块,Odoo提供了scaffold(脚手架)命令。

本节展示如何使用scaffold命令新建一个模块,它会创建一个目录和文件结构来供使用。

准备工作

我们将在一个自定义模块目录中新建一个插件模块,因此需要已安装Odoo并且给自定义模块一个目录。假定Odoo安装的位置为~/odoo-dev/odoo,我们的自定义模块会放置在~/odoo-dev/local-addons目录中。

如何操作…

我们将使用scaffold命令来创建范本代码。按照如下步骤来使用scaffold命令新建一个模块:

  1. 切换工作目录到想要放置模块的地方。它可以是你所选择的任意目录,但需要在一个插件路径中才可进行使用。按照我们在前面小节中所选择的目录,应该是这样的:
  2. 为这个新模块选择一个技术名称,并使用scaffold命令来创建它。本例中,我们选择的是my_module:
  3. 编辑默认模块声明文件__manifest__.py并修改相应的值。肯定想要改的有name键中的模块标题。

以下是生成的插件模块的结构:

现在应编辑生成的各文件来适应你对新模块的需求。

运行原理…

scaffold命令创建基于一个模板的新模块结构。

默认,这个新模块在当前工作目录中进行创建,但我们也可以指定一个目录来创建该模块,将其作为一个额外的参数进行传递。

参考如下示例:

此处使用了default模板,但也可以为网站主题编写的theme模板。可以使用-t选项来选择一个指定的模板。我们也可以使用包含模板的一个目录来作为路径。

这表示我们可以通过scaffold命令来使用自己的模板。内置的模板位于./odoo/cli/templates这个Odoo子目录中。用类似下面的命令来使用我们自己的模板:

默认,在/odoo/cli/templates目录中有两个模板。一个是default模板,另一个是theme模板。但可以创建我们自己的模板,像以上命令那样用-t来使用它。

退出移动版