第一章:字段和视图

在上一章中,我们学习了一系列的技能,包括如何创建和使用服务,与布局组件一起工作,使仪表板可翻译,并延迟加载像Chart.js这样的JavaScript库。现在,让我们学习如何创建新的字段和视图。

本章节的每个练习的解决方案都托管在 官方Odoo教程存储库 上。 在尝试练习之前不建议查看解决方案。

字段和视图是Odoo用户界面中最重要的概念之一。它们对许多重要的用户交互起着关键作用,因此应该完美地工作。在JavaScript框架的上下文中,字段是专门用于可视化/编辑给定记录的特定字段的组件。例如,(Python)模型可以定义一个字符字段,该字段将由字段组件 CharField 表示。

一个字段组件基本上只是在 fields registry 中注册的组件。字段组件可以定义一些额外的静态键(元数据),例如 displayNamesupportedTypes ,以及最重要的一个: extractProps ,它准备了 CharField 接收到的基本 props。

示例:一个简单的字段

让我们讨论一个简化的 CharField 实现。首先,这是模板:

<t t-name="web.CharField" owl="1">
    <t t-if="props.readonly">
        <span t-esc="formattedValue" />
    </t>
    <t t-else="">
        <input
            class="o_input"
            t-att-type="props.isPassword ? 'password' : 'text'"
            t-att-placeholder="props.placeholder"
            t-on-change="updateValue"
        />
    </t>
</t>

它具有只读模式和编辑模式,后者是带有一些属性的输入。现在,这是JavaScript代码:

export class CharField extends Component {
    get formattedValue() {
        return formatChar(this.props.value, { isPassword: this.props.isPassword });
    }

    updateValue(ev) {
        let value = ev.target.value;
        if (this.props.shouldTrim) {
            value = value.trim();
        }
        this.props.update(value);
    }
}

CharField.template = "web.CharField";
CharField.displayName = _lt("Text");
CharField.supportedTypes = ["char"];

CharField.extractProps = ({ attrs, field }) => {
    return {
        shouldTrim: field.trim && !archParseBoolean(attrs.password),
        maxLength: field.size,
        isPassword: archParseBoolean(attrs.password),
        placeholder: attrs.placeholder,
    };
};

registry.category("fields").add("char", CharField);

有几个重要的事情需要注意:

  • The CharField receives its (raw) value in props. It needs to format it before displaying it.

  • 它在其props中接收一个 update 函数,该函数由字段用于通知状态所有者该字段的值已更改。请注意,字段不会(也不应该)维护其值的本地状态。每当更改已应用时,它将通过props的方式返回(可能在onchange之后)。

  • 它定义了一个 extractProps 函数。这是一个将通用标准属性转换为视图特定专用属性的步骤,这些属性对组件非常有用。这使得组件具有更好的 API,并且可能使其可重用。

字段必须在 fields 注册表中注册。一旦完成,它们可以在某些视图中使用(即: formlistkanban ),通过使用 widget 属性。

Example

<field name="preview_moves" widget="account_resequence_widget"/>

1. image_preview 字段

每个在网站上的新订单都会被创建为 awesome_tshirt.order。这个模型有一个 image_url 字段(类型为 char),目前只能以字符串的形式显示。我们希望能够在表单视图中看到图片本身。

Exercise

  1. 创建一个新的 ImagePreview 组件并将其注册到适当的 注册表 中。在你的模板中使用 CharField 组件。你可以使用 t-propsImagePreview 接收到的属性传递给 CharField。通过设置 widget 属性来更新表单视图的结构以使用你的新字段。

  2. 更改 ImagePreview 组件的代码,使得图片在 URL 下方显示。

  3. 当字段为只读时,只显示图像,隐藏URL。

注解

可以通过继承 CharField 来解决这个练习,但是这个练习的目标是从头开始创建一个字段。

../../../_images/image_field.png

另请参阅

Code: CharField

2. 改进 image_preview 字段

我们希望改进上一个任务的领域,以帮助员工识别需要采取某些行动的订单。

Exercise

如果订单中没有指定图像URL,则以红色显示警告“缺少T恤设计”。

../../../_images/missing_image.png

3. 自定义字段组件

让我们看看如何使用继承来扩展现有组件。

有一个 is_late 字段,是只读的布尔字段,位于订单模型上。在列表/看板/视图上看到这个信息会很有用。然后,假设我们想在它被设置为 true 时,在旁边添加一个红色的词“Late!”。

Exercise

  1. 创建一个继承自 BooleanField 的新 LateOrderBoolean 字段。 LateOrderBoolean 的模板也可以从 BooleanField 模板中 继承

  2. 在列表/看板/表单视图中使用它。

  3. 修改它以在旁边添加一个红色的 Late,如所请求的。

../../../_images/late_field.png

4. 针对某些客户的消息

Odoo表单视图支持 widget API,它类似于字段,但更通用。它可用于在表单视图中插入任意组件。让我们看看如何使用它。

Exercise

为了实现超高效的工作流程,我们希望根据一些条件显示具体信息的警告块:

  • 如果 image_url 字段未设置,则应显示“无图像”。

  • 如果订单的 amount 高于100欧元,则应显示“添加促销材料”。

  • 确保您的小部件实时更新。

小技巧

尝试在浏览器的开发工具的 Console 选项卡中评估 props.record

../../../_images/warning_widget.png

5. 使用 markup

让我们看看如何在模板中显示原始HTML。`t-out`指令可以用于此目的。实际上,它通常像`t-esc`一样工作,除非数据已经明确地使用标记函数标记了<https://github.com/odoo/owl/blob/master/doc/reference/templates.md#outputting-data>`_。在这种情况下,它的值将被注入为HTML。

