Odoo16 中 X2Many 字段指定动态视图

大家应该都知道在 Odoo 的 context 中有若干用以指定视图的属性,如 form_view_reftree_view_ref 等,可以给字段指定其展示所使用的对应类型视图的。这个功能在 Odoo 16 之前的版本中,是可以指定一个动态的视图的,如根据单据类型的不同,在打开关系型字段的弹窗表单也可以随着单据类型变化。
<notebook> <page name="items" string="订单明细"> <field name="item_ids" context="{'form_view_ref': 'foo.' + order_type + '_order_item_view_form'}"/> </page> </notebook>
这个功能在 Odoo 16 中变得不太好使了,具体表现为只会使用当前记录首次解析出来的 form_view_ref 值,以上面的代码为例,若打开页面时,order_type 的值为 bar,则无论如何改变 order_type 的值,打开或创建 item_ids 对应的记录时打开的页面会一直是 foo.bar_order_item_view_form 对应的视图。

原因

通过 X2Many 字段组件的源码(/web/static/src/views/fields/x2many/x2many_field.js)可以发现创建和编辑的操作调用了 _openRecord(params) 这个方法,但是并没有发现任何跟视图相关的逻辑,继续往上一层找,可以看到调用了 useOpenX2ManyRecord() 这个方法,其中可以看到和视图有关的一段逻辑:
/** * /web/static/src/views/fields/relational_utils.js */ const form = await getFormViewInfo({ list, activeField, viewService, userService, env });
跳转到这个方法里面继续查看,在调用 loadViews() 加载视图的方法里将 context 作为参数传递了进去,到这里直觉告诉我离想要的答案不远了。
const { fields, relatedModels, views } = await viewService.loadViews({ context: list.context, resModel: comodel, views: [[false, "form"]], });
接着打开 loadViews() 所在的源文件(/web/static/src/views/view_service.js)看看里面的实现,会发现下面这一段,把各种参数序列化成字符串后作为缓存的 Key 使用了,如果参数值没有变化,就不会通过 RPC 去请求获取视图的方法 get_views 了。
const { context, resModel, views } = params; const filteredContext = Object.fromEntries( Object.entries(context || {}).filter((k, v) => !String(k).startsWith("default_")) ); const key = JSON.stringify([resModel, views, filteredContext, loadViewsOptions]); if (!cache[key]) { ... }
在最开始我们提到过,context 中的 form_view_ref 值只会使用首次解析(实际上这里是 context 本身没有被重新解析)出来的值,因为 context 没有变化,其他参数也没有变化,通过上面的这段逻辑就可以知道,其实打开的视图都是缓存下来的。
很明显,问题就出在 context 没有重新解析这一点上,或者说,使用的 context 值是首次打开页面时解析的值,之后就不更新了。因此也很容易就可以定位到导致该问题的具体代码,就是 loadViews() 时传入的 list.context 有问题。

解决方案

再往回翻看一下 useOpenX2ManyRecord() 里的逻辑,可以看到 async function openRecord({ record, mode, context, title, onClose }) 这个方法的定义,是有传入 context 作为参数的,而在调用 getFormViewInfo() 时,却没有传递进去,而是直接通过 list 来获取 context 的值。
而在 Odoo 10 中,加载视图前会 var context = dataset.get_context(); 获取最新的 context 值,在 Odoo 14 中,则会使用通过参数传递进去的 context 值,这些 context 都是 load_views 前最新解析出来的值。所以在 Odoo 16 中,要修复这个问题,只需要让 getFormViewInfo() 这个方法可以用上最新的 context 值即可。
先来看看最终的实现:
/** @odoo-module */ import { patch } from "@web/core/utils/patch"; import { X2ManyField } from "@web/views/fields/x2many/x2many_field"; patch(X2ManyField.prototype, 'x2many_field_with_dynamic_form_view_ref', { /** * x2m 字段打开前更新 list.context 中的视图引用值 * 使动态视图上下文可以生效 * * @override */ setup() { this._super(...arguments); // 保留原本的逻辑 const __openRecord = this._openRecord; this._openRecord = (params) => { // 仅当 context 中出现 form_view_ref 才更新 if (params.context && params.context.form_view_ref) { this.list.context.form_view_ref = params.context.form_view_ref; } // 执行原本的逻辑 __openRecord(params); }; } });
非常简单的一个 patch 就可以解决指定动态视图的问题,只需要在 X2ManyField 组件的 setup() 初始化方法里将 _openRecord 的逻辑做一点变更,在它执行前把 list.context 更新成最新的值。这里只处理了 form_view_ref 这个属性,是因为想要保证在 patch 时只做最小化的修改,尽量不影响原本的行为。

©️
 本文采用 CC BY-NC-ND 4.0 许可协议。转载或引用时请遵守协议内容!

© Ruter Lü 2016 - 2024