From 2750e15062ca60c826243efdf65817f4da681082 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 13 Aug 2019 17:13:49 +0200 Subject: [PATCH] base_exception: Remove side effect from api.constrains In the documentation. The method called by '_check_exception' has a side effect, it writes on 'exception.rule' + on the Many2many relation between it and the related model (such as sale.order). When decorated by @api.constrains, any error during the method will be caught and re-raised as "ValidationError". This part of code is very prone to concurrent updates as 2 sales having the same exception will both write on the same 'exception.rule'. A concurrent update (OperationalError) is re-raised as ValidationError, and then is not retried properly. Calling the same method in create/write has the same effect than @api.constrains without shadowing the exception type. Full explanation: OCA/server-tools#1642 --- base_exception/models/base_exception.py | 40 ++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/base_exception/models/base_exception.py b/base_exception/models/base_exception.py index 549af1e45..f17eb7b25 100644 --- a/base_exception/models/base_exception.py +++ b/base_exception/models/base_exception.py @@ -237,14 +237,40 @@ class BaseException(models.AbstractModel): @api.multi def _check_exception(self): """ - This method must be used in a constraint that must be created in the - object that inherits for base.exception. - for sale : - @api.constrains('ignore_exception',) + This method must be called in the create and write methods of the model + using exceptions. The model has to inherit from 'base.exception'. + + Example for sale (used in OCA/sale-workflow/sale_exception): + + def _fields_trigger_check_exception(self): + return ['ignore_exception', 'order_line', 'state'] + + @api.model + def create(self, vals): + record = super(SaleOrder, self).create(vals) + check_exceptions = any( + field in vals for field + in self._fields_trigger_check_exception() + ) + if check_exceptions: + record.sale_check_exception() + return record + + @api.multi + def write(self, vals): + result = super(SaleOrder, self).write(vals) + check_exceptions = any( + field in vals for field + in self._fields_trigger_check_exception() + ) + if check_exceptions: + self.sale_check_exception() + return result + def sale_check_exception(self): - ... - ... - self._check_exception + orders = self.filtered(lambda s: s.state == 'sale') + if orders: + orders._check_exception() """ exception_ids = self.detect_exceptions() if exception_ids: