第十七章 SmartButton 设计

篇别:第三篇 数据库与数据管理

本章学习目标

  • 能使用 计算字段 + statinfo 实现 统计型 Smart Button
  • 能对 多记录批量统计 使用 read_group 优化,避免 N 次 search_count
  • 能配置 type="action" 跳转并携带 domain、context、group_by
  • 能权衡 store=True实时计算 对性能、搜索、缓存的影响。

导读:Smart Button 的用户价值

表单右上角 按钮盒(button box) 中的 统计按钮 让用户 一眼看到关联数量一键跳入过滤后的列表/看板。实现上 = 展示用计算字段 + 窗口动作 + 可选图标与样式


17.1 关联计数

17.1.1 知识要点

  • 计算字段Integercomputesearch_count聚合查询;常 store=False 保证实时性,或 store=True 降低打开表单频率。
  • 模板<div class="oe_button_box"><button type="action"> + <field widget="statinfo"/>
  • 性能单张表单打开时,若对 多个 statsearch_count,会多次 SQL;可合并 read_group单 SQL

17.1.2 案例

案例 A:基础写法(易读,量大时慎用)

loan_count = fields.Integer(compute='_compute_loan_count', string='借阅次数')

@api.depends('partner_id')
def _compute_loan_count(self):
    Loan = self.env['library.loan']
    for partner in self:
        partner.loan_count = Loan.search_count([('partner_id', '=', partner.id)])

案例 B:批量 read_group(多 partner 一次查询)

@api.depends('partner_id')
def _compute_loan_count(self):
    partners = self.filtered('id')
    if not partners:
        return
    Loan = self.env['library.loan']
    data = Loan.read_group(
        [('partner_id', 'in', partners.ids)],
        ['partner_id'],
        ['partner_id'],
    )
    count_by_pid = {}
    for d in data:
        pid = d['partner_id'][0]
        # 计数字段名因版本而异,常见为 __count 或 partner_id_count
        count_by_pid[pid] = d.get('__count', d.get('partner_id_count', 0))
    for p in self:
        p.loan_count = count_by_pid.get(p.id, 0)

字段名 partner_id_countread_group 返回为准,不同 Odoo 版本可能为 __count 等,需打印 data 调试。

17.1.3 截图占位

图 17-1 联系人表单 statinfo

图 17-1b 按钮盒内多个 smart button 排列

17.1.4 本节练习

  1. 实操:将案例 A 改为案例 B 思路(打印 read_group 结果校对)。
  2. 简答read_group 返回条数上限与 分页 注意点?
  3. 判断store=True 的计算字段一定比 store=False 快。( )

参考答案提示:3. 错,依赖重算成本与索引。


17.2 跳转动作

17.2.1 知识要点

  • type="action"name 使用 %(module.xml_id)d 引用 ir.actions.act_window
  • 上下文default_partner_idsearch_default_*active_id
  • Domain:也可在 Python 返回的 dict 里动态修改 action(Smart button 调 object 方法时常用)。

17.2.2 案例

案例 A:XML 静态绑定

<div class="oe_button_box" name="button_box">
  <button type="action" name="%(library.action_loan_from_partner)d"
          class="oe_stat_button" icon="fa-book">
    <field name="loan_count" widget="statinfo" string="借阅"/>
  </button>
</div>

预置动作 action_loan_from_partner 中:

<field name="domain">[('partner_id','=',active_id)]</field>
<field name="context">{'default_partner_id': active_id, 'search_default_open': 1}</field>

注意:active_id 在客户端求值;若动作仅用于 smart button,可写死 domain 用 Python 方法返回更安全。

案例 B:Python 返回动作(动态 domain)

def action_view_loans(self):
    self.ensure_one()
    action = self.env['ir.actions.act_window']._for_xml_id('library.action_library_loan')
    action['domain'] = [('partner_id', '=', self.id)]
    action['context'] = {'default_partner_id': self.id, 'group_by': 'state'}
    return action

表单按钮:type="object", name="action_view_loans"

17.2.3 截图占位

图 17-2 点击后列表已过滤

17.2.4 本节练习

  1. 实操:跳转列表默认 state 分组contextactiongroup_by)。
  2. 简答type="action"type="object" 返回 action 的选型?
  3. 情景:计数为 0 时是否隐藏按钮?如何实现?

参考答案提示:2. XML 简单动作用 action;复杂 domain 用 object。3. invisible 表达式或 attrs。


17.3 图书场景:双统计按钮设计

17.3.1 知识要点

  • 当前借出domain=[('state','=','open')]
  • 历史借阅:全部或 state in ('returned','lost')
  • 两个 compute 字段或 一个 read_group 多度量(视模型设计而定)。

17.3.2 本节练习

  1. 实操:在 library.book 上增加「在借人数」「累计借阅次数」两个 stat(定义清晰业务含义)。
  2. 简答:「在借人数」应对 借阅行 去重用户还是计数行?

本章综合练习

  1. 设计:图书表单:当前借出册数(未还)+ 历史借阅次数;画出字段与关系。
  2. 性能:对比 store=True statread_group 每次打开 的适用场景各三例。
  3. 无障碍:stat 按钮仅图标时,如何改善 屏幕阅读器 体验(简答)?
  4. 拓展:阅读 sale.order 中 smart button 实现,摘录一种 _for_xml_id 用法

本章对应白皮书目录:第十七章 SmartButton 设计。