odoo x2many字段的只读应用

问题

odoo中的x2many字段,在编辑状态下可以添加删除行,如果想要禁止编辑状态下的添加和删除,在原生状态下是不可以完成的,通常的思路是把x2many字段声明为只读:

1
2
3
<div>
<field name="lots_info" editable="bottom" readonly="1"/>
</div>

lots_info是个one2many类型的字段,其值是通过计算赋值的,但这样做存在下面几个问题:

  • 虽然lots_info的tree视图只读了,但是单击某一行依旧有弹窗弹出,而且带有保存并关闭、保存并新建等按钮。
  • 只读字段不能缓存,单击保存并关闭后,再次打开该列,并没有上次的信息。
  • 只读字段不会保存到数据库中。

1

2

解决办法

问题出现了,那么我就需要找到适当的解决办法来解决这个问题,毕竟,这个需求的场景是存在并且有一定的合理性。

目前我们知道,对于添加了readonly属性的字段并不会被存储到数据库当中,即便store=True。这个过程不是在后端代码中进行的,而是在前端js触发保存的那一刻就舍弃了带有readonly的字段。因此,我们需要找到一种途径来确保即便字段声明了readonly也能够被存储起来。第二个问题,对于单击弹窗的问题,我们需要阻断这个弹窗的操作,让它在我们需要的条件下不进行弹窗。

经过长时间的探索,没有发现一种好的方法能够让odoo保存带有readonly属性的字段,退而求其次的,我在context中添加了一个属性x2m_readonly,只要发现字段的context中包含这个属性,就禁用掉前端的create和delete action。至于编辑,阻止掉弹窗就自然而然不能编辑了。

这段逻辑的开发完全基于js,不涉及掉后端python代码。这里简要介绍一下基本思路和关键代码。

首先要明确,我们常见的One2many是odoo qweb框架的一个部件(widget),被定义为One2manyField,One2manyField和Many2manyField同是X2manyField的子类。这些都定义在RelatedField部件中。One2many部件根据当前的编辑模式(编辑、只读)会有两种不同的模式:edit和readonly,对应于我们是否打开编辑按钮的操作。

在readonly模式下

我们单击One2many部件中某一行的动作会打开一个叫做FormViewDialog的部件,这个部件继承自ViewDialog。

FormViewDialog有一个动作叫open,这个就是我们单击x2many部件某一行弹窗的动作,因此,我们要重载这个方法:

1
2
3
4
5
6
7
8
9
FormViewDialog.include({
open: function () {
// 添加x2m_readonly属性不弹窗
if (!this.context.x2m_readonly) {
// 没有设置x2m_readonly属性
this._super();
}
},
});

检测这个部件的context中是否包含x2m_readonly属性,不包含的我们才继续调用父类方法,完成弹窗的动作,否则直接忽略掉该动作,表现为”只读”。

在edit模式下

同样的,对于One2many编辑模式下的单击,它会触发One2many部件的私有方法_onEditLine,我们也要予以阻断:

1
2
3
4
5
6
7
8
9
10
One2manyField.include({

_onEditLine:function(ev){
// x2m_readonly 不予响应编辑事件
if(!this.field.context.x2m_readonly){
this._super(ev)
}
},

...

接下来要处理的是编辑模式下的Add new line和delete按钮,这个需要在部件显示的时候将其”击毙”,部件显示的方法为_render:

1
2
3
4
5
6
7
8
_render: function () {
...
// 对于拥有x2m_readonly属性的字段,不显示添加和删除按钮
if (this.field.context.x2m_readonly){
this.activeActions.create = false;
this.activeActions.delete = false;
}
...

这样就基本解决了我们开始所碰见的问题。

最后,需要源代码的同志戳这里

你的支持我的动力