Odoo 14开发者指南第三章 创建Odoo插件模块
现在我们已经有了开发环境并且知道如何管理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$ cd ~/odoo-dev
$ mkdir local-addons为新模块选择一个技术名称并使用该名称作为模块名创建目录。本例中,我们使用my_library:
1
$ mkdir local-addons/my_library
ℹ️模块的技术名称必须是有效的Python标识符,需以字母开头,仅包含字母、数字和下划线。建议在模块名称中只使用小写字母。
通过添加__init__.py文件来让Python模块可导入:
1
$ touch local-addons/my_library/__init__.py
为Odoo添加一个最小化的模块声明来让其成为一个插件模块。在my_library文件夹下创建__manifest__.py文件并添加如下行:
1
{'name': 'My Library'}
启动Odoo实例,将我们的插件目录添加到插件路径中:
1
$ odoo/odoo-bin --addons-path=odoo/addon/,local-addons/
ℹ️如果在该Odoo命令中添加了 –save 选项,插件路径会被保存到配置文件中。下次你启动服务时,如未提供插件路径选项的话,就会使用它。
让这个新模块在Odoo中可用;使用管理员登录Odoo,启动开发者模式,然后在Apps顶级菜单中选择Update Apps List。现在Odoo应该就识别到了我们的模块了:
图3.2 – 更新应用列表对话框选择顶部的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声明文件的模块来进行操作。你可能要按前一节的步骤来提供一个可以操作的模块。
如何操作…
我们为这个插件模块添加声明文件和一个图标:
以最相关的键名创建一个声明文件,编辑模块的__manifest__.py文件至如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
'name': "My library",
'summary': "轻松管理图书",
'description': """
Manage Library
==============
Description related to library.
""",
'author': "Alan Hou",
'website': "https://alanhou.org",
'category': 'Uncategorized',
'version': '14.0.1',
'depends': ['base'],
'data': ['views/views.xml'],
'demo': ['demo.xml'],
}为该模块添加一个图标,选择一张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描述会覆盖掉声明文件中所定义的描述。
还有一些常用的其它键名:
- licence:默认值为LGPL-3。这一标识符用于模块对外使用的证书。其它可用的证书有AGPL-3、Odoo自有证书v1.0(多用于付费应用)以及其它OSI核准的证书。
- application:如果为True,模块作为应用列出。通常这用于一个功能区的中心模块。
- auto_install:若为True,表示这是一个胶水模块,在它所有的依赖模块安装后会被自动安装。
- installable:若为True(默认值),表示该模块可以进行安装。
- external_dependencies:有些Odoo模块内部使用了Python/bin库。如果你的模块使用了这些库,需要在这里进行添加。如果所列模块在主机上没有安装的话则会停止该模块的安装。
- {pre_init, post_init, uninstall}_hook:这是在安装/卸载时调用的Python函数钩子。更多详细示例,请见第八章 高级服务端开发技巧。
还有一些应用商店列表使用到的特殊键:
- price: 这个键用于对插件模块设置价格。值应为整型值。如未设置价格,则表示应用为免费的。
- currency: 这是价格对应的币种。可用的值有USD 和 EUR。该键的默认值为EUR。
- live_test_url: 如果想为应用提供一个在线测试URL,可以使用该键来在应用商店中显示在线预览按钮。
- iap: 如果模块用于提供IAP服务的话,需设置IAP开发者密钥。
- images: 给出图片路径。该图片用作Odoo应用商店的封面图。
组织插件模块文件结构
一个插件模块包含代码及其它资源文件,如XML文件和图像。大多数这些文件,我们都可以自由选择其在插件目录中的存放位置。
但是,Odoo使用了一些模块结构的惯例,建议遵循这些惯例。
准备工作
我们应当有一个插件模块目录,其中仅包含__init__.py和__manifest__.py文件。本节中,我们假定这个目录为local-addons/my_library。
如何操作…
执行如下步骤来为该插件模块创建基本骨架结构:
为代码文件创建目录:
1
2
3
4
5
6
7
8
9
10
11
12
13
14$ cd local-addons/my_library
$ mkdir models
$ touch models/__init__.py
$ mkdir controllers
$ touch controllers/__init__.py
$ mkdir views
$ touch views/views.xml
$ mkdir security
$ mkdir wizard
$ touch wizard/__init__.py
$ mkdir report
$ mkdir data
$ mkdir demo
$ mkdir i18n编辑模块的顶级 init.py文件,这样子目录中的代码会被加载到:
1
2
3from . import models
from . import controllers
from . import wizard这会给我们一个包含最常用入手目录结构,类似下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19my_library
├── __init__.py
├── __manifest__.py
├── controllers
│ └── __init__.py
├── data
├── demo
├── i18n
├── models
│ └── __init__.py
├── report
├── security
├── static
│ └── description
│ └── icon.png
├── views
│ └── views.xml
└── wizard
└── __init__.py
运行原理…
为大家普及一下背景,Odoo插件模块可以有三种类型文件:
- Python代码由 init.py加载,通过该文件导入.py文件及代码子目录。子目录中包含的Python代码,再由其内部的__init__.py导入。
- 模块声明文件__manifest__.py中data和demo键名所声明供加载的数据文件,通常为用户界面、fixture数据和演示数据中会使用到的XML和CSV文件。还可使用YAML文件,可以包含一些模块加载时运行的过程指令,例如,通过程序生成或更新记录而非在XML文件中加入数据。
- 网页资源,如JavaScript代码和库文件、CSS、SASS及QWeb/HTML模板文件。这些文件用于在 UI 元素中构建UI组成部分及管理用户动作。它们通过继承主模板的XML文件来进行声明,用于为网页客户端或网站页面添加这些资源。
插件文件在如下目录中进行组织:
- models/ 包含后端代码文件,用于创建模型及其业务逻辑。推荐每个模型一个文件并使用与模型相同的名称,例如 library.book模型的对应文件为library_book.py。这些在第四章 应用模型中会进行深入讲解。
- views/ 包含用于用户界面的XML文件,包含动作、表单、列表等等。类似于模型,建议每个模型一个文件。网站模板的文件名通常以_template后缀进行结尾。后端视图在第九章 后端视图中讲解,网站视图在第十四章 CMS网站开发中进行讲解。
- data/ 包含模块初始数据的其它数据文件。数据文件在第六章 管理模块数据中进行讲解。
- demo/ 包含带演示数据的数据文件,对于测试、培训或模块评测都非常有用。
- Odoo会在i18n/ 中查找.pot及.po翻译文件。更多详情参见第十一章 国际化。这些文件无需在声明文件中声明。
- security/ 包含定义访问控制列表的数据文件,通常是一个ir.model.access.csv文件,也可以是一个XML文件,用于定义权限组及行级权限的记录规则。参见第十章 权限安全来获取更多内容。
- controllers/ 包含网站控制器的代码文件,用于为模块提供各种功能。网页控制器在第十三章 Web服务端开发中进行讲解。
- static/ 用于放置所有的网页资源。和其它目录不同,该目录名不只是一种惯例。这一目录中的文件无需用户登录即可对外提供访问。该目录多包含JavaScript、样式表、图像等文件。它们无需在模块声明文件中进行声明,但需要在网页模板中引用。这会在第十四章 CMS网站开发中进行讨论。
- wizard/ 包含所有与向导有关的文件。在Odoo中,向导用于存放中间数据。在第八章 高级服务端开发技巧中将会进一步学习到向导的
- report/ : Odoo提供了为诸如销售订单和发票生成PDF文档的功能。该目录存放与PDF报告相关的所有文件。我们在第十二章 自动化、工作流、Email和打印件中会进一步学习 PDF报告的相关知识。
📝在向模块添加新文件时,不要忘记在__manifest__.py(数据文件)或__init__.py(代码文件)文件中进行声明,否则会忽略这些文件而不进行加载。
添加模型
模型中定义的数据结构会用于我们的业务应用。这一节向读者展示如何为模块添加基本模型。
在我们的示例中,希望对图书馆的书籍进行管理。那么,我们需要创建一个代表书籍的模型。每本书包含一个书名及一名或多名作者。
准备工作
我们需要有一个模块来进行操作。如果你按照本章第一节创建和安装一个新的插件模块操作的话,则会有一个名为my_library的空模块。我们将使用它来做进一步讲解。
如何操作…
要添加新模型,我们需要添加一个Python文件来描述它,然后升级插件模块(如未安装则执行安装)。以下使用的路径为我们的插件模块目录中的相对路径(例如,~/odoo-dev/local-addons/my_library/):
为模块添加一个 Python 文件models/library_book.py,代码如下:
1
2
3
4
5
6
7
8
9
10
11from odoo import models, fields
class LibraryBook(models.Model):
_name = 'library.book'
name = fields.Char('Title', required=True)
date_release = fields.Date('Release Date')
author_ids = fields.Many2many(
'res.partner',
string='Authors'
)在模块中添加一个Python初始化文件models/init.py来加载代码文件,内容如下:
1
from . import library_book
编辑模块的Python初始化文件来在模块中加载models/目录:
1
from . import models
从命令行或用户界面中的Apps菜单中升级该Odoo模块。如果你在升级模块时仔细查看服务日志的话,会看到如下行:
1
odoo.modules.registry: module my_library: creating or updating database table
然后,就可以在我们的Odoo实例中使用这一新的library.book模型了。有两种方式来查看我们的模型是否在数据库中进行了添加。
第一种方式是可以在用户界面中进行查看。激活开发者工具,然后打开菜单Settings>Technical>Database Structure>Models。然后在那里搜索library.book模型。
第二种方式是查看PostgreSQL数据库中的表数据。可以在数据库中搜索library_book数据表。在下面的代码示例中,我们使用了test-14.0作为数据库。但是你可以修改为你自己的数据库名:
1 | $ psql 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:
创建一个XML文件views/library_book.xml来添加描述用户界面的数据记录:
1
2
3
4<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Data records go here -->
</odoo>在插件模块声明文件__manifest__.py中添加新的数据文件,通过views/library_book.xml来进行添加:
1
2
3
4
5
6{
'name': "My Library",
'summary': "Manage books easily",
'depends': ['base'],
'data': ['views/library_book.xml'],
}在library_book.xml文件中添加打开视图的动作:
1
2
3
4
5<record id='library_book_action' model='ir.actions.act_window'>
<field name="name">Library Books</field>
<field name="res_model">library.book</field>
<field name="view_mode">tree,form</field>
</record>在library_book.xml文件中添加菜单项,让其对用户可见:
1
2<menuitem name="My Library" id="library_base_menu" />
<menuitem name="Books" id="library_book_menu" parent="library_base_menu" action="library_book_action"/>在library_book.xml文件中添加一个自定义表单视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<record id="library_book_view_form" model="ir.ui.view">
<field name="name">Library Book Form</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<form>
<group>
<group>
<field name="name"/>
<field name="author_ids" widget="many2many_tags"/>
</group>
<group>
<field name="date_release"/>
</group>
</group>
</form>
</field>
</record>在library_book.xml文件中添加自定义树状(列表)视图:
1
2
3
4
5
6
7
8
9
10<record id="library_book_view_tree" model="ir.ui.view">
<field name="name">Library Book List</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<tree>
<field name="name"/>
<field name="date_release"/>
</tree>
</field>
</record>在library_book.xml文件中添加自定义搜索选项:
1
2
3
4
5
6
7
8
9
10
11<record id="library_book_view_search" model="ir.ui.view">
<field name="name">Library Book Search</field>
<field name="model">library.book</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="author_ids"/>
<filter string="No Authors" name="without_author" domain="[('author_ids','=',False)]"/>
</search>
</field>
</record>
在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属性用于在用户打开动作时向用户所显示的标题。这些都是基本属性。窗口动作还支持其它属性,让我们对视图渲染方式拥有更多的控制,比如显示什么视图,为可用的记录添加过滤器或设置默认值。这些会在第九章 后端视图中进行讨论。
通常,数据记录使用
类似地,菜单项存储在ir.ui.menu模型中,我们可以使用
以下是菜单项的主要属性:
- name:这是要显示的菜单项文本。
- action:这是要执行的动作的标识符。我们使用前一步中所创建的窗口动作的ID。
- sequence:这用于设置同级菜单项的显示顺序。
- parent:这是父级菜单项的标识符。本例中的菜单项没有父级,即它会显示在顶级菜单中。
- web_icon: 这一属性用于显示菜单的图标。图标仅在Odoo企业版中才显示。
至此,我们还没有在模型中定义任何视图。但是,如果这时你升级了该模型,Odoo会自动地实时为你创建视图。可是,我们一定会想要控制视图的展示内容,因此,在接下来的两步中创建了一个表单视图和一个树状视图。
这两个视图以 ir.ui.view 模型上的记录进行定义。我们所使用的属性如下:
name:这是标识视图的标题。在Odoo的源码中,你会发现这里重复使用了XML ID,但是你完全可以添加一个更易于阅读的名称作为标题。
小贴士: 如果省略了name字段,Odoo会使用模型名称及视图类型来生成一个。对于新模型的标准视图这完全没有问题。在继承视图时建议使用一个更具说明性的名称,因为这会让你在Odoo用户界面上查找具体视图时更为方便。
model:这是目标模型的内部标识符,和_name属性中的所定义的名称一致。
arch:这是视图架构,实际定义结构的地方。这里不同类型的视图会有不同。
表单视图在顶级