Odoo 14,在 stock.quant.write() 上触发一个函数,但只触发一次

Odoo 14, triggering a function upon stock.quant.write(), but only once

我想在写入 stock.quant 时触发一个动作,即当产品的总库存发生变化时。我目前的做法是:

class StockQuantInherit(models.Model):
_inherit = "stock.quant"

def write(self, vals):
    res = super(StockQuantInherit, self).write(vals)
    available = self.product_id.product_tmpl_id.mapped('virtual_available')[0]
    log(available) # see result below
    api_call_to_external_site(self.product_id.default_code, available)
    return res

当产品库存发生变化时,这会导致 stock.quant 被写入 次,如下所示。 (显示为错误,因此我可以轻松地在信息流中发现它)

2021-08-08 12:25:44,278 11335 ERROR odoo odoo.addons.stock_enhanced.models.stock_move: 87.0 
2021-08-08 12:25:44,281 11335 ERROR odoo odoo.addons.stock_enhanced.models.stock_move: 87.0 
2021-08-08 12:25:44,299 11335 ERROR odoo odoo.addons.stock_enhanced.models.stock_move: 96.0 

或者也可能是这样的:

2021-08-08 12:32:43,987 11335 ERROR odoo odoo.addons.stock_enhanced.models.stock_move: 100.0 
2021-08-08 12:32:43,990 11335 ERROR odoo odoo.addons.stock_enhanced.models.stock_move: 100.0 
2021-08-08 12:32:44,009 11335 ERROR odoo odoo.addons.stock_enhanced.models.stock_move: 100.0 

我对前两个值不感兴趣,因为它们可能正确也可能不正确。只有最后一个是有趣的。因此,以效率的名义而不是调用同一个函数,这将在一瞬间调用外部 API 三次,我想仅在最后一次写入事件后触发该函数。

现在我的问题来了,我该怎么做?只触发第一次就够简单了,但我不知道如何只在最后一次触发它。

好的,我设法解决了问题。我要找的是“事件去抖动”。

我试图复制粘贴这个装饰器:https://gist.github.com/walkermatt/2871026

然而,这并没有奏效,因为显然每次都会创建一个新的去抖函数实例(?),导致三个线程都成功等待 1 秒通过。

所以我在我的模块中实现了这个函数,以确保它总是被调用的那个。如果我对装饰器为什么不工作的解释不正确,我深表歉意。

无论如何,这个解决方案现在解决了我的问题:

from threading import Timer

def call_api(sku, quantity_available):
    def call_it():
        log(quantity_available)
    try:
        call_api.t.cancel()
    except(AttributeError):
        pass
    call_api.t = Timer(1, call_it)
    call_api.t.start()


class StockQuantInherit(models.Model):
    _inherit = "stock.quant"

    def write(self, vals):
        res = super(StockQuantInherit, self).write(vals)
        if self.product_id.default_code: 
            available = self.product_id.product_tmpl_id.mapped('virtual_available')[0]
            call_api(self.product_id.default_code, available)
        return res