第 11 章:锦上添花

我们的不动产模块现在从业务角度来看已经说得通了。我们创建了 特定视图,添加了若干 动作按钮 和 约束。然而,我们的用户界面仍然有点简陋。我们想为列表视图添加一些颜色,并让一些字段和按钮有条件地消失。例如,当房产已售出或已取消时,’Sold’ 和 ‘Cancel’ 按钮应该消失,因为此时已不允许更改状态。

本章仅涵盖视图中可做内容的一小部分。如需更全面的概览,请随时阅读参考文档。

参考资料:与本章相关的文档可参见 视图记录 和 视图架构。

内联视图

Note

目标:在本节结束时,房产类型视图中应添加一个特定的房产列表:

内联列表视图

在不动产模块中,我们为一个房产添加了报价列表。我们只是简单地添加了字段 offer_ids:

<field name="offer_ids"/>

该字段使用 estate.property.offer 的特定视图。在某些情况下,我们希望定义一个仅在表单视图上下文中使用的特定列表视图。例如,我们希望显示与某个房产类型关联的房产列表。但为了清晰起见,我们只想显示 3 个字段:名称、预期价格和状态。

为此,我们可以定义 内联 列表视图。内联列表视图直接定义在表单视图内部。例如:

from odoo import fields, models

class TestModel(models.Model):
    _name = "test_model"
    _description = "Test Model"

    description = fields.Char()
    line_ids = fields.One2many("test_model_line", "model_id")


class TestModelLine(models.Model):
    _name = "test_model_line"
    _description = "Test Model Line"

    model_id = fields.Many2one("test_model")
    field_1 = fields.Char()
    field_2 = fields.Char()
    field_3 = fields.Char()
<form>
    <field name="description"/>
    <field name="line_ids">
        <list>
            <field name="field_1"/>
            <field name="field_2"/>
        </list>
    </field>
</form>

在 test_model 的表单视图中,我们为 test_model_line 定义了一个特定列表视图,包含字段 field_1 和 field_2。

您可以在 此处 找到一个示例。

Exercise

添加一个内联列表视图。

  • 将 One2many 字段 property_ids 添加到 estate.property.type 模型中。

  • 在 estate.property.type 表单视图中添加该字段,如本节 目标 所示。

组件

参考:与本节相关的文档见 字段。

Note

目标:在本节结束时,房产的状态应使用特定的小部件显示:

状态栏小部件

共显示四种状态:New、Offer Received、Offer Accepted 和 Sold。

每当我们向模型添加字段时,(几乎)从不需要担心这些字段在用户界面中的呈现方式。例如,Date 字段会提供日期选择器,One2many 字段会自动显示为列表。Odoo 会根据字段类型选择合适的’widget’(小部件)。

不过,在某些情况下,我们希望字段有特定的呈现方式,这可以通过 widget 属性实现。我们之前在使用 widget="many2many_tags" 属性时,已经在 tag_ids 字段上用过它。如果不使用它,该字段将显示为列表。

每种字段类型都有一组可用于微调其显示的小部件。有些小部件还接受额外的选项。完整列表见 字段。

Exercise

使用状态栏小部件。

使用 statusbar 小部件显示 estate.property 的 state,如本节 目标 所示。

提示:简单示例可参见 这里。

Warning

同一字段在视图中出现多次

在列表或表单视图中只能 一次 添加字段。不支持多次添加。

列表排序

参考:与本节相关的文档见 模型。

Note

目标:在本节结束时,所有列表默认都应按确定的顺序显示。房产类型可以手动排序。

在前面的练习中,我们创建了多个列表视图。但我们从未指定记录默认按什么顺序列出。对许多业务场景来说,这是非常重要的一点。例如,在我们的房地产模块中,我们希望最高的报价显示在列表顶部。

Model

Odoo 提供了多种设置默认排序的方法。最常见的方法是直接在模型中定义 _order 属性。这样,检索到的记录将遵循确定的顺序,并且在所有视图(包括以编程方式搜索记录时)中都保持一致。默认情况下没有指定排序,因此记录将按 PostgreSQL 决定的非确定顺序检索。

_order 属性接受一个字符串,其中包含用于排序的字段列表。它会被转换为 SQL 中的 order_by 子句。例如:

from odoo import fields, models

class TestModel(models.Model):
    _name = "test_model"
    _description = "Test Model"
    _order = "id desc"

    description = fields.Char()

