diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py index 422d59fd323..466318e22f5 100644 --- a/openerp/addons/test_inherit/models.py +++ b/openerp/addons/test_inherit/models.py @@ -25,10 +25,9 @@ class mother(models.Model): # in the child object class daughter(models.Model): _name = 'test.inherit.daughter' - _inherits = {'test.inherit.mother': 'template_id'} template_id = fields.Many2one('test.inherit.mother', 'Template', - required=True, ondelete='cascade') + delegate=True, required=True, ondelete='cascade') field_in_daughter = fields.Char('Field1') diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py index 6909882b2c6..8867629be61 100644 --- a/openerp/addons/test_inherit/tests/test_inherit.py +++ b/openerp/addons/test_inherit/tests/test_inherit.py @@ -3,7 +3,13 @@ from openerp.tests import common class test_inherits(common.TransactionCase): - def test_access_from_child_to_parent_model(self): + def test_00_inherits(self): + """ Check that a many2one field with delegate=True adds an entry in _inherits """ + daughter = self.env['test.inherit.daughter'] + + self.assertEqual(daughter._inherits, {'test.inherit.mother': 'template_id'}) + + def test_10_access_from_child_to_parent_model(self): """ check whether added field in model is accessible from children models (_inherits) """ # This test checks if the new added column of a parent model # is accessible from the child model. This test has been written @@ -15,7 +21,7 @@ class test_inherits(common.TransactionCase): self.assertIn('field_in_mother', mother._fields) self.assertIn('field_in_mother', daughter._fields) - def test_field_extension(self): + def test_20_field_extension(self): """ check the extension of a field in an inherited model """ mother = self.env['test.inherit.mother'] daughter = self.env['test.inherit.daughter'] @@ -41,7 +47,7 @@ class test_inherits(common.TransactionCase): self.assertEqual(field.string, "Template") self.assertTrue(field.required) - def test_depends_extension(self): + def test_30_depends_extension(self): """ check that @depends on overridden compute methods extends dependencies """ mother = self.env['test.inherit.mother'] field = mother._fields['surname'] @@ -49,7 +55,7 @@ class test_inherits(common.TransactionCase): # the field dependencies are added self.assertItemsEqual(field.depends, ['name', 'field_in_mother']) - def test_selection_extension(self): + def test_40_selection_extension(self): """ check that attribute selection_add=... extends selection on fields. """ mother = self.env['test.inherit.mother'] diff --git a/openerp/addons/test_new_api/ir.model.access.csv b/openerp/addons/test_new_api/ir.model.access.csv index d43088d808b..5b62045ae08 100644 --- a/openerp/addons/test_new_api/ir.model.access.csv +++ b/openerp/addons/test_new_api/ir.model.access.csv @@ -2,5 +2,4 @@ access_category,test_new_api_category,test_new_api.model_test_new_api_category,,1,1,1,1 access_discussion,test_new_api_discussion,test_new_api.model_test_new_api_discussion,,1,1,1,1 access_message,test_new_api_message,test_new_api.model_test_new_api_message,,1,1,1,1 -access_talk,test_new_api_talk,test_new_api.model_test_new_api_talk,,1,1,1,1 access_mixed,test_new_api_mixed,test_new_api.model_test_new_api_mixed,,1,1,1,1 diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py index 9866c8b6fd4..55611f6029d 100644 --- a/openerp/addons/test_new_api/models.py +++ b/openerp/addons/test_new_api/models.py @@ -153,12 +153,6 @@ class Message(models.Model): self.double_size = self.double_size + size -class Talk(models.Model): - _name = 'test_new_api.talk' - - parent = fields.Many2one('test_new_api.discussion', delegate=True, required=True) - - class MixedModel(models.Model): _name = 'test_new_api.mixed' diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py index 6ffeb9b9153..8a32e5412a9 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -375,20 +375,3 @@ class TestMagicFields(common.TransactionCase): record = self.env['test_new_api.discussion'].create({'name': 'Booba'}) self.assertEqual(record.create_uid, self.env.user) self.assertEqual(record.write_uid, self.env.user) - - -class TestInherits(common.TransactionCase): - - def test_inherits(self): - """ Check that a many2one field with delegate=True adds an entry in _inherits """ - Talk = self.env['test_new_api.talk'] - self.assertEqual(Talk._inherits, {'test_new_api.discussion': 'parent'}) - self.assertIn('name', Talk._fields) - self.assertEqual(Talk._fields['name'].related, ('parent', 'name')) - - talk = Talk.create({'name': 'Foo'}) - discussion = talk.parent - self.assertTrue(discussion) - self.assertEqual(talk._name, 'test_new_api.talk') - self.assertEqual(discussion._name, 'test_new_api.discussion') - self.assertEqual(talk.name, discussion.name) diff --git a/openerp/fields.py b/openerp/fields.py index 7a58bef7641..5c38d5bb4bd 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -297,6 +297,13 @@ class Field(object): self._attrs = {key: val for key, val in kwargs.iteritems() if val is not None} self._free_attrs = [] + # self._triggers is a set of pairs (field, path) that represents the + # computed fields that depend on `self`. When `self` is modified, it + # invalidates the cache of each `field`, and registers the records to + # recompute based on `path`. See method `modified` below for details. + self._triggers = set() + self.inverse_fields = [] + def new(self, **kwargs): """ Return a field of the same type as `self`, with its own parameters. """ return type(self)(**kwargs) @@ -396,16 +403,6 @@ class Field(object): # Field setup # - def reset(self): - """ Prepare `self` for a new setup. """ - self.setup_done = False - # self._triggers is a set of pairs (field, path) that represents the - # computed fields that depend on `self`. When `self` is modified, it - # invalidates the cache of each `field`, and registers the records to - # recompute based on `path`. See method `modified` below for details. - self._triggers = set() - self.inverse_fields = [] - def setup(self, env): """ Make sure that `self` is set up, except for recomputation triggers. """ if not self.setup_done: diff --git a/openerp/models.py b/openerp/models.py index a9f32c72588..890d972068f 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -636,8 +636,8 @@ class BaseModel(object): attrs = { '_name': name, '_register': False, - '_columns': {}, # filled by _setup_fields() - '_defaults': {}, # filled by Field._determine_default() + '_columns': None, # recomputed in _setup_fields() + '_defaults': None, # recomputed in _setup_base() '_inherits': dict(cls._inherits), '_depends': dict(cls._depends), '_constraints': list(cls._constraints), @@ -745,15 +745,40 @@ class BaseModel(object): for (key, _, msg) in cls._sql_constraints: cls.pool._sql_error[cls._table + '_' + key] = msg - # collect constraint and onchange methods - cls._constraint_methods = [] - cls._onchange_methods = defaultdict(list) - for attr, func in getmembers(cls, callable): - if hasattr(func, '_constrains'): - cls._constraint_methods.append(func) - if hasattr(func, '_onchange'): - for name in func._onchange: - cls._onchange_methods[name].append(func) + @property + def _constraint_methods(self): + """ Return a list of methods implementing Python constraints. """ + def is_constraint(func): + return callable(func) and hasattr(func, '_constrains') + + cls = type(self) + methods = [] + for attr, func in getmembers(cls, is_constraint): + if not all(name in cls._fields for name in func._constrains): + _logger.warning("@constrains%r parameters must be field names", func._constrains) + methods.append(func) + + # optimization: memoize result on cls, it will not be recomputed + cls._constraint_methods = methods + return methods + + @property + def _onchange_methods(self): + """ Return a dictionary mapping field names to onchange methods. """ + def is_onchange(func): + return callable(func) and hasattr(func, '_onchange') + + cls = type(self) + methods = defaultdict(list) + for attr, func in getmembers(cls, is_onchange): + for name in func._onchange: + if name not in cls._fields: + _logger.warning("@onchange%r parameters must be field names", func._onchange) + methods[name].append(func) + + # optimization: memoize result on cls, it will not be recomputed + cls._onchange_methods = methods + return methods def __new__(cls): # In the past, this method was registering the model class in the server. @@ -800,19 +825,6 @@ class BaseModel(object): "TransientModels must have log_access turned on, " \ "in order to implement their access rights policy" - # retrieve new-style fields (from above registry class) and duplicate - # them (to avoid clashes with inheritance between different models) - cls._fields = {} - above = cls.__bases__[0] - for attr, field in getmembers(above, Field.__instancecheck__): - cls._add_field(attr, field.new()) - - # introduce magic fields - cls._add_magic_fields() - - # register constraints and onchange methods - cls._init_constraints_onchanges() - # prepare ormcache, which must be shared by all instances of the model cls._ormcache = {} @@ -2941,26 +2953,28 @@ class BaseModel(object): if cls._setup_done: return - # first make sure that parent models determine all their fields + # 1. determine the proper fields of the model; duplicate them on cls to + # avoid clashes with inheritance between different models + for name in getattr(cls, '_fields', {}): + delattr(cls, name) + + # retrieve fields from parent classes + cls._fields = {} + cls._defaults = {} + for attr, field in getmembers(cls, Field.__instancecheck__): + cls._add_field(attr, field.new()) + + # add magic and custom fields + cls._add_magic_fields() + cls._init_manual_fields(self._cr, partial) + + # 2. make sure that parent models determine their own fields, then add + # inherited fields to cls cls._inherits_check() for parent in cls._inherits: self.env[parent]._setup_base(partial) - - # remove inherited fields from cls._fields - for name, field in cls._fields.items(): - if field.inherited: - del cls._fields[name] - - # retrieve custom fields - cls._init_manual_fields(self._cr, partial) - - # retrieve inherited fields cls._init_inherited_fields() - # prepare the setup of fields - for field in cls._fields.itervalues(): - field.reset() - cls._setup_done = True @api.model @@ -2968,7 +2982,8 @@ class BaseModel(object): """ Setup the fields, except for recomputation triggers. """ cls = type(self) - # set up fields, and update their corresponding columns + # set up fields, and determine their corresponding column + cls._columns = {} for name, field in cls._fields.iteritems(): field.setup(self.env) if field.store or field.column: @@ -3007,14 +3022,8 @@ class BaseModel(object): # register stuff about low-level function fields cls._init_function_fields(cls.pool, self._cr) - # check constraints - for func in cls._constraint_methods: - if not all(name in cls._fields for name in func._constrains): - _logger.warning("@constrains%r parameters must be field names", func._constrains) - for name in cls._onchange_methods: - if name not in cls._fields: - func = cls._onchange_methods[name] - _logger.warning("@onchange%r parameters must be field names", func._onchange) + # register constraints and onchange methods + cls._init_constraints_onchanges() # check defaults for name in cls._defaults: