即使一个人,也要活得像军队一样!

Odoo13-- Onchange方法的使用扩展

抛砖

场景:
假设存在一张单据,如采购订单。该订单上有如下字段: 采购申请数量、采购批准数量、汇率、金额、价外税、含税金额、库存数量、累计金额、累计含税金额……
单据上有很多表示数量类型的字段,一般在录入单据时,对数据有合法性校验,如不能输入负数数量等等。
此时,我们可能会在models.py中写一个使用api.onchange装饰器装饰的方法。
在校验的字段很多的情况下,我们可能会写很多个onchange方法,或者写一个onchange方法但是装饰器中写很多字段参数。
那么问题来了,如果写很多个方法,未免太多余,因为都是校验负数。但是如果写一个方法,但是装饰器中添加很多参数,那么在某一字段新增一些逻辑时,要么在此方法中写,要么新写一个方法。又是很冗余。
所以,对于这种通用性的校验,是否有一个好的通用方法来写呢?

各位看官,就请关上门,拉上窗,听我细细分说。

关于api.onchange使用,请客官看之前文章的介绍。

揭开 api.onchange 面纱

api.onchange 定义

文件路径: \odoo\odoo\api.py
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def onchange(*args):
""" Return a decorator to decorate an onchange method for given fields.
Each argument must be a field name::

@api.onchange('partner_id')
def _onchange_partner(self):
self.message = "Dear %s" % (self.partner_id.name or "")

In the form views where the field appears, the method will be called
when one of the given fields is modified. The method is invoked on a
pseudo-record that contains the values present in the form. Field
assignments on that record are automatically sent back to the client.

The method may return a dictionary for changing field domains and pop up
a warning message, like in the old API::

return {
'domain': {'other_id': [('partner_id', '=', partner_id)]},
'warning': {'title': "Warning", 'message': "What is this?", 'type': 'notification'},
}
If the type is set to notification, the warning will be displayed in a notification.
Otherwise it will be displayed in a dialog as default.

.. danger::

Since ``@onchange`` returns a recordset of pseudo-records,
calling any one of the CRUD methods
(:meth:`create`, :meth:`read`, :meth:`write`, :meth:`unlink`)
on the aforementioned recordset is undefined behaviour,
as they potentially do not exist in the database yet.

Instead, simply set the record's field like shown in the example
above or call the :meth:`update` method.

.. warning::

``@onchange`` only supports simple field names, dotted names
(fields of relational fields e.g. ``partner_id.tz``) are not
supported and will be ignored
"""
return attrsetter('_onchange', args)

各位看官,请看,是不是很简单, 很冰清玉洁啊, 朴实无华, 那么多的注释,只有一行代码。
啥?看不懂?attrsetter方法是啥?我好想听过setattr
别急,继续看

1
2
3
def attrsetter(attr, value):
""" Return a function that sets ``attr`` on its argument and returns it. """
return lambda method: setattr(method, attr, value) or method

attrsetter的作用是返回一个函数并且设置属性,而恰恰就是使用的setattr方法。
返回一个函数?设置属性?好熟悉… 是啥来着… 装饰器,可不是咋地,可不就是这玩意儿。
搜嘎,原来api.onchange是装饰器 是这么来的。返回了onchange方法

那么结合上面api中的onchange方法,我们明白了,原来是就是给相应的字段设置了onchange属性。

继续看models.py中的onchange方法,路径\odoo\odoo\models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def onchange(self, values, field_name, field_onchange):
""" Perform an onchange on the given field.

:param values: dictionary mapping field names to values, giving the
current state of modification
:param field_name: name of the modified field, or list of field
names (in view order), or False
:param field_onchange: dictionary mapping field names to their
on_change attribute
"""
# this is for tests using `Form`
self.flush()
...
省略
...
return result

:param values: 当前状态下的记录的值
:param field_name: 改变的字段,可能是列表
:param field_onchange: 给字段设置on_change属性

所以我们已经掌握了基本姿势。
接下来就很简单了,我们只要继承onchange方法,做出相应修改即可。

姿势要领

直接实战姿势:

1
2
3
4
5
6
7
8
9
def get_check_onchange_field(self):
"""返回onchange方法要校验的字段"""
return ['amount', 'quantity', 'tax']

def onchange(self, values, field_name, field_onchange):
if field_name in self.get_check_onchange_field():
if getattr(self, field_name) < 0:
raise ValidationError("数值不合法,不能为负数")
return super(SaleOrder, self).onchange(values, field_name, field_onchange)

以上

先了解,再找准,再开发。
– end –


更新

有些看官反映还是没看懂,而且后面的实战有些东西没说清楚。请看另一篇补充文章:

传送门:《以onchange()为例,深入源码,一探究竟》

-------------本文结束感谢您的阅读-------------