Exercise

  1. 修改前面的练习,将 imagematerial 这两个词加粗。

  2. 警告应该被标记,并且模板应该被修改以使用 t-out

  3. 从Owl中导入 markup 函数,并且对于每个消息,将其替换为使用该函数并将消息作为参数传递的调用。

注解

这是一个对 t-out 的安全使用示例,因为字符串是静态的。

../../../_images/warning_widget2.png

6. 在控制面板中添加按钮

视图是Odoo中最重要的组件之一:它们允许用户与其数据进行交互。让我们讨论一下Odoo视图的设计。

Odoo 视图的强大之处在于它们声明了一个特定屏幕应该如何与一个 XML 文档(通常命名为 arch,即 architecture 的缩写)进行交互。这个描述可以通过服务器端的 xpaths 进行扩展/修改。然后,浏览器加载该文档,解析它(这是一个花哨的说法,意思是提取有用的信息),然后相应地表示数据。

Example

The arch document is view specific. Here is how a graph view or a calendar view could be defined:

<graph string="Invoices Analysis" type="line" sample="1">
    <field name="product_categ_id"/>
    <field name="price_subtotal" type="measure"/>
</graph>

<calendar string="Leads Generation" create="0" mode="month" date_start="activity_date_deadline" color="user_id" hide_time="true" event_limit="5">
    <field name="expected_revenue"/>
    <field name="partner_id" avatar_field="avatar_128"/>
    <field name="user_id" filters="1" invisible="1"/>
</calendar>

视图是由具有一些特定键的对象在视图注册表中定义的。

  • type:视图的(基本)类型(例如,formlist…)。

  • display_name: What should be displayed in the tooltip in the view switcher.

  • icon: Which icon to use in the view switcher.

  • multiRecord: Whether the view is supposed to manage a single record or a set of records.

  • Controller: The component that will be used to render the view (the most important information).

Example

这是一个最小的 Hello 视图,它不显示任何内容:

/** @odoo-module */

import { registry } from "@web/core/registry";

export const helloView = {
   type: "hello",
   display_name: "Hello",
   icon: "fa fa-picture-o",
   multiRecord: true,
   Controller: Component,
};

registry.category("views").add("hello", helloView);

大多数(或全部?)Odoo视图共享一个通用的架构:

../../../_images/view_architecture.svg

视图描述可以定义一个 props 函数,该函数接收标准的 props,并计算具体视图的基本 props。 props 函数只执行一次,可以被视为某种工厂。它有助于解析 arch XML 文档,并允许视图进行参数化(例如,它可以返回一个将用作 Renderer 的 Renderer 组件)。然后,可以轻松自定义子视图使用的特定渲染器。

props 在传递给控制器之前将被扩展。特别是,搜索 props(domain/context/groupby)将被添加。

最后,根组件,通常称为 Controller ,协调所有事物。它使用通用的 Layout 组件(用于添加控制面板),实例化一个 Model ,并在 Layout 的默认插槽中使用一个 Renderer 组件。 Model 负责加载和更新数据, Renderer 应该处理所有的渲染工作,以及所有的用户交互。

实际上,一旦T恤订单被打印出来,我们需要打印一个标签贴在包裹上。为了做到这一点,让我们在订单的表单视图的控制面板中添加一个按钮,该按钮将调用一个模型方法。

有一个专门用于调用模型方法的服务: orm_service ,位于 core/orm_service.js 。它提供了一种调用常见模型方法的方式,以及一个通用的 call(model, method, args, kwargs) 方法。

Example

setup() {
    this.orm = useService("orm");
    onWillStart(async () => {
        // will read the fields 'id' and 'descr' from the record with id=3 of my.model
        const data = await this.orm.read("my.model", [3], ["id", "descr"]);
        // ...
    });
}

Exercise

  1. 创建一个自定义的表单视图,扩展 web 表单视图,并将其注册为 awesome_tshirt.order_form_view

  2. 在表单视图的结构中添加一个 js_class="awesome_tshirt.order_form_view" 属性,这样Odoo就会加载它。

  3. 创建一个新的模板,继承自表单控制器模板,并在“新建”按钮之后添加一个“打印标签”按钮。

  4. 点击此按钮应调用模型 awesome_tshirt.order 中的方法 print_label 并传递正确的 id。

    注解

    print_label 是一个模拟方法;它只在日志中显示一条消息。

  5. 如果当前订单处于 create 模式(即尚不存在),则按钮不应被禁用。

    小技巧

    记录 this.props.resIdthis.model.root.resId 的值,并在进入 create 模式之前和之后进行比较。

  6. 如果客户正确设置并且任务阶段为 printed,则按钮应显示为主要按钮。否则,应显示为次要按钮。

  7. 加分项:点击两次按钮不应触发2个RPC。

../../../_images/form_button.png

7. 自动重新加载看板视图

Bafien很沮丧:他想在外部显示器上看到t恤订单的看板视图,但视图需要保持最新。他厌倦了每30秒点击 刷新 图标,所以他委托你找到一种自动完成的方法。

就像之前的练习一样,这种定制需要创建一个新的JavaScript视图。

Exercise

  1. 扩展看板视图/控制器,使其每分钟重新加载数据。

  2. 在视图注册表中注册它,位于 awesome_tshirt.autoreloadkanban

  3. 在看板视图的arch中使用它(使用 js_class 属性)。

重要

如果您使用 setInterval 或类似的东西,请确保在组件卸载时正确取消它。否则,您将引入内存泄漏。