第2章:创建画廊视图¶
让我们看看如何完全从头开始创建一个新视图。从某种意义上说,这并不是很难,但是没有真正好的资源来说明如何做到这一点。请注意,大多数情况应该通过定制现有视图或使用客户端操作来解决。
在本练习中,我们假设要创建一个 gallery
视图,这是一种允许我们用图像字段表示一组记录的视图。
这个问题当然可以通过看板视图来解决,但这意味着我们不能在同一个操作中同时使用普通看板视图和画廊视图。
让我们创建一个图库视图。每个图库视图将由其 arch 中的 image_field
属性定义:
<gallery image_field="some_field"/>
为了完成本章的任务,您需要安装awesome_gallery插件。该插件包含必要的服务器文件以添加新视图。
目标

本章每个练习的解决方案都托管在 官方 Odoo 教程仓库 上。
1. 创建一个 Hello World 视图¶
第一步是创建一个带有简单组件的JavaScript实现。
在
static/src
中创建gallery_view.js
、gallery_controller.js
和gallery_controller.xml
文件。在
gallery_controller.js
中实现一个简单的 hello world 组件。在
gallery_view.js
中,导入控制器,创建一个视图对象,并将其注册到视图注册表中,名称为gallery
。Example
以下是如何定义视图对象的示例:
import { registry } from "@web/core/registry"; import { MyController } from "./my_controller"; export const myView = { type: "my_view", display_name: "MyView", icon: "oi oi-view-list", multiRecord: true, Controller: MyController, }; registry.category("views").add("my_controller", myView);
将
gallery
添加为contacts.action_contacts
操作中的一种视图类型。确保在切换到图库视图时,您可以看到您的hello world组件。


2. 使用布局组件¶
到目前为止,我们的图库视图看起来不像是一个标准视图。让我们使用 Layout
组件来拥有其他视图的标准功能。
导入
Layout
组件并将其添加到GalleryController
的components
中。更新模板以使用
Layout
。它需要一个display
属性,可以在props.display
中找到。

3. 解析 arch¶
目前,我们的图库视图还没有做太多事情。让我们从阅读视图中包含的结构信息开始。
解析arch的过程通常使用 ArchParser
来完成,它是每个视图特定的。它继承自一个通用的 XMLParser
类。
Example
这是一个 ArchParser 可能看起来像的例子:
export class MyCustomArchParser {
parse(xmlDoc) {
const myAttribute = xmlDoc.getAttribute("my_attribute")
return {
myAttribute,
}
}
}
在单独的文件中创建
ArchParser
类。使用它来读取
image_field
信息。更新
gallery
视图代码,将其添加到控制器接收的 props 中。
注解
这样做可能有点过度设计,因为我们基本上只需要从arch中读取一个属性,但这是每个Odoo视图都使用的设计,因为它让我们将一些前期处理从控制器中提取出来。
另请参阅
4. 加载一些数据¶
让我们现在从服务器获取一些真实数据。为此,我们必须使用 orm 服务中的 webSearchRead
。
Example
以下是一个 webSearchRead
的示例,用于从模型中获取记录:
const { length, records } = this.orm.webSearchRead(this.resModel, domain, {
specification: {
[this.fieldToFetch]: {},
[this.secondFieldToFetch]: {},
},
context: {
bin_size: true,
}
})
在
GalleryController
中添加一个loadImages(domain) {...}
方法。它 应该执行一个来自 orm 服务的webSearchRead
调用,以获取与域相对应的记录 并使用在 props 中接收到的imageField
。如果在调用时未在上下文中包含
bin_size
,你将收到以 base64 编码的图像字段。确保将bin_size
放入上下文中以接收图像字段的大小。我们稍后将显示图像。修改
setup
代码,在onWillStart
和onWillUpdateProps
钩子中调用该方法。修改模板以在
Layout
组件的默认插槽内显示每张图片的 id 和大小。
注解
加载数据的代码将在下一个练习中被移至一个合适的模型中。

