From 3deafa8ebc972493c41b8264d60bb61ad90bdfc6 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 3 May 2021 16:29:26 +0000 Subject: [PATCH] [FIX] core: enable callable in api.constrains() This avoids a call to __init__() which no longer has access to the model's class' parent classes, and was not even correct... closes odoo/odoo#70400 Related: odoo/enterprise#18141 Signed-off-by: Raphael Collet (rco) --- addons/account/models/sequence_mixin.py | 5 +---- odoo/api.py | 5 +++++ odoo/models.py | 9 +++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/addons/account/models/sequence_mixin.py b/addons/account/models/sequence_mixin.py index 09977376e43..60d4195d797 100644 --- a/addons/account/models/sequence_mixin.py +++ b/addons/account/models/sequence_mixin.py @@ -45,10 +45,7 @@ class SequenceMixin(models.AbstractModel): field=sql.Identifier(self._sequence_field), )) - def __init__(self, pool, cr): - api.constrains(self._sequence_field, self._sequence_date_field)(type(self)._constrains_date_sequence) - return super().__init__(pool, cr) - + @api.constrains(lambda self: (self._sequence_field, self._sequence_date_field)) def _constrains_date_sequence(self): # Make it possible to bypass the constraint to allow edition of already messed up documents. # /!\ Do not use this to completely disable the constraint as it will make this mixin unreliable. diff --git a/odoo/api.py b/odoo/api.py index b98a4db613b..27440797ead 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -127,7 +127,12 @@ def constrains(*args): sure a constraint will always be triggered (e.g. to test the absence of value). + One may also pass a single function as argument. In that case, the field + names are given by calling the function with a model instance. + """ + if args and callable(args[0]): + args = args[0] return attrsetter('_constrains', args) diff --git a/odoo/models.py b/odoo/models.py index a07d4cccdc3..0b1861ea932 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -750,9 +750,18 @@ class BaseModel(metaclass=MetaModel): def is_constraint(func): return callable(func) and hasattr(func, '_constrains') + def wrap(func, names): + # wrap func into a proxy function with explicit '_constrains' + @api.constrains(*names) + def wrapper(self): + return func(self) + return wrapper + cls = type(self) methods = [] for attr, func in getmembers(cls, is_constraint): + if callable(func._constrains): + func = wrap(func, func._constrains(self)) for name in func._constrains: field = cls._fields.get(name) if not field: