第八章 视图(Views)

篇别:第一篇 基础知识

本章学习目标

  • 能独立编写 form / list / search 及常用分析视图,并理解 arch 与模型字段 的对应关系。
  • 掌握 看板 card 化pivot/graph视图继承与 priority 等升级敏感点。
  • 能说明 grid、settings、activity 等视图的 依赖前提(mixin、模块、字段)。
  • 具备 xpath 调试 与「升级后视图报错」的基本排查能力。

导读:视图是什么

视图是存储在 ir.ui.view 中的 XML 架构(arch),描述 字段布局、按钮、子视图、装饰与属性。同一模型可有 多套视图(不同 xmlid/类型/priority),由 ir.actions.act_windowview_modeview_id 等决定默认打开哪一套。


8.1 表单视图(Form)

8.1.1 知识要点

  • 根节点 <form>;常用子结构:<header>(按钮+状态)、<sheet>(主内容)、<chatter>(若模型继承 mail)。
  • <group>:两列表格布局;<notebook> / <page> 分页签。
  • 字段属性:readonlyrequiredinvisibleplaceholderoptionswidget(见第五章)。
  • invisible 表达式:可用 attrs 旧式或 Odoo 17+ 域表达式风格(以项目版本为准)。

8.1.2 案例

<form string="图书">
  <header>
    <button name="action_confirm" type="object" string="确认" class="btn-primary"
            invisible="state != 'draft'"/>
    <field name="state" widget="statusbar" statusbar_visible="draft,published,archived"/>
  </header>
  <sheet>
    <div class="oe_title">
      <label for="name"/>
      <h1><field name="name" placeholder="书名"/></h1>
    </div>
    <group>
      <group string="基本信息">
        <field name="isbn"/>
        <field name="category_id"/>
      </group>
      <group string="馆藏">
        <field name="copy_count"/>
      </group>
    </group>
    <notebook>
      <page string="借阅记录" name="loans">
        <field name="loan_line_ids">
          <list>
            <field name="partner_id"/>
            <field name="loan_date"/>
          </list>
        </field>
      </page>
    </notebook>
  </sheet>
</form>

8.1.3 截图占位

图 8-1 标准表单:header + sheet + notebook

8.1.4 本节练习

  1. 实操:为图书表单增加第二个 page「备注」,放 note 文本字段。
  2. 简答buttoninvisiblestates 属性(若仍可用)相比,推荐哪种维护方式?
  3. 判断:表单里没有写的字段,用户一定不能改。( )(提示:其他视图或 RPC)

参考答案提示:3. 错。


8.2 列表视图(List/Tree)

8.2.1 知识要点

  • 根节点 <list>(历史名 tree 仍可能被旧代码使用)。
  • editable="top|bottom":行内编辑;注意 并发写onchange 性能
  • default_orderlimitcreatedeletemulti_edit
  • decoration-*:基于行数据 CSS 类;与 colors(旧)区分。

8.2.2 案例

<list string="图书" editable="bottom" default_order="sequence,name"
      decoration-muted="state=='archived'"
      decoration-danger="not active">
  <field name="sequence" widget="handle"/>
  <field name="name"/>
  <field name="state"/>
  <field name="copy_count" sum="合计册数"/>
</list>

8.2.3 截图占位

图 8-2 可编辑列表与 decoration 效果

8.2.4 本节练习

  1. 简答editable 打开时,对 记录规则批量导入 各有什么影响?
  2. 实操:开启 multi_edit 后多选两行批量改某一字段,观察一次 write 还是多次。

8.3 看板视图(Kanban)(补充)

8.3.1 知识要点

Odoo 19 推荐 <kanban> 内使用 <card> 等结构化标签,替代旧 <kanban-box>;具体标签名以官方模块 crmproject 为准。

看板常配合 default_group_by(在 action context)按 Many2one / Selection 分列。

8.3.2 案例(结构示意)

<kanban default_group_by="category_id" class="o_kanban_mobile">
  <field name="id"/>
  <field name="name"/>
  <field name="state"/>
  <templates>
    <t t-name="card">
      <div class="oe_kanban_card oe_kanban_global_click">
        <field name="name"/>
        <field name="state" widget="badge"/>
      </div>
    </t>
  </templates>
</kanban>

