编写可导入模块¶
尽管作为开发人员,我们更希望拥有 Python 的全部功能来编写模块,但有时这是不可能的;通常在不允许部署自定义 Python 代码(如 Odoo.com 平台)的托管解决方案上。
然而,Odoo 的灵活性意味着允许开箱即用的定制。虽然 Studio 可以做很多事情,但也可以在 XML Data Files 中定义模型、字段和逻辑。这使得开发、维护和部署这些定制变得更加容易。
在本教程中,我们将学习如何在 XML 数据文件中定义模型、字段和逻辑并将它们捆绑到模块中。这些有时称为“可导入模块”或“数据模块”。我们还将看到这种模块开发方法的局限性。
问题陈述¶
就像在 服务器框架101 教程中一样,我们将研究房地产概念。
我们的目标是创建一个新的应用程序,以与 服务器框架101 教程类似(尽管更简单)的方式管理房地产。我们将在 XML 数据文件而不是 Python 文件中定义模型、字段和逻辑。
在本教程结束时,我们将能够在我们的应用程序中实现以下目标:
管理待售房地产
在网站上发布这些属性
从网站在线接受报价
出售房产时向买家开具发票
模块结构¶
与任何开发项目一样,清晰的结构可以更轻松地管理和维护代码。
与同时使用 Python 和 XML 文件的标准 Odoo 模块不同,数据模块仅使用 XML 文件。因此,预计您的工作树将如下所示:
estate
├── actions
│ └── *.xml
├── models
│ └── *.xml
├── security
│ └── ir.model.access.csv
│ └── estate_security.xml
├── views
│ └── *.xml
├── __init__.py
└── __manifest__.py
您将拥有的唯一 Python 文件是 __init__.py 和 __manifest__.py 文件。 __manifest__.py 文件将与任何 Odoo 模块相同,但也会将其模型导入 data 列表中。
请记住按依赖关系顺序列出 __manifest__.py 的 data 部分中的文件,通常从模型文件开始。
__init__.py 文件是空的,但如果您想以经典方式部署模块(通过将其添加到插件路径中),Odoo 需要该文件来识别该模块。对于将要“导入”的模块来说,这并不是绝对必要的,但保留它是一个很好的做法。
部署模块¶
要部署该模块,您需要创建该模块的 zip 文件并将其上传到您的 Odoo 实例。确保您的实例上安装了模块 base_import_module,然后转到 并上传 zip 文件。您必须位于 developer mode 才能看到 Import Module 菜单项。
如果修改模块,则需要创建一个新的 zip 文件并再次上传,这将重新加载模块中的所有数据。但请注意,某些操作是不可能的,例如更改之前创建的字段的类型。以前版本的模块创建的数据(如删除的字段)不会自动删除。一般来说,处理此问题的最简单方法是从新数据库开始或在上传新版本之前卸载模块。
上传模块时,向导将接受两个选项:
Force init:如果您的模块已经安装并且您再次上传;选中此选项将强制更新 XML 文件中标记为noupdate="1"的所有数据。Import demo data:不言自明
还可以使用 odoo-bin 命令行工具和 deploy 命令来部署模块:
$ odoo-bin deploy <path_to_your_module> https://<your_odoo_instance> --login <your_login> --password <your_password>
此命令还接受 --force 选项,该选项相当于向导中的 Force init 选项。
请注意,用于部署模块的用户必须具有 Administration/Settings 访问权限。
Exercise
创建以下文件夹和文件:
/home/$USER/src/tutorials/estate/__init__.py/home/$USER/src/tutorials/estate/__manifest__.py
__manifest__.py文件应该只定义模块的名称和依赖项。目前唯一必要的框架模块是“base`(and ``base_import_module`” - 尽管严格来说您的模块并不“依赖”它,但您需要它才能导入您的模块)。创建模块的 zip 文件并将其上传到您的 Odoo 实例。
模型和基本领域¶
正如您可以想象的那样,在 XML 文件中定义模型和字段并不像在 Python 中那么简单。
由于数据文件是按顺序读取的,因此您必须按正确的顺序定义元素。例如,您必须先定义一个模型,然后才能在该模型上定义字段,并且必须在将字段添加到视图之前定义字段。
此外,XML 比 Python 冗长得多。
让我们首先定义一个简单的模型来表示模块 models 目录中的房地产属性。
Odoo 模型作为 ir.model 记录存储在数据库中。与任何其他记录一样,它们可以在 XML 文件中定义:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="model_real_estate_property" model="ir.model">
<field name="name">Real Estate Property</field>
<field name="model">x_estate.property</field>
</record>
</odoo>
请注意,数据文件中定义的所有模型和字段都必须以 x_ 为前缀;这是强制性的,用于将它们与 Python 文件中定义的模型和字段区分开来。
与 Python 中定义的经典模型一样,Odoo 会自动向模型添加几个字段:
id(Id) 模型记录的唯一标识符。create_date(Datetime) 记录的创建日期。create_uid(Many2one) 创建记录的用户。write_date(Datetime) 记录的上次修改日期。write_uid(Many2one) 最后修改记录的用户。
我们还可以向新模型添加多个字段。让我们添加一些简单的字段,例如名称(字符串)、售价(浮点数)、描述(作为 html)和邮政编码(作为字符)。
与模型一样,字段只是 ir.model.fields 模型的记录,可以在数据文件中这样定义:
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ...model definition from before... -->
<record id="field_real_estate_property_name" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_name</field>
<field name="field_description">Name</field>
<field name="ttype">char</field>
<field name="required">True</field>
</record>
<record id="field_real_estate_property_selling_price" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_selling_price</field>
<field name="field_description">Selling Price</field>
<field name="ttype">float</field>
<field name="required">True</field>
</record>
<record id="field_real_estate_property_description" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_description</field>
<field name="field_description">Description</field>
<field name="ttype">html</field>
</record>
<record id="field_real_estate_property_postcode" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_postcode</field>
<field name="field_description">Postcode</field>
<field name="ttype">char</field>
</record>
</odoo>
您可以为新字段设置各种属性。对于基本领域,这些包括:
name:字段的技术名称(必须以x_开头)field_description:字段的标签help:该字段的帮助文本,显示在界面中ttype:字段的类型(例如char、integer、float、html等)required:该字段是否必填(默认:False)readonly:该字段是否只读(默认:False)index:字段是否已索引(默认值:False)copied:复制记录时是否复制字段(默认值:True对于非关系非计算字段,False对于关系和计算字段)translate:字段是否可翻译(默认值:False)
属性还可用于控制 HTML 清理以及其他更高级的功能;有关完整列表,请参阅 菜单中可用的数据库中的 ir.model.fields 模型,或参阅 base 模块中的 ir.model.fields 模型定义。
Exercise
在表中添加以下基本字段:
场地 |
类型 |
必需的 |
|---|---|---|
x_日期_可用性 |
日期 |
|
x_预期_价格 |
漂浮 |
真的 |
x_卧室 |
整数 |
|
x_生活区 |
整数 |
|
x_立面 |
整数 |
|
x_车库 |
布尔值 |
|
x_花园 |
布尔值 |
|
x_花园_区域 |
整数 |
|
x_花园方向 |
选择 |
还可以设置 x_garden_orientation field must have 4 possible values: ‘North’, ‘South’, ‘East’ and ‘West’. The selection list must be created by first creating the ir.model.fields record for the field itself, then creating the ir.model.fields.selection records. These records take three fields: field_id, name (the name in the UI) and value (the value in the database). A sequence 字段,它控制选择在 UI 中显示的顺序(首先显示较低的序列值)。
默认值¶
在 Python 中,可以通过创建以下记录,使用所有属性的“default` argument in the field declaration. In data modules, default values are set by creating an ir.default record for each field. For example, it is possible to set the default value of the x_selling_price field to ``100000`”在字段上设置默认值:
<odoo>
<!-- ...model definition from before... -->
<record id="default_real_estate_property_selling_price" model="ir.default">
<field name="field_id" ref="estate.field_real_estate_property_selling_price" />
<field name="json_value">100000</field>
</record>
</odoo>
有关更多详细信息,请参阅 菜单中可用的数据库中的 ir.default 模型,或参阅 base 模块中的 ir.default 模型定义。
警告
这些默认值是静态的,但可以由公司和/或用户使用“user_id` and company_id fields of the ir.default record. This means that having a dynamic default value of “today” for the ``x_date_availability`”字段进行设置,例如,这是不可能的。
安全¶
数据模块中的安全性与 Python 模块中的安全性完全相同,可以在 第 4 章:安全性 - 简介 中找到。
有关详细信息,请参阅该教程。
Exercise
在适当的文件夹中创建
ir.model.access.csv文件并在__manifest__.py文件中定义它。向组“
base.group_user”授予读取、写入、创建和取消链接权限。
小技巧
日志中的警告消息为您提供了大部分解决方案;-)
意见¶
视图是允许用户与数据交互的 UI 组件。它们在 XML 文件中定义,可以在模块的 views 目录中找到。
由于视图和操作已在 第 5 章:最后,一些可以使用的 UI 和 第6章:基本观点 中定义,因此我们在此不再赘述。
Exercise
向 estate 模块添加基本 UI。
向 estate 模块添加基本 UI,以允许用户查看、创建、编辑和删除房地产属性。
为模型“
x_estate.property”创建一个操作。为模型“
x_estate.property”创建树视图。为模型“
x_estate.property”创建一个表单视图。将视图添加到操作中。
将菜单项添加到主菜单以允许用户访问该操作。
关系¶
像 Odoo 这样的关系系统的真正威力在于能够将记录链接在一起。在普通的 Python 模块中,可以在模型上定义新字段,以通过一行代码将其链接到其他模型。在数据模块中,这仍然是可能的,但需要更多的跑腿工作,因为我们不能使用与 Python 中相同的语法。
与 第 7 章:模型之间的关系 一样,我们将向 estate 模块添加一些关系。我们将添加链接到:
购买该房产的客户
出售房产的房地产经纪人
房产类型:独立屋、公寓、顶层公寓、城堡……
描述该房产的标签列表:舒适、翻新……
收到的报价清单
多对一¶
多对一是到另一个对象的简单链接。例如,为了定义到“res.partner”的链接,我们可以在模型中定义一个新字段:
<odoo>
<!-- ...model definition from before... -->
<record id="field_real_estate_property_partner_id" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_partner_id</field>
<field name="field_description">Customer</field>
<field name="ttype">many2one</field>
<field name="relation">res.partner</field>
</record>
</odoo>
对于多对一字段,可以设置几个属性来详细说明关系:
relation:要链接到的模型的名称(必需)ondelete:删除记录时执行的操作(默认:set null)domain:应用于关系的域过滤器
Exercise
使用以下字段创建一个新模型“
x_estate.property.type”:场地
类型
必需的
name查尔
真的
为“
x_estate.property.type”模型添加操作、列表视图和菜单项。将访问权限添加到“
x_estate.property.type”模型以授予用户访问权限。在“
x_estate.property”模型上创建以下字段:场地
类型
必需的
x_property_type_idMany2one (
x_estate.property.type)真的
`x_partner_id`(买方)
Many2one (
res.partner)`x_user_id`(销售人员)
Many2one (
res.users)在“
x_estate.property”模型的表单视图中包含新字段。
多对多¶
多对多是与对象列表的关系。在我们的示例中,我们将定义与新的“x_estate.property.tag”模型的多对多关系。该标签代表该房产的特征,例如:翻新、舒适等。
一个属性可以有多个标签,一个标签可以分配给多个属性——这是典型的多对多关系。
多对多字段的定义方式与多对一字段相同,但 ttype 设置为 many2many。 relation 属性还设置为要链接到的模型的名称。可以设置其他属性来控制关系:
relation_table:用于关系的表的名称column1和column2:用于关系的列的名称
这些属性是可选的,通常仅当两个模型之间存在多个多对多字段时才应指定,以避免冲突;在大多数情况下,Odoo ORM 将能够确定要使用的正确关系表和列。
Exercise
使用以下字段创建一个新模型“
x_estate.property.tag”:场地
类型
必需的
name查尔
真的
为“
x_estate.property.tag”模型添加操作、列表视图和菜单项。将访问权限添加到“
x_estate.property.tag”模型以允许用户访问。在“
x_estate.property”模型上创建以下字段:场地
类型
x_property_tag_ids多对多 (
x_estate.property.tag)将新字段包含在“
x_estate.property”模型的表单视图中。
一对多¶
一对多是与对象列表的关系。在我们的示例中,我们将定义与新的“x_estate.property.offer”模型的一对多关系。此报价代表客户购买房产的报价。
一对多字段的定义方式与多对一字段相同,但 ttype 设置为 one2many。 relation 属性还设置为要链接到的模型的名称。必须设置另一个属性来控制关系:
relation_field:相关模型上包含对父模型的引用的字段名称(多对一字段)。这用于将两个模型链接在一起。
Exercise
使用以下字段创建一个新模型“
x_estate.property.offer”:场地
类型
必需的
价值观
x_price漂浮
真的
x_status选择
接受、拒绝
x_partner_idMany2one (
res.partner)真的
x_property_idMany2one (
x_estate.property)真的
将访问权限添加到“
x_estate.property.offer”模型以允许用户访问。- 创建一个树视图和一个包含价格、partner_id 和状态字段的表单视图。无需创建操作或菜单。
添加字段“
x_offer_ids`to your ``x_estate.property`”模型及其表单视图。
计算字段¶
计算字段是 Odoo 中的核心概念,用于定义基于其他字段计算的字段。这对于从其他字段派生的字段非常有用,例如子记录的总和(将销售订单中所有商品的价格相加)。
参考:与此主题相关的文档可以在 计算字段 中找到。
数据模块可以定义任何类型的计算字段,但与 Python 模块相比非常有限。事实上,由于数据模块旨在部署在不允许运行任意代码的系统上,因此允许的Python代码非常有限。
注解
所有为数据模块编写的Python代码都在沙盒环境中执行,这限制了可以执行的操作。例如,您无法导入库,无法访问任何操作系统文件,甚至无法打印到控制台。提供了一些实用程序,但这取决于所使用的沙盒环境的类型。
就计算方法而言,沙箱非常有限,仅提供最少的实用程序来允许执行代码。除了 Python 内置函数之外,您还可以访问 datetime、dateutil 和 time 模块(例如,帮助进行日期计算)。
另请注意,沙箱中禁用了“点分配”,因此您无法写入 property.x_total_area = 1 in the compute method. You have to use dictionary access: property['x_total_area'] = 1. Dot notation for field access works normally: property.x_garden_area will return the value of the x_garden_area 字段。
我们之前在``x_estate.property`` model: living_area and ``garden_area``上定义了两个“区域”字段。要在模型上定义返回两个区域之和的计算字段,我们可以将以下代码添加到数据模块中:
<odoo>
<!-- ...model definition from before... -->
<record id="field_real_estate_property_total_area" model="ir.model.fields">
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="name">x_total_area</field>
<field name="field_description">Total Area</field>
<field name="ttype">float</field>
<field name="depends">x_living_area,x_garden_area</field>
<field name="compute"><![CDATA[
for property in self:
property['x_total_area'] = property.x_living_area + property.x_garden_area
]]>
</field>
</record>
</odoo>
注解
在服务器操作中,您将迭代 records 变量,而对于计算字段,您将迭代 self 变量,该变量包含计算该字段的记录集。
depends attribute is used to define the fields that the computed field depends on and the compute 属性用于定义执行计算字段的代码(使用 Python 代码)。
与 Python 模块不同,计算字段是默认存储的。如果您希望不存储计算字段(例如,出于性能原因或避免数据库膨胀),可以设置 store attribute to False. The same applies to readonly: if you want your computed field to not be editable, you need to set the readonly attribute to True。
CDATA 部分用于向 XML 解析器指定内容是字符串而不是 XML;这可以防止解析器尝试将 Python 代码解释为 XML,或者在模块安装时将代码插入数据库时添加额外的空间等。
Exercise
将计算字段添加到
x_estate.propertymodel that returns the sum of thex_living_areaandx_garden_area字段,如上所示。将该字段包含在“
x_estate.property”模型的表单视图中。
注解
与 Python 模块不同,无法为计算字段定义逆方法或搜索方法。
代码和业务逻辑¶
服务器操作¶
在 Python 模块中,您可以自由地在模型上定义任何方法。一种常见的使用模式是向模型添加所谓的“操作”方法,然后将这些方法绑定到 UI 中的按钮(例如,确认报价、发布发票等)。
在数据模块中,您可以通过定义绑定到模型的 Server Actions 来实现相同的效果。服务器操作表示在服务器上动态运行的逻辑片段。这些操作可以直接通过 菜单在数据库中手动配置,并且可以是不同的类型;在我们的例子中,我们将使用``code``类型,它允许我们在沙盒环境中运行任何Python代码。
该环境包含几个实用程序来帮助您与 Odoo 数据库交互:
env:记录的环境model:记录的模型useranduid:当前用户及其 IDdatetime,dateutil,timezoneandtime:帮助进行日期/时间计算的库float_compare:一个实用函数,用于比较两个给定精度的浮点值b64encodeandb64decode:用于以 Base64 编码和解码值的实用函数ORM reference 中的“
Command`: a utility class to help build complex expressions and commands (see the `Command”类)
此外,您还可以通过“record` and ``records`”变量访问执行操作的记录集(从表单视图执行操作时通常是单个记录,从列表视图执行操作时通常是多个记录)。
注解
如果您的操作需要向客户端返回操作(例如将用户重定向到另一个视图),您可以将其分配给``action`` variable inside your server action’s code. The code sandbox will inspect the variables defined in your code after its execution and will automatically return it if it detects the presence of an ``action``变量。
如果安装了 website 模块,则 request 对象将在代码沙箱中可用,您可以将 response 对象分配给 response 变量,以类似的方式向客户端返回响应。 网站控制者 部分对此进行了更详细的探讨。
例如,我们可以在 x_estate.property model that sets the x_status field of all its offers to Refused 上定义一个操作:
<record id="action_x_estate_property_refuse_all_offers" model="ir.actions.server">
<field name="name">Refuse all offers</field>
<field name="model_id" ref="estate.model_real_estate_property"/>
<field name="state">code</field>
<field name="code"><![CDATA[
for property in records:
property.x_offer_ids.write({'x_status': 'refused'})
]]></field>
</record>
要将此操作作为按钮包含在“x_estate.property”模型的表单视图中,我们可以在表单视图的标题中添加以下 button 节点:
<!-- form view definition from your code... -->
<header>
<button name="estate.action_x_estate_property_refuse_all_offers" type="action" string="Refuse all offers"/>
</header>
还可以在齿轮图标 () 中添加一个条目来运行此操作(例如,避免向已经拥挤的视图添加按钮)。为此,您可以将服务器操作“绑定”到模型和特定类型的视图:
<record id="action_x_estate_property_refuse_all_offers" model="ir.actions.server">
<field name="name">Refuse all offers</field>
<field name="model_id" ref="estate.model_real_estate_property"/>
<field name="state">code</field>
<field name="binding_model_id" ref="estate.model_real_estate_property"/>
<field name="binding_view_types">tree,form</field>
<field name="code"><![CDATA[
for property in records:
property.x_offer_ids.write({'x_status': 'refused'})
]]></field>
</record>
这将使操作在 x_estate.property 模型的齿轮图标 ()、列表(当通过复选框选择一个或多个记录时)和表单视图中可用。
Exercise
将服务器操作添加到“
x_estate.property.offer`model that sets thex_statusfield of an offer toAcceptedand updates the selling price and buyer of the property to which the offer is attached accordingly. This action should also mark all the other offers on the same property as ``Refused`”。在优惠的嵌入式列表视图中包含一个按钮,允许执行此操作
重写 Python 模型¶
通过 UI 元素¶
与 Python 模块不同,不可能干净地重写 Python 模型的方法。
但是,(在某些情况下)可以替换调用这些方法的 UI 元素,并在服务器操作中拦截对这些方法的调用。
一个典型的例子是与 Odoo 的“Sales”应用程序集成。假设您的房地产模块与销售应用程序集成,以便在销售特定产品时(例如,用于管理房产销售的报价),您希望在模块中自动创建新的房产记录。
为了实现这一目标,您需要:
创建一个调用按钮原始方法的服务器操作,并在该方法调用之前或之后添加自定义逻辑
将视图中的按钮替换为调用服务器操作的自定义按钮
<record id="view_sale_order_form" model="ir.ui.view">
<field name="name">sale.order.form.inherit.estate</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//button[@name='action_confirm'][@type='object']" position="attributes">
<attribute name="type">action</attribute>
<attribute name="name">estate.action_x_estate_property_create_from_sale_order</attribute>
</xpath>
<!-- since the button is present twice in the original view, we need to replace it twice -->
<xpath expr="//button[@name='action_confirm'][@type='object']" position="attributes">
<attribute name="type">action</attribute>
<attribute name="name">estate.action_x_estate_property_create_from_sale_order</attribute>
</xpath>
</field>
</record>
<record id="action_x_estate_property_create_from_sale_order" model="ir.actions.server">
<field name="name">Confirm and create property from sale order</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code"><![CDATA[
for order in records:
order.action_confirm()
property_type = env['x_estate.property.type'].sudo().search([('x_name', '=', 'Other')], limit=1)
property = env['x_estate.property'].sudo().create({
'x_name': order.name,
'x_expected_price': 0,
'x_selling_price': 0,
'x_sale_order_id': order.id,
'x_property_type_id': property_type.id,
})
]]></field>
</record>
通过自动化规则¶
自动化规则是一种根据特定触发器(例如状态更改、添加标签等)自动对数据库中的记录执行操作的方法。它们可用于将行为与记录的生命周期事件联系起来,例如在接受报价时发送电子邮件。
使用自动化规则来扩展标准行为可能比基于 UI 的方法更强大,因为如果生命周期事件以其他方式触发而不是通过按钮(例如,通过 Webhook 或直接调用方法;例如,当通过门户或电子商务确认报价时),它也会运行。然而,它们的正确设置有点挑剔,因为需要通过设置要监视的特定字段等来确保自动化仅在适当的时刻运行。
文档:与此主题相关的更完整的文档可以在 自动化规则 中找到。
注解
自动化规则不是“base` module; they come with the base_automation module; so if you define automation rules in your data module, you need to make sure that ``base_automation`”的一部分,而是模块依赖项的一部分。
安装后,自动化规则通过 菜单进行管理。
自动化规则对于将数据模块绑定到现有标准 Odoo 模块特别有用。由于数据模块无法覆盖方法,因此将自动化与标准模型的生命周期更改联系起来是扩展标准模块的常见方法。
如果我们要使用自动化规则重写上一节的示例,则需要进行一些更改:
服务器操作不应再调用按钮的原始方法(相反,原始方法将触发将触发自动化规则的更改)
不需要视图扩展
我们需要定义一个自动化规则来触发服务器对适当事件的操作
<record id="action_x_estate_property_create_from_sale_order" model="ir.actions.server">
<field name="name">Create property from sale order</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code"><![CDATA[
for order in records:
property_type = env['x_estate.property.type'].sudo().search([('x_name', '=', 'Other')], limit=1)
property = env['x_estate.property'].sudo().create({
'x_name': order.name,
'x_expected_price': 0,
'x_selling_price': 0,
'x_sale_order_id': order.id,
'x_property_type_id': property_type.id,
})
]]></field>
</record>
<record id="automation_rule_x_estate_property_create_from_sale_order" model="base.automation">
<field name="name">Create property from sale order</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="trigger">on_state_set</field>
<field name="trg_selection_field_id" ref="sale.selection__sale_order__state__sale"/>
<field name="trigger_field_ids" eval="[(4, ref('sale.field_sale_order__state'))]"/>
<field name="action_server_ids" eval="[(4, ref('estate.action_x_estate_property_create_from_sale_order'))]"/>
</record>
请注意,标准 Odoo 模型、字段、选择值等的 XML IDs 可以通过导航到技术菜单中的相应记录并使用``View Metadata`` menu entry of the debug menu. XML IDs for models are simply the model name with dots replaced by underscores and prefixed by model_ (e.g., sale.model_sale_order is sale.order as defined in the sale module); XML IDs for fields are the model name with dots replaced by underscores and prefixed by field_, the model’s name and the field name (e.g., sale.field_sale_order__name is the XML ID for the name field of the sale.order model which is defined in the `sale`模块)。
网站控制者¶
Odoo 中的 HTTP 控制器通常定义在模块的 controllers 目录中。在数据模块中,如果安装了网站模块,则可以定义充当控制器的服务器操作。
安装网站模块后,服务器操作可以标记为 Available on the website 并给出路径(完整路径始终以 /website/action 为前缀,以避免 URL 冲突);全局 request 对象在代码服务器操作的本地范围内可用。
request 对象提供了几种访问请求正文的方法:
request.get_http_params():从查询字符串和正文中存在的表单中提取键值对(application/x-www-form-urlencoded和multipart/form-data)。request.get_json_data():从请求正文中提取 JSON 数据。
由于不可能从服务器操作中返回值,因此要定义要返回的响应,可以将一个类似响应的对象分配给 response 变量,该变量将自动返回到网站。
下面是一个简单网站控制器的示例,当调用 URL /website/action/estate 时,该控制器将返回属性列表:
<record id="server_action_estate_list" model="ir.actions.server">
<field name="name">Estate List Controller</field>
<field name="model_id" ref="estate.model_real_estate_property" />
<field name="website_published">True</field>
<field name="website_path">estate</field>
<field name="state">code</field>
<field name="code"><![CDATA[
html = '<html><body><h1>Properties</h1><ul>'
for property in request.env['x_estate.property'].search([]):
html += f'<li>{property.x_name}</li>'
html += '</ul></body></html>'
response = request.make_response(html)
]]></field>
</record>
request 对象中提供了几个有用的方法来促进响应对象的生成:
request.render(template, qcontext=None, lazy=True, **kw)使用其 xmlid 渲染 QWeb 模板;额外的关键字参数被转发到werkzeug.Response对象(例如设置 cookies、标头等)request.redirect(location, code=303, local=True)重定向到不同的 URL;local参数用于指定重定向是否应相对于网站(默认值:True)。request.not_found()返回werkzeug.HTTPException异常,向网站发出 404 错误信号。request.make_response(data, headers=None, cookies=None, status=200)手动创建werkzeug.Response对象;status参数是要返回的 HTTP 状态代码(默认值:200)。request.make_json_response(data, headers=None, cookies=None, status=200)手动创建 JSON 响应;数据将使用json.dumps实用程序进行 json 序列化;这对于通过 API 调用建立服务器到服务器的通信很有用。
有关实现细节或其他(不太常见)方法,请参阅 odoo.http 模块中 Request 对象的实现。
请注意,安全问题留给了开发人员(通常通过安全规则或使用 sudo 访问记录)。
注解
服务器操作的 model_id 字段中使用的模型必须可供公共用户访问,以便执行此服务器操作的写入操作;否则服务器操作将返回 403 错误。避免授予访问权限的一种方法是将服务器操作链接到公共用户已经可以访问的模型,一个典型的(如果奇怪)示例是将服务器操作链接到 ir.filters 模型。
Exercise
将 JSON API 添加到您的模块,以便外部服务可以检索待售房产列表。
向模型添加新的
x_api_published字段以控制属性是否在 API 上发布添加访问权限记录,允许公共用户读写模型
通过为具有不可能域的写入操作添加记录规则(例如
[('id', '=', False)])来防止公共用户进行任何写入添加记录规则,以便公共用户可以读取标记为
x_api_published的属性添加服务器操作以在调用 URL
/website/action/estate时返回 JSON 格式的属性列表
一些 JavaScript¶
虽然可导入模块不能包含 Python 文件,但 JavaScript 文件不存在此类限制。将 JavaScript 文件添加到可导入模块与将它们添加到标准 Odoo 模块完全相同。
这意味着可导入模块可以包含新的字段组件甚至全新的视图。
作为示例,让我们向 Estate 模块添加一个简单的“游览”。游览是 Odoo 中的一种标准机制,用于通过引导用户完成应用程序来引导用户。
通过在 static/src/js/tour.js 中添加此文件,可以添加一个非常简单的单步游览:
import { registry } from "@web/core/registry";
registry.category("web_tour.tours").add('estate_tour', {
url: "/web",
steps: () => [{
trigger: '.o_app[data-menu-xmlid="estate.menu_root"]',
content: 'Start selling your properties from this app!',
}],
});
然后,您需要将该文件包含在清单文件中适当的包中:
{
"name": "Real Estate",
# [...]
"assets": {
"web.assets_backend": [
"estate/static/src/js/tour.js",
],
},
}
您还需要在数据文件夹中的新estate_tour.xml 文件中添加一条xml 记录,以便显示您的游览:
<record id="estate_tour" model="web_tour.tour">
<field name="name">estate_tour</field>
<field name="sequence">2</field>
<field name="rainbow_man_message">Welcome! Happy exploring.</field>
</record>
注解
与普通 Python 模块不同,可导入模块不支持 glob 扩展;因此,您需要专门列出要包含在模块中的每个文件。