第四章:视图层开发——让界面真正好用起来
说实话,Odoo 的视图层是我最喜欢折腾的部分。为什么?因为后端逻辑写得再好,用户看不到。但视图不一样——你改一个字段位置,用户马上就能感受到。我刚开始接触 Odoo 时,总觉得视图就是拖拖拽拽,后来才发现,真正的高手都在视图里藏了不少门道。
这一章,咱们就聊聊表单视图、树形视图、搜索视图、看板视图、图表视图,还有 QWeb 模板。嗯,内容不少,但都是实战中高频使用的。
4.1 表单视图(Form View)高级用法
表单视图是用户每天面对最多的界面。说白了,它就是数据的「编辑窗口」。但很多人只用了皮毛——放几个字段,加个按钮,完事。其实,表单视图能做的远不止这些。
4.1.1 动态隐藏与只读
我在项目中遇到过这样一个需求:订单状态为「已审核」时,金额字段不能再修改。你可能会想,这简单,加个 readonly 不就行了?但问题是,审核前要能改,审核后不能改。这就需要用 attrs 了。
<field name="amount_total" attrs="{'readonly': [('state', '=', 'approved')], 'invisible': [('state', '=', 'draft')]}" />
这里要注意:attrs 里的条件是基于当前表单的数据。如果字段不在表单上,条件可能不生效。嗯,我曾经踩过这个坑——在 tree 视图里用 attrs,结果死活不生效。后来才发现,树形视图不支持 attrs,得用 decoration-* 系列。
invisible 配合 context 传参。我个人习惯把复杂判断写在模型方法里,然后在视图中调用。
4.1.2 分组与页签
字段一多,表单就乱。你想想看,一个采购订单,基本信息、明细行、财务信息、物流信息全堆在一起,用户不骂娘才怪。
我的做法是:用 <group> 做基础分组,用 <notebook> 做页签切换。
<form>
<sheet>
<group>
<group>
<field name="partner_id"/>
<field name="date_order"/>
</group>
<group>
<field name="user_id"/>
<field name="state"/>
</group>
</group>
<notebook>
<page string="订单明细">
<field name="order_line"/>
</page>
<page string="财务信息">
<group>
<field name="amount_untaxed"/>
<field name="amount_tax"/>
<field name="amount_total"/>
</group>
</page>
</notebook>
</sheet>
</form>
这里有个细节:<sheet> 标签是 Odoo 的「画布」,它让表单看起来更干净。如果你不用 <sheet>,字段会直接贴在背景上,视觉效果差很多。
4.1.3 按钮与动作
表单上的按钮,不只是「保存」「取消」。你可以自定义按钮,触发服务端方法。
<header>
<button name="action_confirm" string="确认订单" type="object" class="btn-primary"/>
<button name="action_cancel" string="取消" type="object" class="btn-secondary"/>
</header>
注意 type="object" 表示调用当前模型的方法。如果你需要跳转到其他视图,可以用 type="action" 配合 context 传参。
confirm="确定要执行吗?" 属性,结果用户点了按钮后弹了两次确认框。后来发现是按钮的 type 写成了 action,触发了两次确认。嗯,细节决定成败。
4.2 树形视图(Tree View)与搜索视图(Search View)
树形视图,说白了就是列表。用户每天看的最多的就是它。但很多人只把它当表格用,忽略了它的高级功能。
4.2.1 树形视图的装饰与排序
我习惯用 decoration-* 给行加颜色。比如,逾期未付款的订单标红,已完成的标绿。
<tree decoration-danger="state == 'overdue'" decoration-success="state == 'done'">
<field name="name"/>
<field name="partner_id"/>
<field name="date_order"/>
<field name="state"/>
</tree>
这里 decoration-danger 对应红色,decoration-success 对应绿色。还有 decoration-warning(黄色)、decoration-info(蓝色)、decoration-muted(灰色)。
排序方面,默认是按 sequence 字段排。如果没有,就按 id 排。我建议每个模型都加一个 sequence 字段,方便手动排序。
4.2.2 搜索视图的进阶用法
搜索视图不只是放几个字段。你可以自定义过滤器、分组,甚至上下文。
<search>
<field name="name"/>
<field name="partner_id"/>
<filter name="my_orders" string="我的订单" domain="[('user_id', '=', uid)]"/>
<filter name="today" string="今日订单" domain="[('date_order', '=', context_today().strftime('%Y-%m-%d'))]"/>
<group expand="0" string="分组">
<filter name="groupby_state" string="按状态" context="{'group_by': 'state'}"/>
<filter name="groupby_partner" string="按客户" context="{'group_by': 'partner_id'}"/>
</group>
</search>
这里 uid 是当前用户 ID,context_today() 是今天的日期。你想想看,用户每天打开系统,直接点「今日订单」就能看到当天的数据,多方便。
domain 支持 Python 表达式,但要注意性能。如果数据量很大,尽量用数据库层面的过滤,而不是在视图里写复杂逻辑。
4.3 看板视图(Kanban View)与图表视图(Graph View)
看板视图,说白了就是卡片式布局。它特别适合任务管理、销售管道这类场景。图表视图则适合数据统计。
4.3.1 看板视图的结构
看板视图的核心是 <kanban> 标签,里面嵌套 <templates> 定义卡片样式。
<kanban>
<field name="name"/>
<field name="state"/>
<field name="user_id"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="oe_kanban_content">
<strong><t t-esc="record.name.raw_value"/></strong>
<p>负责人:<t t-esc="record.user_id.raw_value"/></p>
<p>状态:<t t-esc="record.state.raw_value"/></p>
</div>
</div>
</t>
</templates>
</kanban>
这里 record.name.raw_value 是获取字段的原始值。如果你需要格式化,可以用 record.name.value。嗯,这个区别我一开始也搞混过。
4.3.2 图表视图的配置
图表视图支持柱状图、折线图、饼图。配置起来很简单:
<graph type="bar">
<field name="date_order" interval="month"/>
<field name="amount_total" type="measure"/>
</graph>
interval="month" 表示按月分组。还有 day、week、quarter、year。我建议根据数据量选择合适的时间粒度,数据太多时按月,数据少时按天。
type 属性,我一般用 bar 做对比,用 line 看趋势,用 pie 看占比。但饼图不要超过 5 个分类,否则根本看不清。
4.4 QWeb 模板基础
QWeb 是 Odoo 的模板引擎。说白了,它就是在 HTML 里嵌入 Python 逻辑。看板视图、报表、门户页面,都用到了 QWeb。
4.4.1 基本语法
QWeb 的指令以 t- 开头。最常用的几个:
| 指令 | 作用 | 示例 |
|---|---|---|
t-esc |
输出文本(自动转义) | <t t-esc="record.name"/> |
t-raw |
输出 HTML(不转义) | <t t-raw="record.description"/> |
t-if |
条件判断 | <t t-if="record.state == 'done'"> |
t-foreach |
循环 | <t t-foreach="records" t-as="r"> |
t-set |
设置变量 | <t t-set="total" t-value="0"/> |
举个例子,假设你要在卡片里显示一个列表:
<t t-foreach="record.order_line" t-as="line">
<div>
<t t-esc="line.product_id.name"/> -
<t t-esc="line.quantity"/> x
<t t-esc="line.price_unit"/>
</div>
</t>
4.4.2 继承与复用
QWeb 支持模板继承。你可以定义一个基础模板,然后在子模板里覆盖特定部分。
<!-- 基础模板 -->
<t t-name="my_module.base_template">
<div>
<t t-set="header">默认标题</t>
<t t-raw="header"/>
<t t-raw="0"/>
</div>
</t>
<!-- 子模板 -->
<t t-extend="my_module.base_template">
<t t-jquery="div" t-operation="replace">
<div>新的内容</div>
</t>
</t>
这里 t-jquery 用 jQuery 选择器定位元素,t-operation 指定操作方式(替换、追加、插入等)。
t- 前缀。
小结
这一章的内容,说白了就是让界面更好用、更高效。表单视图要注重布局和动态控制,树形视图要善用装饰和搜索,看板视图适合任务管理,图表视图用于数据统计,QWeb 则是所有自定义视图的基础。
我个人觉得,视图层开发最考验的是「用户思维」。你想想看,用户每天面对这些界面,他们需要什么?是快速找到数据,还是方便地编辑?是直观的图表,还是灵活的筛选?想清楚这些,你的视图开发就成功了一半。
下一章,咱们聊聊业务逻辑层——模型与 API 的深度应用。到时候我会分享一些我在项目中踩过的坑,嗯,保证让你少走弯路。