实际 t-namecard 写法请复制当前版本 project 模块模板。

8.3.3 截图占位

图 8-3 看板列与卡片

8.3.4 本节练习

  1. 实操:将旧模块 kanban-box 按官方示例改为新结构并 -u
  2. 简答:看板 quick_create 依赖什么?

8.4 搜索视图(Search)

8.4.1 知识要点

  • <field name="..."/>:可搜索列;可设 filter_domain
  • <filter>:预置 domain;namecontextsearch_default_xxx 使用。
  • <group> + context="{'group_by': ...}":分组。
  • <searchpanel>:侧栏筛选(依赖字段与模块)。

8.4.2 案例

<search>
  <field name="name" string="书名/ISBN" filter_domain="['|',('name','ilike',self),('isbn','ilike',self)]"/>
  <separator/>
  <filter name="draft" string="草稿" domain="[('state','=','draft')]"/>
  <filter name="published" string="已出版" domain="[('state','=','published')]"/>
  <group expand="0" string="分组">
    <filter name="grp_category" string="分类" context="{'group_by':'category_id'}"/>
    <filter name="grp_state" string="状态" context="{'group_by':'state'}"/>
  </group>
</search>

动作中默认勾选草稿

<field name="context">{'search_default_draft': 1}</field>

8.4.3 截图占位

图 8-4 搜索栏与过滤器

8.4.4 本节练习

  1. 实操:增加 search_default_publishedsearchpanel(若字段适合)。
  2. 简答filter_domain 中的 self 指什么?

参考答案提示:2. 当前搜索框输入值。


8.5 日历视图(Calendar)

8.5.1 知识要点

date_start 必填;date_stopdate_delayall_daycolormode(month/week/day)。拖拽改时间会 write 对应字段。

8.5.2 案例

<calendar string="借阅日历" date_start="start_date" date_stop="end_date"
          color="partner_id" mode="month" quick_create="0">
  <field name="name"/>
  <field name="partner_id"/>
</calendar>

8.5.3 截图占位

图 8-5 月视图

8.5.4 本节练习

  1. 实操:定义冲突检测:end_date < start_date@api.constrains 拦截。
  2. 简答:无 write 权限的用户拖拽事件会怎样?

8.6 数据透视表(Pivot)(补充)

8.6.1 知识要点

行/列维度 + 度量;底层与 read_group 同源思路;Odoo 19 可能对 GROUPING SETS 等有增强,以文档为准。

适合 财务、销售分析;大数据量注意 超时与预聚合

8.6.2 案例

在带 sale.report 类模型的模块中打开 pivot,切换度量与「翻转行/列」。

8.6.3 截图占位

图 8-6 Pivot 行/列/度量

8.6.4 本节练习

  1. 简答:pivot 与 graph 的选型差异?
  2. 拓展:用 read_group 在 Python 中复现 pivot 某一切片。

8.7 图表视图(Graph)

8.7.1 知识要点

type="bar|line|pie"stackedordermeasure 字段类型需可聚合。

8.7.2 案例

<graph string="借阅统计" type="bar" stacked="True">
  <field name="category_id" type="row"/>
  <field name="loan_count" type="measure"/>
</graph>

8.7.3 截图占位

图 8-7 柱状图

8.7.4 本节练习

  1. 实操:将 type 改为 pie 观察度量限制。
  2. 判断:graph 可直接编辑数据。( )

8.8 活动视图(Activity)

8.8.1 知识要点

模型需 _inherit mail.activity.mixin;视图中展示待办时间线。常与 calendar 联用。

8.8.2 案例

参考 sale.orderactivity_idsactivity_exception_decoration 等字段(以版本为准)。

8.8.3 截图占位

图 8-8 活动视图

8.8.4 本节练习

  1. 实操:在图书上 activity_schedule 一条下周待办并在活动视图查看。
  2. 简答:无 mixin 能否硬写 activity 视图?

参考答案提示:2. 不应;缺模型能力与字段。


8.9 队列分析(Cohort)

8.9.1 知识要点

首次事件日期 分桶,观察后续周期留存;需清晰定义 cohort 日期字段留存度量

8.9.2 案例

在分析应用中切换 周/月 粒度,与 pivot 同一指标对比阅读体验。

8.9.3 截图占位

图 8-9 Cohort 热力图

