第十二章 UI增强
我们的房地产模块越来越像一个商业应用了,我们创建了特定的视图,增加了动作按钮和相关的约束。 不过我们的用户界面依然有点丑陋。我们将给list视图增加一点颜色并且让一些字段和按钮有条件的隐藏。 例如,售出或者取消按钮在房产已经取消或者售出的时候就不要出现了因为它的状态已经是结束状态不允许改变了。
这一章覆盖了对视图改动的一小部分。别忘了阅读参考手册得到更多的完整的介绍。
Reference: 更多介绍参考这里 Views.
行内视图(Inline Views)
Note
Goal: 在这一小节, 在房产类型视图中增加一个特殊的关于房产的list视图
在房地产模块中我们增加报价列表,我们只是简单的增加了offer_ids:
<field name="offer_ids"/>
这个字段使用了estate.property.offer模型默认的视图,有些时候我们想定义一个特定的list视图,仅仅在form视图内部使用。 例如我们想在房产类型中显示该类型有哪些房产,而且,为了清晰起见,我们只想显示三个字段,名字,期望的售价和状态。
要做到这些,可以定义行内list视图,一个行内list视图直接在form视图内部定义,例如
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">
<tree>
<field name="field_1"/>
<field name="field_2"/>
</tree>
</field>
</form>
这里还有一个例子 here.
练习: 增加一个行内list视图
- 在estate.property.type模型中增加一个一对多字段
property_ids
- 在estate.property.type form视图中展示房产信息,完成本节的目标
<form string="Test">
<sheet>
<field name="name"/>
<notebook>
<page string="房屋信息">
<field name="property_ids">
<tree>
<field name="name"/>
<field name="expected_price"/>
<field name="state"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
小部件(Widgets)
参考:关于这个主题的文档在这里 Field Widgets.
Goal: 在本节最后, 房产状态用一种特殊的小部件来显示
显示四种状态: 新订单,报价已收到,报价已接受,售出
无论什么时候我们在模型中增加字段,我们几乎从不担心这些字段在用户界面上怎么显示。 例如,一个日期类型的字段会渲染成一个日期选择控件,一对多类型会自动渲染成一个列表。 Odoo会根据字段的类型自动的为我们选择合适的控件。
不过,有时候我们想指定一个字段的显示方式,这可以通过widget属性来实现。我们已经为tag_ids使用过它, 当时我们用了widget="many2many_tags"
属性,如果不用这属性,这个字段将会渲染成一个列表。
每种字段类型都有一组控件可以使用来微调它的显示,一些控件还有一些额外的选项,一个详细的列表可以在这里找到Field Widgets.
练习:
使用status bar 控件.
<header>
<field name="state" widget="statusbar" options="{'clickable': '1'}" statusbar_visible="0,1,2,3"/>
</header>
Tip: 这里 有个小案例here.
警告:
相同的字段在视图中多次出现,只允许出现一次。
列表顺序(List Order)
参考:关于这个主题的文档在这里 Models.
Note
Goal: 在本章结尾,所有的列表视图应该有个确定的顺序,房产类型可以手工排序
在前面的练习中,我们创建了几个list视图,但是,我们从来没有指定记录集默认按照什么顺序显示,这对很多商业应用来说很重要。例如,在房地产模块中我们希望最高的报价出现在列表的最上面。
Model
Odoo提供了几种方式来设置默认的排序。最常用的方法是在model中定义_order属性。这种方式,我们在检索数据的时候按照一个确定的排序,这在所有的视图中都会保持一致性包括使用条件查询的时候。 默认情况下是没有指定排序的,因为记录集以一种非确定性的方式被检索,这依赖于postgreSql。
_order 属性包含一个字段的字符用来排序,它将会转换成SQL的order语句,例如
from odoo import fields, models
class TestModel(models.Model):
_name = "test.model"
_description = "Test Model"
_order = "id desc"
description = fields.Char()
我们的记录集按照id降序排序,意味着最大的是第一个。
练习: 给模型增加排序
Model | Order |
---|---|
estate.property | Descending ID |
estate.property.offer | Descending Price |
estate.property.tag | Name |
estate.property.type | Name |
_order = "id desc"
_order = "price desc"
View
排序可以在模型级别进行。它的优点是在任何地方数据被检索的时候保持一致性。 然而,在视图内部可以通过default_order属性指定一个特殊的排序。(example).
<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>
Manual
当排序记录集的时候,模型和视图排序提供了灵活性。 但是依然有一种情况我们需要覆盖: 人工排序。 用户可能想按照商业逻辑来排序,例如,在我们的房地产模块中,我们希望按照房产类型手工来排序。 这确实有用,让使用最多的类型出现在列表最上方。如果我们的房地产中介主要卖的是houses,那么House出现在Apartment前面更方便。
要做到这些,一个sequence字段和handle控件联合使用,sequence字段必须是_order 属性的第一个字段。
练习: 增加手工排序
- 增加下列字段
Model | Field | Type |
---|---|---|
estate.property.type | Sequence | Integer |
- 给estate.property.type list视图增加sequence 字段,注意使用用正确的控件。
Tip: 下面是简单的例子
sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.")
_order = "sequence asc"
<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>
属性和选项(Attributes and options)
详细描述所有微调视图的可用特性是非常困难的。因此,我们只介绍经常使用的。
Form
Note
Goal: 本章结尾,房地产form视图:
- 有条件的显示字段和按钮
- 标签颜色
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F889jt7s-1685846824011)(https://www.odoo.com/documentation/16.0/_images/form.gif)]
在我们的房地产模块中,我们想修改某些字段的行为。 例如,我们不想在form视图中新建或者修改房产类型。 我们期望通过菜单做到这些。 我们还想让标签拥有颜色,为了增加这些自定义的行为,我们可以给控件添加options属性。
Exercise
Add widget options.
- 增加合适的选项不要在form视图中增加或者编辑房产类型,参考这里 Many2one widget documentation
<field name="property_type_id" options="{'no_create':True,'no_open':True}"/>
- Add the following field:
Model | Field | Type |
---|---|---|
estate.property.tag | Color | Integer |
给tag_ids字段增加合适的选项让他拥有颜色,这里有个例子
<field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}"/>
在第六章,我们看到了保留字段特殊的用途。
active: 自动筛选非活跃的数据
state: 通常和states属性一起使用来有条件的控制按钮的显示。
练习:
给按钮增加显示条件:
使用states属性有条件的显示头部的按钮,(注意当state改变的时候卖出和取消按钮是怎么变化的)
Tip: 别忘了在odoo xml文件中 搜索 states= 来寻找一些例子。
<button name="action_confirm" string="Confirm" type="object" icon="fa-check" states="0"/>
<button name="action_cancel" string="Cancel" type="object" icon="fa-times" states="0"/>
当state=0的时候按钮才显示。
更常用的是通过attrs属性根据其他字段的值设置一个字段 invisible
, readonly
or required属性。 注意:
invisible属性可以应用到其他的元素上比如button或者group
attrs属性是一个字典, 属性作为key,domain作为value, domain设置属性有效的条件。例如
<form>
<field name="description" attrs="{'invisible': [('is_partner', '=', False)]}"/>
<field name="is_partner" invisible="1"/>
</form>
这意味这description字段是隐藏的,当is_partner为false的时候。
注意: 用在attrs里的字段必须出现在view中,如果不需要展示给用户,我们可以通过设置invisible属性隐藏它。
Exercise
Use attrs
.
- 在estate.property form视图中当没有花园的时候,让花园面积和朝向隐藏起来。
<field name="garden" widget="boolean_toggle"/>
<field name="garden_area" attrs="{'invisible':[('garden','=',False)]}"/>
<field name="garden_orientation" attrs="{'invisible':[('garden','=',False)]}"/>
-
让“接受“和”拒绝” 按钮隐藏起来一旦报价状态发生改变
<button name="action_confirm" string="Confirm" type="object" icon="fa-check" states="0"/> <button name="action_cancel" string="Cancel" type="object" icon="fa-times" states="0"/>
-
不允许增加新的报价,当房产的状态是报价已经接受,卖出或者取消的时候 ,可以使用readonly 属性做到这些
<page string="订单信息">
<field name="offer_ids" attrs="{'readonly':[('state','>=','3')]}"/>
</page>
警告:
使用条件在视图上控制readonly很有用,可以阻止错误的数据输入。但是记住,它不能提供任何安全,服务器端没有做任何检查,因此可以通过RPC调用写入字段。
List
Note
Goal: 在本小节结尾, 房产和报价的list视图 应该用颜色来修饰 ,另外 报价和标签可以直接在 list视图中编辑,有效期默认隐藏。
当模型只有几个字段,可以直接通过list视图来编辑,没必要打开form视图,在房地产模块中,添加报价的时候没必要打开一个form视图,添加标签的时候也是这样,这可以通过editable属性来实现。
Exercise
让list视图可以编辑
让estate.property.offer
and estate.property.tag
list 视图可以编辑
<tree string="Channel" editable="bottom">
另一方面,当一个模型有大量的字段添加到list视图,看上去不够清晰。一种办法是增加这些字段,但是让他们有选择的隐藏起来,这可以通过optional属性来实现。
练习
让一个字段有选择的展示
让estate.property 的date_availability 字段在list 视图中有选择的展示,默认隐藏。
<field name="date_availability" optional="hide"/>
最后,颜色可以用来增强记录的可视化,例如,在房地产模块中,我们可以让被拒绝的报价显示红色,而让接受的报价显示绿色。这可以通过decoration-{$name}来实现, 这里有完整的介绍 Field Widgets
<tree decoration-success="is_partner==True">
<field name="name"/>
<field name="is_partner" invisible="1"/>
</tree>
当is_partner 为True是显示绿色。
练习:
增加一些装饰
On the estate.property
list view:
-
当收到报价时显示绿色
-
当报价被接受时显示绿色并加粗
-
房产被卖出时,muted
<tree string="Channel"
decoration-success="state=='1' or state=='2'"
decoration-bf="state=='2'"
decoration-danger="state=='4'"
decoration-muted="state == '3'">
On the estate.property.offer
list view:
- 报价被拒绝显示红色
- 报价被接受显示绿色
- 状态不应该再被看见
<tree string="Channel" editable="bottom" decoration-danger="state=='2'" decoration-success="state=='1'">
<field name="state" invisible="1"/>
Tips:
- 注意,在属性中用到的字段必须包含在视图中。
Search
参考:关于这个主题的文档在这里 Search and Search defaults.
Note
Goal: 在本节结尾,有效的房产将被默认显示,搜索使用面积将返回大于给定值的记录
最后但是同样重要的是,当搜索的时候我们希望有些微小的调整。 首先,当我们访问房产数据的时候有一个默认的叫“可用”的过滤器。 要实现这一点,我们需要使用search_default_{KaTeX parse error: Expected 'EOF', got '}' at position 5: name}̲ 动作上下文, {name} 是过滤器名称,这意味着我们要定义的过滤器默认被动作激活。
Here is an example of an action with its corresponding filter.
<record id="crm_opportunity_report_action" model="ir.actions.act_window">
。。。。
<field name="context">{'search_default_opportunity': True, 'search_default_current': True}</field>
<record id="crm_opportunity_report_view_search" model="ir.ui.view">
。。。
<filter name="opportunity" string="Opportunity" domain="[('type','=','opportunity')]" help="Show only opportunity"/>
练习:
增加默认的过滤器
让“有效”过滤器成为estate.property 动作的默认过滤器
在搜索视图中增加:
<filter string="有效房产" name="avaliable" domain="[('state', 'in', ['0','1','2'])]"/>
在 model="ir.actions.act_window 增加:
<field name="context">{'search_default_avaliable': True}</field>
另外一个有效的改进是我们的模块能够有效的搜索使用面积,实际上,一个用户希望搜索大于或等于给定值的使用面积,让用户找到一个精确的使用面积是不现实的
搜索视图的field元素可以有一个filter_domain, 它可以重写默认的domain。 在domain中,self代表用户输入的值,在下面的例子中,它用来搜索name或者描述
<search string="Test">
<field name="description" string="Name and description"
filter_domain="['|', ('name', 'ilike', self), ('description', 'ilike', self)]"/>
</search>
练习: 修改居住面积的搜索
给居住面积增加一个filter_domain的过滤器,过滤大于或等于给定值的房产。
搜索视图中增加:
<field name="living_area" string="面积不小于" filter_domain="[('living_area', '>=', self)]"/>
状态按钮(Stat Buttons)
Note
Goal: 在本小节结尾,房产类型的form视图上会出现一个状态按钮,当点击它的时候,显示所有该类房产的报价列表。
如果你已经使用过odoo的功能模块,你可能已经碰到过stat button了,这些按钮在form视图的右上角,并且快速连接到文档。 在我们的房地产模型中,我们将快速连接到给定房产类型的相关报价。
这篇教程写到这里,我们已经学习了很多概念去实现这个目的,然而,这不是一个单一的解决方案,它依然会让人困惑。如果你不知道从哪里开始,我们将一步一步的描述解决方案。 在odoo代码中查找例子永远是一个好办法,现在尝试查找一下“oe_stat_button”
接下来的练习可能有一点困难比起前面的练习,因为我们假定你已经能够从源代码中搜索一些例子。如果你碰到困难了,希望附近有人能够帮助你;-)
这个练习介绍了 Related fields的概念,理解这个概念最简单的办法是把它当做一种特殊的计算字段,注意下面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
每当客户名称改变的时候,描述也会改变。
练习
给房产类型增加一个stat button
<div class="oe_button_box" name="button_box">
<button string="报价列表"
name="%(estate.estate_property_offer_act_window)d"
type="action"
class="oe_stat_button"
icon="fa-refresh"><field name="offer_count"/> offers </button>
</div>
- 给estate.property.offer 增加一个字段property_type_id, 我们可以将它定义为一个相关字段property_id.property_type_id并且让他存储在数据库中。
通过这个字段,一个报价可以连接到房产类型,你可以将这个字段添加到报价的list视图中来确认它会工作。
- 在estate.property.type 中定义offer_ids字段,这是一个一对多字段作为上一步定义的字段的逆操作
- 在estate.property.type 中增加 offer_count字段, 这是一个计算字段,计算给定房产类型的的报价数量(利用offer_ids来做,是否可以好用len函数?)
到这一步,我们已经有了关于某种房产类型有多少报价的必要信息。 如果还有疑问,将offer_ids和offer_count直接添加到视图中, 下一步我们将展示通过点击按钮来展示对应的列表。
- 在estate.property.type form视图中创建一个stat button 指向estate.property.offer 动作,这意味这,你应该使用type=“action”属性。(可以到这里在复习一下 Chapter 10: Ready For Some Action? )
到这里,点击stat button应该会显示所有的报价,我们还需要过滤报价。
- 在
estate.property.offer
action中,增加一个domain, 定义property_type_id = active_id (=当前记录,这里有个例子here is an example)
<field name="domain">[('event_id', '=', active_id)]</field>
看上去还好吗,如果不是,别担心,下一章不需要stat button 了;-)