From 00406326966eb3f25bd477ac1b9dfbc027d01be5 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 13 Jan 2016 11:30:36 +0100 Subject: [PATCH] [FIX] models: after extending a model, recompute its children models attributes This is necessary when adding `_inherits`, `_depends` or constraints to a model. --- openerp/addons/test_inherit/models.py | 2 + .../addons/test_inherit/tests/test_inherit.py | 6 ++ openerp/models.py | 78 ++++++++++--------- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py index e82540df2b5..c50becd9490 100644 --- a/openerp/addons/test_inherit/models.py +++ b/openerp/addons/test_inherit/models.py @@ -138,6 +138,8 @@ class Parent2(models.AbstractModel): foo = fields.Integer() + _sql_constraints = [('unique_foo', 'UNIQUE(foo)', 'foo must be unique')] + def stuff(self): return super(Parent2, self).stuff() + 'P2' diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py index 58bec9356ec..98f00c0e392 100644 --- a/openerp/addons/test_inherit/tests/test_inherit.py +++ b/openerp/addons/test_inherit/tests/test_inherit.py @@ -117,6 +117,12 @@ class TestInherit(common.TransactionCase): self.assertEqual(parent.stuff(), 'P1P2') self.assertEqual(child.stuff(), 'P1P2C1') + # check inferred model attributes + self.assertEqual(parent._table, 'test_inherit_parent') + self.assertEqual(child._table, 'test_inherit_child') + self.assertEqual(len(parent._sql_constraints), 1) + self.assertEqual(len(child._sql_constraints), 1) + # check properties memoized on model self.assertEqual(len(parent._constraint_methods), 1) self.assertEqual(len(child._constraint_methods), 1) diff --git a/openerp/models.py b/openerp/models.py index 8b1fdf037d8..540b92edca5 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -639,51 +639,55 @@ class BaseModel(object): else: bases.add(parent_class) parent_class._inherit_children.add(name) - ModelClass.__bases__ = bases = tuple(bases) + ModelClass.__bases__ = tuple(bases) # determine the attributes of the model's class - description = None - table = None - sequence = None - log_access = ModelClass._auto - inherits = {} - depends = {} - constraints = {} - sql_constraints = [] - - for base in reversed(bases): - if not getattr(base, 'pool', None): - # the following attributes are not taken from model classes - description = base._description or description - table = base._table or table - sequence = base._sequence or sequence - log_access = getattr(base, '_log_access', log_access) - - inherits.update(base._inherits) - - for mname, fnames in base._depends.iteritems(): - depends[mname] = depends.get(mname, []) + fnames - - for cons in base._constraints: - # cons may override a constraint with the same function name - constraints[getattr(cons[0], '__name__', id(cons[0]))] = cons - - sql_constraints += base._sql_constraints - - ModelClass._description = description or name - ModelClass._table = table or name.replace('.', '_') - ModelClass._sequence = sequence or (ModelClass._table + '_id_seq') - ModelClass._log_access = log_access - ModelClass._inherits = inherits - ModelClass._depends = depends - ModelClass._constraints = constraints.values() - ModelClass._sql_constraints = sql_constraints + ModelClass._build_model_attributes(pool) # instantiate the model, and initialize it model = object.__new__(ModelClass) model.__init__(pool, cr) return model + @classmethod + def _build_model_attributes(cls, pool): + """ Initialize base model attributes. """ + cls._description = cls._name + cls._table = cls._name.replace('.', '_') + cls._sequence = None + cls._log_access = cls._auto + cls._inherits = {} + cls._depends = {} + cls._constraints = {} + cls._sql_constraints = [] + + for base in reversed(cls.__bases__): + if not getattr(base, 'pool', None): + # the following attributes are not taken from model classes + cls._description = base._description or cls._description + cls._table = base._table or cls._table + cls._sequence = base._sequence or cls._sequence + cls._log_access = getattr(base, '_log_access', cls._log_access) + + cls._inherits.update(base._inherits) + + for mname, fnames in base._depends.iteritems(): + cls._depends[mname] = cls._depends.get(mname, []) + fnames + + for cons in base._constraints: + # cons may override a constraint with the same function name + cls._constraints[getattr(cons[0], '__name__', id(cons[0]))] = cons + + cls._sql_constraints += base._sql_constraints + + cls._sequence = cls._sequence or (cls._table + '_id_seq') + cls._constraints = cls._constraints.values() + + # recompute attributes of children models + for child_name in cls._inherit_children: + child_class = type(pool[child_name]) + child_class._build_model_attributes(pool) + @classmethod def _init_function_fields(cls, pool, cr): # initialize the list of non-stored function fields for this model