当前位置: 首页 > java >正文

Odoo 开发:揭秘表单视图中“添加行”按钮的出现条件

Odoo 开发:揭秘表单视图中“添加行”按钮的出现条件

作为一名 Odoo 开发者,你一定在表单视图中看到过那种非常方便的列表区域,比如销售订单中的订单行、发票中的发票行。这些列表通常可以让你直接在当前表单页面上添加、编辑和删除记录,而它们的底部或顶部,就有一个醒目的“添加行”按钮或链接。

你可能好奇:这个“添加行”按钮是什么时候出现的?它受什么控制?我们这次研究就是为了揭开这个秘密。

通过查阅 Odoo 的前端源码(主要集中在 Web 模块),我们发现这个按钮的出现不是魔法,而是由 XML 视图定义中的特定“指令”和 Odoo 前端框架根据这些指令进行的“判断”共同决定的。

简单来说,这个“添加行”按钮的出现,主要受控于以下三个核心条件:

  1. 它必须是一个关联字段 (one2manymany2many) 的视图。
  2. 这个关联字段在表单视图中嵌套的列表 (<tree><list>) 必须被标记为可编辑 (editable)。
  3. 当前用户必须有权限创建这个关联模型的新记录,并且视图定义没有明确禁止创建。

让我们一步步看看这些条件如何在代码中体现。

1. XML 视图:前端的“指令”

一切的起点都在 XML 视图文件里。当你定义一个表单视图时,你会使用 <field> 标签来显示关联字段。在这个 <field> 标签内部,你可以嵌套定义这个关联字段应该以什么样的视图形式显示,通常是列表视图 (<tree><list>)。

示例位置:
你可以在 Odoo 模块的 views 目录下找到这样的文件,比如 addons/sale/views/sale_order_views.xml 定义了销售订单的视图。

关键的 XML 指令:editable 属性

在嵌套的 <tree><list> 标签上,有一个非常重要的属性控制着是否可以在表单视图中直接编辑列表内容,进而影响“添加行”按钮的出现:editable

<field name="order_line"> <!-- 这是一个 one2many 字段 --><tree string="Sales Order Lines" editable="bottom"> <!-- 注意这里! --><!-- ... 这里是列表视图的列定义 ... --></tree>
</field>
  • editable="bottom":这意味着这个列表可以在底部直接编辑,并且“添加行”区域会出现在列表的底部。
  • editable="top":意味着列表可以在顶部直接编辑,“添加行”区域会出现在顶部。
  • 如果 <tree><list> 标签上没有 editable 属性,或者设置为 editable="false",那么这个列表就不能直接在表单里编辑,通常点击行会打开一个弹出表单,并且“添加行”按钮也不会出现。

这个 editable 属性是 XML 中最直接告诉 Odoo 前端“这个列表是可以添加新行的”的指令。

其他的 XML 影响因素:创建权限

除了 editable,XML 视图中关于创建行为的定义也会影响按钮的出现。这通常通过以下方式体现:

  • <tree><list> 标签上设置 create="false"
  • <field> 标签内部使用 <control> 标签,并且里面没有 <create/> 或一个执行创建动作的 <button>

这些设置会被 Odoo 的视图解析器读取,并转化为前端组件可以理解的配置信息,特别是影响到用户是否有“创建”或“关联”新记录的权限标志。

2. JavaScript 前端框架:指令的“执行者”和“判断者”

Odoo 的 Web 客户端(前端)框架负责读取这些 XML 定义,并根据定义动态地构建和管理界面。实现列表视图渲染的核心组件是 ListRenderer

关键文件:
addons/web/static/src/views/list/list_renderer.js

ListRenderer 组件在渲染列表时,会判断是否需要显示“添加行”区域。这个判断逻辑体现在它的 QWeb 模板 (list_renderer.xml) 中使用 t-if 指令的部分。

