第四章:视图层开发——让界面真正好用起来

说实话,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" 表示按月分组。还有 dayweekquarteryear。我建议根据数据量选择合适的时间粒度,数据太多时按月,数据少时按天。

个人经验: 图表视图的 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 指定操作方式(替换、追加、插入等)。

注意: QWeb 模板的调试比较麻烦。我建议在开发时开启开发者模式(设置 -> 激活开发者模式),然后通过「查看视图」功能检查模板渲染结果。我曾经花了一下午找 bug,最后发现是模板里少写了一个 t- 前缀。

小结

这一章的内容,说白了就是让界面更好用、更高效。表单视图要注重布局和动态控制,树形视图要善用装饰和搜索,看板视图适合任务管理,图表视图用于数据统计,QWeb 则是所有自定义视图的基础。

我个人觉得,视图层开发最考验的是「用户思维」。你想想看,用户每天面对这些界面,他们需要什么?是快速找到数据,还是方便地编辑?是直观的图表,还是灵活的筛选?想清楚这些,你的视图开发就成功了一半。

下一章,咱们聊聊业务逻辑层——模型与 API 的深度应用。到时候我会分享一些我在项目中踩过的坑,嗯,保证让你少走弯路。