8.9.4 本节练习

  1. 简答:Cohort 与 pivot 在「时间维度」上的根本不同?
  2. 情景:B2B 长周期成交,cohort 分桶过细有何问题?

8.10 甘特图(Gantt)

8.10.1 知识要点

依赖 项目/计划 类模块与企业版或特定扩展;字段:date_startdate_stopprogressdepends(视模块)。

8.10.2 案例

创建两条依赖任务,观察连线(环境支持时)。

8.10.3 截图占位

图 8-10 甘特

8.10.4 本节练习

  1. 拓展:社区版无甘特时,用 calendar + list + graph 如何凑合项目管理?

8.11 地图视图(Map)

8.11.1 知识要点

地址或经纬度 字段、地理编码服务、对应 地图模块 与合法 API 密钥(合规存储在系统参数)。

8.11.2 案例

为伙伴维护完整地址,地图聚类显示。

8.11.3 截图占位

图 8-11 地图

8.11.4 本节练习

  1. 实操:列出启用地图的 配置清单(参数名可空,占位即可)。
  2. 简答:地图视图的隐私与 GDPR 注意点?

8.12 网格视图(Grid)(新增)

8.12.1 知识要点

二维矩阵(常见:人 × 日)批量录入;依赖 timesheet/grid 类模块与约定字段。

8.12.2 案例

对比 grid 单元格list editable 在录入 20×7 格数据时的效率。

8.12.3 截图占位

图 8-12 Grid

8.12.4 本节练习

  1. 简答:Grid 与 pivot 的「只读分析」有何不同?

8.13 设置视图(Settings)(新增)

8.13.1 知识要点

res.config.settings TransientModel;字段 config_parameter 持久化到 ir.config_parameter;布尔常用 implied_group 启模块。

8.13.2 案例

class ResConfigSettings(models.TransientModel):
    _inherit = 'res.config.settings'
    library_max_loan_days = fields.Integer(
        string='最大借阅天数',
        config_parameter='library.max_loan_days',
        default=30,
    )

8.13.3 截图占位

图 8-13 设置页

8.13.4 本节练习

  1. 实操:借阅确认时读取 ir.config_parameter 校验天数。
  2. 简答:为何设置类字段多放在 TransientModel?

8.14 视图继承(新增)

8.14.1 知识要点

  • inherit_id 指向父视图;arch 内用 xpath 定位节点。
  • positioninsideafterbeforereplaceattributes
  • priority:数值越小越先应用(以官方说明为准);多模块改同一视图时 协调 priority 避免互相覆盖。
  • 稳定锚点:优先 name 属性、data-key(若有),避免脆弱路径。

8.14.2 案例

<record id="view_partner_form_inherit_library" model="ir.ui.view">
  <field name="name">res.partner.form.inherit.library</field>
  <field name="model">res.partner</field>
  <field name="inherit_id" ref="base.view_partner_form"/>
  <field name="priority">20</field>
  <field name="arch" type="xml">
    <xpath expr="//field[@name='function']" position="after">
      <field name="is_library_member"/>
    </xpath>
    <xpath expr="//sheet" position="inside">
      <div class="alert alert-info" invisible="not is_library_member">
        该联系人为图书馆注册读者。
      </div>
    </xpath>
  </field>
</record>

8.14.3 截图占位

图 8-14 继承视图列表与最终合并 arch

8.14.4 本节练习

  1. 实操:用 position="attributes" 给某字段加 readonly
  2. 简答:大段 replace 的风险?
  3. 故障:升级报 xpath 找不到节点——列出 5 步 排查。

参考答案提示:2. 上游结构变更即碎。3. 激活开发者模式看合并 arch、查父视图是否变、priority、模块是否加载、XML id 是否正确。


本章综合练习

  1. library.book 配置 list + form + search + kanban 四套视图,并写入 一个 act_window
  2. 继承:在 两个 独立模块中继承同一表单,通过 priority 控制字段顺序并截图合并结果。
  3. 性能:列表展示 10 万 条时,仅靠视图层能做什么?ORM/DB 层需做什么?
  4. 文档化:为你团队写「视图评审 checklist」5 条(字段权限、xpath、移动端、翻译、无障碍任选角度)。

本章对应白皮书目录:第八章 视图。