QWeb 模板中的判断:
你之前找到的 list_renderer.xml 文件中,渲染“添加行”的 <tr> 标签上有一个 t-if="displayRowCreates" 或一个包含 props.editablecanCreate 的更复杂的 t-if。这表明是否渲染取决于 ListRenderer 组件实例的 displayRowCreates 属性或对 props.editablecanCreate 的判断结果。

ListRenderer 中的判断逻辑:displayRowCreates, isX2Many, canCreate

list_renderer.js 这个文件中,你可以找到控制这些 t-if 条件的 JavaScript 代码:

  • displayRowCreates Getter:

    get displayRowCreates() {return this.isX2Many && this.canCreate;
    }
    

    这个 getter 直接决定了 t-if="displayRowCreates" 的值。它告诉你,“添加行”区域只在 this.isX2Many(是关联字段的嵌入列表)和 this.canCreate(允许创建)同时为真时才显示。

  • isX2Many Getter:

    get isX2Many() {return this.activeActions.type !== "view";
    }
    

    这个 getter 判断当前的 ListRenderer 是否被用作一个关联字段的子视图(而不是顶级的独立列表视图)。它是通过检查 this.activeActionstype 属性的值来实现的。当 ListRenderer 渲染嵌套列表时,这个 type 通常不是 "view"

  • canCreate Getter:

    get canCreate() {return "link" in this.activeActions ? this.activeActions.link : this.activeActions.create;
    }
    

    这个 getter 判断是否允许创建新记录。它查看 this.activeActions 对象中是否有 linkcreate 权限标志。这些标志是前端从 XML 解析器(如 list_arch_parser.js)和 Odoo 的访问控制权限那里获取的最终结果。如果 XML 中设置了 create="false" 或者用户没有创建权限,那么 this.activeActions.create 就会是 false,导致 canCreatefalse

  • props.editable 属性:
    这个值直接从 ListRenderer 的父组件 (ListControllerX2ManyField) 传递进来。父组件在创建 ListRenderer 实例时,会根据 XML 中 <tree editable="..."> 属性的值来设置 editable 这个 prop。

总结:按钮出现的条件

所以,这个“添加行”按钮/区域是否会出现在 Odoo 表单视图中的嵌入式列表底部(或顶部),最终取决于以下条件的组合判断:

  1. 视图类型: 列表必须是嵌套在表单视图中,用于显示 one2manymany2many 字段的。 (isX2Many 为 true)
  2. XML 可编辑属性: 嵌套的 <tree><list> 标签必须明确设置 editable="top"editable="bottom"。 (props.editable 不为 false)
  3. 创建权限/设置: Odoo 的访问控制权限必须允许当前用户创建相关模型的新记录,并且 XML 视图定义(如 <tree create="false"><control> 的配置)没有明确阻止创建。 (canCreate 为 true)

只有这三个条件都满足时,ListRenderer 的 QWeb 模板才会渲染出那个包含“Add a line”链接的 <tr> 元素,你才能在界面上看到并使用那个方便的添加行按钮。