我们的记录按 id 降序排列,即最大的排在最前。

Exercise

添加模型排序。

在相应的模型中定义以下排序:

Model

排序

estate.property.type

ID 降序

estate.property.offer

价格降序

estate.property.tag

名称

estate.property.type

名称

视图

排序可以在模型级别进行,它的优势是在任何检索记录列表的地方都能保持一致的顺序。不过,也可以通过 default_order 属性直接在视图中定义特定顺序(示例)。

手动排序

模型排序和视图排序都为记录排序提供了灵活性,但还有一种情况需要覆盖:手动排序。用户可能希望根据业务逻辑对记录进行排序。例如,在我们的房地产模块中,我们希望手动对房产类型进行排序。让最常用的类型显示在列表顶部确实很有用。如果我们的房地产中介主要销售独栋房屋,那么让 ‘House’ 排在 ‘Apartment’ 之前会更方便。

为此,可以使用 sequence 字段配合 handle 小部件。显然,sequence 字段必须是 _order 属性中的第一个字段。

Exercise

添加手动排序。

  • 添加以下字段:

Model

字段

类型

estate.property.type

序列

整数

  • 使用正确的小部件将序列添加到 estate.property.type 列表视图中。

提示:可以在此处找到示例:模型 和 视图。

属性与选项

详尽介绍所有可用于微调视图外观的可用特性将不胜其烦。因此,我们只介绍最常用的那些。

表单

Note

目标:在本节结束时,房产表单视图将具有:

  • 按钮和字段的条件显示

  • 标签颜色

锦上添花的表单视图

在我们的房地产模块中,我们希望修改某些字段的行为。例如,我们不希望从表单视图创建或编辑房产类型,而是希望在相应的菜单中处理这些类型。我们还希望给标签添加颜色。为了添加这些行为定制,我们可以向若干字段小部件添加 options 属性。

Exercise

添加小部件选项。

  • 向 property_type_id 字段添加适当的选项,以阻止从房产表单视图创建和编辑房产类型。更多信息请参阅 Many2one 小部件文档。

  • 添加以下字段:

Model

字段

类型

estate.property.tag

颜色

整数

然后向 tag_ids 字段添加适当的选项,为标签添加颜色选择器。更多信息请参阅 FieldMany2ManyTags 小部件文档。

在 第 5 章:终于有 UI 可以玩了 中,我们看到保留字段用于特定行为。例如,active 字段用于自动过滤掉非活动记录。我们也将 state 添加为保留字段。现在是使用它的时候了!state 字段可以在视图中与 invisible 属性结合使用,以有条件地显示按钮。

Exercise

添加按钮的条件显示。

使用 invisible 属性有条件地显示头部按钮,如本节 目标 所示(注意状态改变时 ‘Sold’ 和 ‘Cancel’ 按钮的变化)。

提示:不妨在 Odoo XML 文件中搜索 invisible= 寻找一些示例。

更广泛地说,可以根据其他字段的值将字段设为 invisible、readonly 或 required。注意 invisible 也可以应用于视图的其他元素,如 button 或 group。

invisible、readonly 和 required 可以使用任意 Python 表达式作为值。该表达式给出属性生效的条件。例如:

<form>
    <field name="description" invisible="not is_partner"/>
</form>

这意味着当 is_partner 为 False 时,description 字段不可见。需要注意,invisible 中使用的字段 必须 存在于视图中。如果不希望向用户显示它,我们可以使用 invisible 属性将其隐藏。

Exercise

使用 invisible。

  • 当没有花园时,在 estate.property 表单视图中隐藏花园面积和朝向。

  • 报价状态一经设置,就让 ‘Accept’ 和 ‘Refuse’ 按钮不可见。

  • 当房产状态为 ‘Offer Accepted’、’Sold’ 或 ‘Cancelled’ 时,不允许添加报价。为此请使用 readonly 属性。

Warning

在视图中使用(有条件的) readonly 属性有助于防止数据输入错误,但请记住它不提供任何级别的安全性!服务端不会进行任何检查,因此始终可以通过 RPC 调用写入该字段。

列表

Note

目标:在本节结束时,房产和报价列表视图应具有颜色装饰。此外,报价和标签将可以直接在列表中编辑,且可用日期默认隐藏。

带装饰和可选字段的列表视图 可编辑列表