5. Solve the concurrency problem¶
目前,我们的代码不具备并发安全性。如果某人两次更改域,它将触发 loadImages(domain)
两次。因此,我们有两个请求,它们可能会根据不同的因素在不同时间到达。如果在接收到第二个请求的响应之后才接收到第一个请求的响应,将导致状态不一致。
Odoo 的 KeepLast
原语解决了这个问题,它管理一个任务列表,并且只保持最后一个任务处于活动状态。
从
@web/core/utils/concurrency
导入KeepLast
。在模型中实例化一个
KeepLast
对象。将
webSearchRead
调用添加到KeepLast
中,以便仅解析最后一次调用。
另请参阅
6. Reorganize code¶
真正的视图会更加有组织。在这个例子中可能有些过度,但这是为了学习如何在Odoo中构建代码结构。此外,这将更好地适应不断变化的需求。
将所有模型代码移动到自己的
GalleryModel
类中。将所有渲染代码移动到
GalleryRenderer
组件中。在
GalleryController
中导入GalleryModel
和GalleryRenderer
以使其工作。
7. Make the view extensible¶
要扩展视图,可以导入画廊视图对象并根据自己的喜好进行修改。问题在于,目前无法定义自定义模型或渲染器,因为它们在控制器中是硬编码的。
在 gallery 视图文件中导入
GalleryModel
和GalleryRenderer
。向图库视图对象添加
Model
和Renderer
键,并将它们分配给GalleryModel
和GalleryRenderer
。将Model
和Renderer
作为 props 传递给控制器。移除控制器中的硬编码导入,并从 props 中获取它们。
使用 t-component 来拥有动态子组件。
注解
这是现在有人可以通过修改渲染器来扩展画廊视图的方式:
import { registry } from '@web/core/registry';
import { galleryView } from '@awesome_gallery/gallery_view';
import { GalleryRenderer } from '@awesome_gallery/gallery_renderer';
export class MyExtendedGalleryRenderer extends GalleryRenderer {
static template = "my_module.MyExtendedGalleryRenderer";
setup() {
super.setup();
console.log("my gallery renderer extension");
}
}
registry.category("views").add("my_gallery", {
...galleryView,
Renderer: MyExtendedGalleryRenderer,
});
8. Display images¶
更新渲染器以便以美观的方式显示图像,如果字段已设置。如果 image_field
为空,则显示一个空框。
小技巧
有一个控制器允许从记录中检索图像。您可以使用此代码片段来构建链接:
import { url } from "@web/core/utils/urls";
const url = url("/web/image", {
model: resModel,
id: image_id,
field: imageField,
});

9. Switch to form view on click¶
更新渲染器以响应对图像的单击并切换到表单视图。您可以使用操作服务中的 switchView
函数。
另请参阅
10. Add an optional tooltip¶
鼠标悬停时提供一些额外的信息是有用的。
更新代码以允许在arch上添加一个可选的附加属性:
<gallery image_field="some_field" tooltip_field="some_other_field"/>
鼠标悬停时,显示工具提示字段的内容。如果字段是字符字段、数字字段或 many2one 字段,则应有效。要为 html 元素添加工具提示,可以将字符串放在元素的
data-tooltip
属性中。更新客户画廊视图架构,将客户添加为工具提示字段。

11. Add pagination¶
让我们在控制面板上添加一个分页器,并像在普通 Odoo 视图中一样管理所有分页。

