从商业角度来看,我们的房地产模块现在是有意义的。我们创建了特定的视图,添加了几个操作按钮和约束。然而,我们的用户界面仍然有点粗糙。我们想为列表视图添加一些颜色,并使一些字段和按钮有条件地消失。例如,当属性被出售或取消时,’ Sold ‘和’ Cancel '按钮应该消失,因为此时不再允许更改状态。
这一章涵盖了视图能做的事情中的很小一部分。 如有需要,请阅读参考文档以获得更完整的概述。
<field name="offer_ids"/>
该字段使用estate.property.offer的特定视图。在某些情况下,我们想要定义一个特定的列表视图,它只在表单视图的上下文中使用。例如,我们希望显示链接到属性类型的属性列表。但是,为了清晰起见,我们只想显示3个字段:名称(name)、预期价格(expected pric)和状态(state)。
为此,我们可以定义内联列表视图。内联列表视图直接在表单视图中定义。例如:
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()
TestModel 视图
<form>
<field name="description"/>
<field name="line_ids">
<tree>
<field name="field_1"/>
<field name="field_2"/>
<field name="field_3"/>
</tree>
</field>
</form>
完整视图例子:
<field name="tag_ids" context="{'default_category_id': active_id}">
<tree string="Tags" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="color" widget="color_picker"/>
</tree>
</field>
每当我们向模型中添加字段时,我们(几乎)不必担心这些字段在用户界面中的外观。例如,为date字段提供了日期选择器,One2many字段自动显示为列表。Odoo根据字段类型选择正确的“小部件”。
但是,在某些情况下,我们需要字段的特定表示,这可以通过widget属性来实现。当我们使用widget="many2many_tags"属性时,我们已经将它用于tag_ids字段。如果我们没有使用它,那么该字段将显示为一个列表。
每种字段类型都有一组可用于微调其显示的小部件。一些小部件还具有额外的选项。可以在字段中找到详尽的列表。
在前面的练习中,我们创建了几个列表(tree)视图。但是,在任何时候我们都没有指定记录在默认情况下必须以哪个顺序列出。这对于很多商业案例来说都是非常重要的。例如,在我们的房地产模块中,我们希望在列表顶部显示最高报价。
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()
可以在模型级别进行排序。这样做的优点是在检索记录列表的任何地方都有一致的顺序。然而,由于default_order属性,也可以直接在视图中定义特定的顺序。
示例代码:
<record id="crm_activity_report_view_tree" model="ir.ui.view">
<field name="name">crm.activity.report.tree</field>
<field name="model">crm.activity.report</field>
<field name="arch" type="xml">
<tree default_order="date desc">
<field name="date"/>
<field name="author_id"/>
<field name="mail_activity_type_id"/>
<field name="body"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
在排序记录时,模型和视图排序都允许灵活性,但是我们仍然需要介绍一种情况:手动排序。用户可能希望根据业务逻辑对记录进行排序。例如,在房地产模块中,我们希望手动对属性类型进行排序。让最常用的类型出现在列表的顶部确实很有用。如果我们的房地产中介主要卖的是房子,那么“House”出现在“Apartment”之前会更方便。
为此,将序列字段与handle小部件结合使用。显然,sequence字段必须是_order属性中的第一个字段。
示例代码:
model 代码
class Stage(models.Model):
""" 案例阶段模型。这对文档的主要阶段进行建模
管理流程。主要的CRM对象(潜在客户,机会,项目)
问题,…)现在将只使用阶段,而不是状态和阶段。
例如,阶段用于显示记录的看板视图。
"""
_name = "crm.stage"
_description = "CRM Stages"
_rec_name = 'name'
_order = "sequence, name, id"
@api.model
def default_get(self, fields):
""" Hack : 从流水线中脱离出来,与销售团队一起创建一个舞台
背景不应只为当前的销售团队创造舞台
"""
ctx = dict(self.env.context)
if ctx.get('default_team_id') and not ctx.get('crm_team_mono'):
ctx.pop('default_team_id')
return super(Stage, self.with_context(ctx)).default_get(fields)
name = fields.Char('Stage Name', required=True, translate=True)
sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.")
is_won = fields.Boolean('Is Won Stage?')
requirements = fields.Text('Requirements', help="Enter here the internal requirements for this stage (ex: Offer sent to customer). It will appear as a tooltip over the stage's name.")
team_id = fields.Many2one('crm.team', string='Sales Team', ondelete='set null',
help='Specific team that uses this stage. Other teams will not be able to see or use this stage.')
fold = fields.Boolean('Folded in Pipeline',
help='This stage is folded in the kanban view when there are no records in that stage to display.')
# This field for interface only
team_count = fields.Integer('team_count', compute='_compute_team_count')
视图代码:
<!-- STAGES TREE VIEW + MUTI_EDIT -->
<record id="crm_stage_tree" model="ir.ui.view">
<field name="name">crm.stage.tree</field>
<field name="model">crm.stage</field>
<field name="arch" type="xml">
<tree string="Stages" multi_edit="1">
<field name="sequence" widget="handle"/>
<field name="name" readonly="1"/>
<field name="is_won"/>
<field name="team_id"/>
</tree>
</field>
</record>
设置属性窗体视图将包含:
例如:
<field name="int_value" options="{'type': 'number', 'step': 100}" />
在之前的文章中,我们看到保留字段用于特定行为。例如,active 活动字段用于自动过滤掉非活动记录。我们也添加了state状态作为保留字段。现在是时候使用它了 !state 状态字段与视图中的不可见属性结合使用,以有条件地显示按钮。
一般来说,invisible可以根据其他字段的值使字段不可见、只读或必需。请注意,不可见也可以应用于视图的其他元素,如按钮或组。
invisible、readonly或required可以具有True、False或域作为值。域给出了属性适用的条件。例如:
<form>
<field name="description" invisible="not is_partner"/>
<field name="is_partner" invisible="1"/>
</form>
在视图中使用(有条件的)readonly属性有助于防止数据输入错误,但请记住,它不提供任何级别的安全性!没有在服务器端完成检查,因此始终可以通过RPC调用在字段上写入。
readonly实现只读代码示例
方式一:
name = fields.Char(readonly=True)
方式二:
field name="name" readonly="True"/>
<tree editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="number_of_months"/>
</tree>
另一方面,当一个模型有很多字段时,很容易在列表视图中添加太多字段,使其不清晰。另一种方法是添加字段,但可选择隐藏它们。这可以通过optional(可选)属性实现。
<field
name="absence_of_today"
widget="progressbar"
options="{
'current_value': 'absence_of_today',
'max_value': 'total_employee',
'editable': false,
}"
/>
最后,颜色代码有助于在视觉上强调记录。例如,在房地产模块中,我们希望以红色显示拒绝的报价,以绿色显示接受的报价。这可以通过 decoration-{$name} 属性来实现(参见Fields获得完整列表):
<tree decoration-success="is_partner==True">
<field name="name"/>
<field name="is_partner" invisible="1"/>
</tree>
我们模块的另一个有用的改进是能够根据居住区域进行有效的搜索。实际上,用户希望搜索“至少”给定区域的属性。期望用户想要找到一个精确的居住区域的属性是不现实的。总是可以进行自定义搜索,但这很不方便。
搜索视图**元素可以有一个filter_domain**,它覆盖为搜索给定字段而生成的域。在给定的域中,self表示用户输入的值。在下面的示例中,它用于搜索名称name和描述description字段。
<search string="Test">
<field name="description" string="Name and description"
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
</search>
在本教程的这一点上,我们已经看到了这样做的大多数概念。然而,没有一个单一的解决方案,如果你不知道从哪里开始,它仍然会令人困惑。我们将在练习中描述一个逐步的解决方案。通过查找oe_stat_button在Odoo代码库中查找一些示例总是很有用的。
下面的练习可能比前面的练习稍微困难一些,因为它假设您能够自己在源代码中搜索示例。如果你被困住了,附近可能有人可以帮助你;-)
练习介绍了相关领域的概念。理解它的最简单方法是将其视为计算字段的特定情况。描述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