当模型只有少量字段时,直接通过列表视图编辑记录而无需打开表单视图会很有用。在房地产示例中,无需打开表单视图即可添加报价或创建新标签。这可以通过 editable 属性实现。

Exercise

让列表视图可编辑。

让 estate.property.offer 和 estate.property.tag 列表视图可编辑。

另一方面,当模型有很多字段时,容易在列表视图中添加过多字段而使界面混乱。另一种方法是添加这些字段,但让它们可以按需隐藏。这可以通过 optional 属性实现。

Exercise

让字段可选。

让 estate.property 列表视图中的 date_availability 字段可选且默认隐藏。

最后,颜色标记有助于从视觉上强调记录。例如,在房地产模块中,我们希望以红色显示被拒绝的报价,以绿色显示被接受的报价。这可以通过 decoration-{$name} 属性实现(完整列表见 字段):

<list decoration-success="is_partner==True">
    <field name="name"/>
</list>

is_partner 为 True 的记录将以绿色显示。

Exercise

添加一些装饰。

在 estate.property 列表视图中:

  • 已收到报价的房产显示为绿色

  • 报价已被接受的房产显示为绿色加粗

  • 已售出的房产显示为淡化样式

在 estate.property.offer 列表视图中:

  • 被拒绝的报价显示为红色

  • 被接受的报价显示为绿色

  • 状态字段不应再显示

提示:

  • 请记住,属性中用到的 所有 字段都必须在视图中!

  • 如果想测试 “Offer Received” 和 “Offer Accepted” 状态的颜色,请将该字段添加到表单视图中并手动更改它(我们稍后会实现相应的业务逻辑)。

统计按钮

Note

目标:在本节结束时,房产类型表单视图上会有一个统计按钮,点击它时显示与给定类型的房产相关的所有报价列表。

统计按钮

如果您已经在 Odoo 中使用过一些功能模块,很可能已经遇到过’统计按钮’(stat button)。这些按钮显示在表单视图的右上角,提供对相关文档的快速访问。在我们的房地产模块中,我们希望有一个快速链接,指向与给定房产类型相关的报价,如本节 目标 所示。

在本教程的这个阶段,我们已经了解了完成此任务所需的大部分概念。不过,解决方案并非只有一种,如果您不知道从哪里开始,可能仍会感到困惑。我们将在练习中描述一个分步解决方案。在 Odoo 代码库中搜索 oe_stat_button 找一些示例总是很有用的。

下面的练习可能比之前的稍难一些,因为它假定您能够自己在源代码中查找示例。如果遇到困难,附近很可能有人可以帮助您 ;-)

本练习介绍了 关联字段 的概念。理解它最简单的方法是将其视为计算字段的一种特例。以下是 description 字段的定义:

...

partner_id = fields.Many2one("res.partner", string="Partner")
description = fields.Char(related="partner_id.name")

等价于:

...

partner_id = fields.Many2one("res.partner", string="Partner")
description = fields.Char(compute="_compute_description")

@api.depends("partner_id.name")
def _compute_description(self):
    for record in self:
        record.description = record.partner_id.name

每次更改伙伴名称时,描述都会被修改。

Exercise

为房产类型添加统计按钮。

  • 向 estate.property.offer 添加 property_type_id 字段。我们可以将其定义为基于 property_id.property_type_id 的关联字段,并设置为存储型。

借助此字段,报价在创建时将链接到房产类型。您可以将该字段添加到报价的列表视图中以确认其正常工作。

  • 向 estate.property.type 添加 offer_ids 字段,它是上一步所定义字段的一对多反向字段。

  • 向 estate.property.type 添加 offer_count 字段。它是一个计算字段,用于统计给定房产类型的报价数量(使用 offer_ids 来实现)。

至此,您已拥有了解某个房产类型关联了多少报价所需的全部信息。如有疑问,可以将 offer_ids 和 offer_count 直接添加到视图中。下一步是在点击统计按钮时显示列表。

  • 在 estate.property.type 上创建一个指向 estate.property.offer 动作的统计按钮。这意味着您应使用 type="action" 属性(如需温习,请回顾 第 9 章:准备好来点动作了吗? 的结尾部分)。

此时,点击统计按钮应显示所有报价。我们还需要对这些报价进行过滤。

  • 在 estate.property.offer 动作上,添加一个将 property_type_id 定义为等于 active_id 的域(即当前记录,这里有一个示例)

看起来不错吗?如果不也没关系,下一章 并不要求统计按钮 ;-)