12. Validating views¶
到目前为止,我们已经拥有了一个漂亮且有用的视图。但在实际应用中,我们可能会遇到用户错误编码其图库视图的 arch
的问题:它目前只是一个非结构化的 XML 片段。
让我们添加一些验证!在 Odoo 中,XML 文档可以用 RN 文件 (Relax NG 文件) 来描述,然后进行验证。
添加一个描述当前语法的 RNG 文件:
一个必填属性
image_field
.一个可选属性:
tooltip_field
。
添加一些代码,确保所有视图都根据此 RNG 文件进行验证。
顺便提一下,确保
image_field
和tooltip_field
是当前模型中的字段。
由于验证 RNG 文件并不容易,这里提供了一个代码片段以帮助您:
# -*- coding: utf-8 -*-
import logging
import os
from lxml import etree
from odoo.loglevels import ustr
from odoo.tools import misc, view_validation
_logger = logging.getLogger(__name__)
_viewname_validator = None
@view_validation.validate('viewname')
def schema_viewname(arch, **kwargs):
""" Check the gallery view against its schema
:type arch: etree._Element
"""
global _viewname_validator
if _viewname_validator is None:
with misc.file_open(os.path.join('modulename', 'rng', 'viewname.rng')) as f:
_viewname_validator = etree.RelaxNG(etree.parse(f))
if _viewname_validator.validate(arch):
return True
for error in _viewname_validator.error_log:
_logger.error(ustr(error))
return False
另请参阅
13. Uploading an image¶
我们的画廊视图不允许用户上传图片。让我们来实现这个功能。
通过使用
FileUploader
组件在每个图像上添加一个按钮。FileUploader
组件接受onUploaded
属性,当用户上传图片时会被调用。确保从 orm 服务调用webSave
以上传新图片。你可能已经注意到图片已上传,但浏览器并未重新渲染。这是因为图片链接未改变,所以浏览器不会重新获取它们。请将记录中的
write_date
包含到图片的URL中。确保点击上传按钮不会触发 switchView。

14. Advanced tooltip template¶
目前我们只能指定一个工具提示字段。但如果我们想允许为其编写特定模板呢?
Example
这是一个图库架构视图的示例,完成本练习后应能正常运行。
<record id="contacts_gallery_view" model="ir.ui.view">
<field name="name">awesome_gallery.orders.gallery</field>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<gallery image_field="image_1920" tooltip_field="name">
<field name="email"/> <!-- Specify to the model that email should be fetched -->
<field name="name"/> <!-- Specify to the model that name should be fetched -->
<tooltip-template> <!-- Specify the owl template for the tooltip -->
<p class="m-0">name: <field name="name"/></p> <!-- field is compiled into a t-esc-->
<p class="m-0">e-mail: <field name="email"/></p>
</tooltip-template>
</gallery>
</field>
</record>
将
awesome_gallery/views/views.xml
中的res.partner
gallery arch 视图替换为上例中的 arch。即使它没有通过 rng 验证,也不必担心。修改 gallery rng 验证器以接受新的 arch 结构。
小技巧
您可以使用此 rng 片段来验证 tooltip-template 标签
<rng:define name="tooltip-template"> <rng:element name="tooltip-template"> <rng:zeroOrMore> <rng:text/> <rng:ref name="any"/> </rng:zeroOrMore> </rng:element> </rng:define> <rng:define name="any"> <rng:element> <rng:anyName/> <rng:zeroOrMore> <rng:choice> <rng:attribute> <rng:anyName/> </rng:attribute> <rng:text/> <rng:ref name="any"/> </rng:choice> </rng:zeroOrMore> </rng:element> </rng:define>
架构解析器应解析字段和工具提示模板。从
@web/core/utils/xml
导入visitXML
并使用它来解析字段名称和工具提示模板。确保模型通过将解析后的字段名称包含在规范中来调用
webSearchRead
。渲染器(或您为其创建的任何子组件)应接收解析后的工具提示模板。操作此模板,将
<field>
元素替换为<t t-esc="x">
元素。小技巧
模板是一个
Element
对象,因此可以像 HTML 元素一样进行操作。通过
@odoo/owl
中的xml
函数将模板注册到 Owl。使用
@web/core/tooltip/tooltip_hook
中的useTooltip
钩子来显示工具提示。此钩子将 Owl 模板和模板所需的变量作为参数。