代码位置回顾:

  • XML 视图定义: 模块的 views/*.xml 文件中,<field name="your_x2many_field"> 内部的 <tree editable="..."><control> 标签。
  • QWeb 模板定义: addons/web/static/src/views/list/list_renderer.xml 文件中,带有 o_field_x2many_list_row_addo_group_field_row_add 类的 <td> 所在的 <tr> 标签,以及它们上面的 t-if 条件。
  • 前端逻辑判断: addons/web/static/src/views/list/list_renderer.js 文件中,ListRenderer 组件的 displayRowCreates, isX2Many, canCreate getter 的定义。这些 getter 依赖于从父组件(如 ListController)接收到的 props.editableprops.activeActions

通过研究 Odoo 源码,我们不仅找到了控制按钮出现的直接代码,更理解了 Odoo 前端如何通过解析 XML 定义、组件间的属性传递以及内部的状态判断来动态构建复杂的用户界面。

另外的问题

editable属性不生效

你的发现:

  1. 设置 editable="false" 时,“添加行”按钮 依然存在
  2. 设置 create="false" 时,“添加行”按钮 消失

这确实与我们之前博客中强调 editable 是最直接控制因素的结论有所出入。出现这个差异的原因在于,list_renderer.xml 中定义“添加行”区域的 t-if 条件,根据列表是否是分组 (grouped) 状态,使用了 不同的逻辑

让我们再次回到 addons/web/static/src/views/list/list_renderer.xml 文件中的 web.ListRenderer.Rows 模板:

  1. 非分组列表 (!list.isGrouped) 的“添加行”行:

    <tr t-if="displayRowCreates">...<a t-on-click.stop.prevent="() => this.add({ context: create.context })"><t t-esc="create.string"/></a>...
    </tr>
    

    这里的 t-if 条件是 displayRowCreates

  2. 分组列表 (t-else="") 内部,渲染组内记录下方(当组不折叠 !group.isFolded 且组内列表非分组 !group.list.isGrouped 时)的“添加行”行:

    <tr t-if="!group.list.isGrouped and props.editable and canCreate">...<a t-on-click.stop.prevent="() => group.addNewRecord({}, props.editable === 'top')">Add a line</a>...
    </tr>
    

    这里的 t-if 条件是 !group.list.isGrouped and props.editable and canCreate

分析差异:

  • 对于非分组的嵌入式列表(这很可能是你进行测试时的默认状态,比如没有按销售员或产品分组的销售订单行),控制“添加行”区域显示的条件是 displayRowCreates
  • 对于分组的嵌入式列表内部的子列表,控制“添加行”区域显示的条件是 props.editable and canCreate (同时还要满足 !group.list.isGrouped!group.isFolded,但这主要关乎列表结构本身,而不是按钮控制)。

现在我们看看 list_renderer.jsdisplayRowCreatescanCreate 的定义:

  • displayRowCreates Getter:

    get displayRowCreates() {return this.isX2Many && this.canCreate;
    }
    

    这个 getter 依赖于 isX2ManycanCreate它不依赖于 props.editable!

  • canCreate Getter:

    get canCreate() {return "link" in this.activeActions ? this.activeActions.link : this.activeActions.create;
    }
    

    这个 getter 依赖于 this.activeActions.linkthis.activeActions.create。这些值如前所述,受 XML 中的 create 属性和后端权限控制影响。

解释你的观察:

  1. 设置 editable="false" 但按钮依然存在:
    当你测试的是非分组列表时,控制按钮显示的条件是 displayRowCreates,也就是 this.isX2Many && this.canCreateeditable="false" 会导致 props.editable 变成 false,但这并不影响 isX2ManycanCreate 的值。因此,只要它是一个关联字段的嵌入列表(isX2Many 为 true)并且允许创建(canCreate 为 true),displayRowCreates 就依然是 true,按钮就依然会显示。你的观察完全符合非分组列表的渲染逻辑。

  2. 设置 create="false" 按钮消失:
    当你设置 create="false" 时,Odoo 的视图解析器会将这个信息记录下来,导致传递给 ListRenderer 的 activeActions 中的 create 标志变成 false。根据 canCreate 的定义,如果 activeActions.create 是 false 且 link 也不允许,那么 canCreate 就会变成 false

    • 对于非分组列表displayRowCreates ( isX2Many && canCreate) 中的 canCreate 变为 false,整个条件变为 false,按钮消失。
    • 对于分组列表!group.list.isGrouped and props.editable and canCreate 中的 canCreate 变为 false,整个条件变为 false,按钮消失。
      所以,create="false" 会在两种情况下都导致按钮消失,这也符合你的观察。

更精确的结论:

“添加行”按钮是否显示,取决于以下更精确的条件:

  1. 这是一个关联字段 (one2manymany2many) 的视图。 (isX2Many 为 true)

  2. 用户有权限创建新记录,并且 XML 定义允许创建。 (canCreate 为 true,这受 XML 中 <tree create="..."><control> 标签以及后端 ACLs 共同影响)

  3. 如果列表处于 分组 (grouped) 状态: 还需要 XML 中嵌套的 <tree><list> 标签设置了 editable="top"editable="bottom"。 (props.editable 不为 false)

所以,在非分组列表中,editable 属性主要控制的是 已存在记录的行是否可以直接在当前页面编辑(即进入行编辑模式),而不是控制“添加行”按钮的出现。控制非分组列表“添加行”按钮出现的关键是创建权限和 XML 的 create 设置 (canCreate)。

而在分组列表中,editable 属性则确实会参与到控制组内子列表的“添加行”按钮的显示逻辑中。

这解释了为什么在非分组状态下,editable="false" 不会隐藏按钮,而 create="false" 会隐藏按钮。

为什么要在不同的列表视图出现这个

好的,我们来详细探讨一下非分组列表和分组列表在“添加行”按钮控制上的区别,以及为什么 Odoo 的设计会是这样。

你的测试结果——在非分组列表下,editable="false" 按钮依然存在,create="false" 按钮消失;而在分组列表下,editable="false" 可能导致组内按钮消失——这确实揭示了 Odoo 前端在处理这两种视图结构时的一些设计选择。

非分组列表 vs. 分组列表

首先,明确一下这两种列表的区别:

  1. 非分组列表 (Non-Grouped List): 这是最简单的列表形式。所有记录在一个平面的表格中显示,没有按任何字段进行归类。你在 XML 中定义一个 <tree>,但不在搜索视图或任何地方设置 group_by,或者即使设置了分组,在界面上取消分组,都会看到这种形式。

    • 对应的 QWeb 模板区域在 list_renderer.xml<t t-if="!list.isGrouped"> 块内部。
  2. 分组列表 (Grouped List): 记录按照一个或多个字段的值进行归类显示。表格会显示分组头部,点击分组头部可以展开或折叠,展开后里面是属于该分组的记录列表(这些组内的列表本身也可以是分组或非分组的,取决于嵌套的分组级别)。这种视图通常在你设置了 group_by 后出现。

    • 对应的 QWeb 模板区域在 list_renderer.xml<t t-else=""> (对应外层的 !list.isGrouped) 块内部,并且会通过递归调用 constructor.rowsTemplate 来渲染组内的子列表。

“添加行”按钮的控制差异

根据我们在 list_renderer.xml 中找到的 t-if 条件:

  • 非分组列表的“添加行”区域显示条件:
    t-if="displayRowCreates"
    其中 displayRowCreateslist_renderer.js 中定义为 this.isX2Many && this.canCreate

  • 分组列表(展开后显示记录时)组内“添加行”区域显示条件:
    t-if="!group.list.isGrouped and props.editable and canCreate"

关键区别在于,分组列表中的条件包含了 props.editable,而非分组列表中的条件则没有。

这就是为什么你观察到了不同的行为:

  • 非分组列表中,editable="false" 只会影响行是否可以进入行内编辑模式(双击或回车时是否在表格里直接显示字段控件进行编辑),但不会影响 displayRowCreates 的值,因此**“添加行”按钮依然会显示**(前提是 isX2ManycanCreate 为真)。
  • 分组列表中,editable="false" 会导致组内子列表的“添加行”按钮的显示条件 (!group.list.isGrouped and props.editable and canCreate) 变为假,因此组内的“添加行”按钮会消失

为什么 Odoo 要这么设计?

理解这种设计需要考虑用户在不同列表视图中的典型交互模式:

  1. 非分组列表中的“添加行” (Non-Grouped List - “Add a line”):

    • 在这种简单列表中,“添加行”按钮提供了一个快速增加新记录的入口。
    • 用户点击这个按钮,通常会期望在列表的底部(或顶部)看到一个空白的新行,然后可以开始填写数据。
    • 即使这个列表不支持已存在记录的行内编辑 (editable="false"),允许用户通过按钮快速添加一个空白行,然后可能通过双击或其他方式在一个独立的表单弹窗中编辑它,这仍然是一个非常有用的工作流程。
    • 所以,对于非分组列表,Odoo 的设计似乎是将“添加新记录”这个功能 (canCreate) 与“已存在记录的行内编辑”这个功能 (editable) 在按钮的显示上做了一定程度的解耦。只要你允许创建,就显示添加按钮,编辑方式(行内还是弹窗)是另一个控制点。
  2. 分组列表组内的“添加行” (Grouped List - “Add a line” within a group):

    • 在分组列表中,“添加行”按钮出现在每个组的下方。
    • 用户点击这个按钮,意味着他们想在这个特定的分组下添加一个新记录。
    • 在这种上下文中的添加操作,与在非分组列表中添加是不同的。它不仅仅是创建一个新记录,而是创建一个属于这个组的新记录。
    • Odoo 的设计似乎将这种“在特定分组下直接添加并编辑”的行为,更紧密地与整个“行内交互” (editable) 的概念绑定在了一起。
    • 如果 editable 为 false,可能意味着在这个复杂的、按组组织的视图中,Odoo 倾向于禁用所有行内的添加和编辑操作,引导用户通过其他方式(比如点击行打开一个弹窗表单)来添加或修改记录。在这种情况下,直接在组内下方添加空白行的功能就没有意义了,因为它通常是行内编辑流程的开始。
    • 可以想象,如果 editable 为 false 但组内添加按钮还在,点击它会弹出一个表单,这可能会让用户感到困惑,因为它打破了当前视图结构的层级和交互模式。将组内添加按钮的显示与 editable 绑定,使得用户界面的行为更一致:要么整个组内列表支持行内添加和编辑(editable 为 true),要么都不支持。

总结为什么这么设计:

核心原因在于,Odoo 前端在不同结构的列表视图中,对 editable 属性和“添加行”功能的关联强度不同。

  • 非分组列表中,创建新记录 (canCreate) 是主要考量,editable 更多是关于如何编辑已存在记录,因此“添加行”按钮的存在主要取决于 canCreate
  • 分组列表中,在特定组内行内添加和编辑被视为一个整体的交互模式,这个模式由 editable 属性控制,所以“添加行”按钮的显示也依赖于 editable

这种设计可能旨在为用户在不同列表结构下提供最合理和一致的交互体验。虽然在初看代码时可能有些令人困惑,但考虑到不同视图模式下的用户习惯和操作流程,这种差异化的控制是有其逻辑依据的。

http://www.xdnf.cn/news/4310.html

相关文章:

  • Netty 是一个基于 Java NIO 的高性能网络通信框架
  • 基于SSM实现的健身房系统功能实现一
  • C++复习
  • Linux进程间通信(上)(21)
  • Unity学习笔记二
  • Java 8 非对称加密代码示例
  • cpp学习笔记2--class
  • STL详解 - map和set
  • tinyrenderer笔记(Shader)
  • OpenCV进阶操作:角点检测
  • WebGIS架构中,各部分承担的角色
  • MySQL 8.0 单节点部署与一主两从架构搭建实战
  • C++ 重载
  • 双口万兆光纤网卡:高性能网络的基石与应用展望
  • 端口安全基本配置
  • AI数据分析中的伪需求场景:现状、挑战与突破路径
  • 内存的位运算
  • 从设计到应用:大尺寸PCB打样的关键领域解析
  • CEF格式说明
  • 深入理解Python异步编程:从协程到高性能IO密集型应用
  • 基于【抖音弹幕抓取数据推送】——制作抖音消息分类查看界面
  • 消失的两个数字 --- 位运算
  • MySQL中的约束
  • 基于STM32、HAL库的SST26VF064B NOR FLASH存储器驱动应用程序设计
  • Python __new__ 一个特殊的静态方法
  • 理清缓存穿透、缓存击穿、缓存雪崩、缓存不一致的本质与解决方案
  • 依赖注入详解与案例(前端篇)
  • 基于ASP.NET+MySQL实现待办任务清单系统
  • 信奥赛CSP-J复赛集训(DP专题)(37):P4170 [CQOI2007] 涂色
  • [学习]RTKLib详解:rtkcmn.c与rtkpos.c