From 36174fcc6e49fe11ed16325b229690bb48736e62 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 2 Oct 2014 17:01:03 +0200 Subject: [PATCH] [IMP] fields: set the default value to the closest field.default or _defaults This solves a subtle issue: in the following case, the class Bar should override the default value set by Foo. But in practice it was not working, because _defaults is looked up before field.default. class Foo(models.Model): _name = 'foo' _columns = { 'foo': fields.char('Foo'), } _defaults = { 'foo': "Foo", } class Bar(models.Model): _inherit = 'foo' foo = fields.Char(default="Bar") The change makes field.default and the model's _defaults consistent with each other. --- openerp/addons/test_inherit/models.py | 9 ++-- .../addons/test_inherit/tests/test_inherit.py | 9 ++-- openerp/fields.py | 44 +++++++++++++++++-- openerp/models.py | 36 ++++----------- 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py index 0896e645a8f..1a386a28274 100644 --- a/openerp/addons/test_inherit/models.py +++ b/openerp/addons/test_inherit/models.py @@ -7,9 +7,12 @@ class mother(models.Model): _columns = { # check interoperability of field inheritance with old-style fields - 'name': osv.fields.char('Name', required=True), + 'name': osv.fields.char('Name'), 'state': osv.fields.selection([('a', 'A'), ('b', 'B')], string='State'), } + _defaults = { + 'name': 'Foo', + } surname = fields.Char(compute='_compute_surname') @@ -37,8 +40,8 @@ class mother(models.Model): field_in_mother = fields.Char() - # extend the name field by adding a default value - name = fields.Char(default='Unknown') + # extend the name field: make it required and change its default value + name = fields.Char(required=True, default='Bar') # extend the selection of the state field state = fields.Selection(selection_add=[('c', 'C')]) diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py index 9047c49b5c5..2b383ce9ba5 100644 --- a/openerp/addons/test_inherit/tests/test_inherit.py +++ b/openerp/addons/test_inherit/tests/test_inherit.py @@ -17,12 +17,15 @@ class test_inherits(common.TransactionCase): def test_field_extension(self): """ check the extension of a field in an inherited model """ - # the field mother.name should inherit required=True, and have a default - # value + # the field mother.name should inherit required=True, and have "Bar" as + # a default value mother = self.env['test.inherit.mother'] field = mother._fields['name'] self.assertTrue(field.required) - self.assertEqual(field.default(mother), 'Unknown') + + self.assertEqual(field.default(mother), "Bar") + self.assertEqual(mother.default_get(['name']), {'name': "Bar"}) + self.assertEqual(mother._defaults.get('name'), "Bar") # the field daugther.template_id should inherit # model_name='test.inherit.mother', string='Template', required=True diff --git a/openerp/fields.py b/openerp/fields.py index 4a9f4a2a888..0fae78d745f 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -312,10 +312,6 @@ class Field(object): attrs.update(self._attrs) # necessary in case self is not in cls # initialize `self` with `attrs` - if 'default' in attrs and not callable(attrs['default']): - # make default callable - value = attrs['default'] - attrs['default'] = lambda recs: value if attrs.get('compute'): # by default, computed fields are not stored, not copied and readonly attrs['store'] = attrs.get('store', False) @@ -336,8 +332,48 @@ class Field(object): if not self.string: self.string = name.replace('_', ' ').capitalize() + # determine self.default and cls._defaults in a consistent way + self._determine_default(cls, name) + self.reset() + def _determine_default(self, cls, name): + """ Retrieve the default value for `self` in the hierarchy of `cls`, and + determine `self.default` and `cls._defaults` accordingly. + """ + self.default = None + + # traverse the class hierarchy upwards, and take the first field + # definition with a default or _defaults for self + for klass in cls.__mro__: + field = klass.__dict__.get(name, self) + if not isinstance(field, type(self)): + return # klass contains another value overridden by self + + if 'default' in field._attrs: + # take the default in field, and adapt it for cls._defaults + value = field._attrs['default'] + if callable(value): + self.default = value + cls._defaults[name] = lambda model, cr, uid, context: \ + self.convert_to_write(value(model.browse(cr, uid, [], context))) + else: + self.default = lambda recs: value + cls._defaults[name] = value + return + + defaults = klass.__dict__.get('_defaults') or {} + if name in defaults: + # take the value from _defaults, and adapt it for self.default + value = defaults[name] + value_func = value if callable(value) else lambda *args: value + self.default = lambda recs: self.convert_to_cache( + value_func(recs._model, recs._cr, recs._uid, recs._context), + recs, validate=False, + ) + cls._defaults[name] = value + return + def __str__(self): return "%s.%s" % (self.model_name, self.name) diff --git a/openerp/models.py b/openerp/models.py index 929b6343972..2562e108410 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -238,8 +238,9 @@ class MetaModel(api.Meta): # transform columns into new-style fields (enables field inheritance) for name, column in self._columns.iteritems(): - if not hasattr(self, name): - setattr(self, name, column.to_field()) + if name in self.__dict__: + _logger.warning("Field %r erasing an existing value", name) + setattr(self, name, column.to_field()) class NewId(object): @@ -602,9 +603,6 @@ class BaseModel(object): ) columns.update(cls._columns) - defaults = dict(parent_class._defaults) - defaults.update(cls._defaults) - inherits = dict(parent_class._inherits) inherits.update(cls._inherits) @@ -629,7 +627,6 @@ class BaseModel(object): '_name': name, '_register': False, '_columns': columns, - '_defaults': defaults, '_inherits': inherits, '_depends': depends, '_constraints': constraints, @@ -643,7 +640,7 @@ class BaseModel(object): '_name': name, '_register': False, '_columns': dict(cls._columns), - '_defaults': dict(cls._defaults), + '_defaults': {}, # filled by Field._determine_default() '_inherits': dict(cls._inherits), '_depends': dict(cls._depends), '_constraints': list(cls._constraints), @@ -1369,15 +1366,7 @@ class BaseModel(object): self[name] = self.env['ir.property'].get(name, self._name) return - # 4. look up _defaults - if name in self._defaults: - value = self._defaults[name] - if callable(value): - value = value(self._model, cr, uid, context) - self[name] = value - return - - # 5. delegate to field + # 4. delegate to field field.determine_default(self) def fields_get_keys(self, cr, user, context=None): @@ -2413,23 +2402,14 @@ class BaseModel(object): def _set_default_value_on_column(self, cr, column_name, context=None): - # ideally should use add_default_value but fails - # due to ir.values not being ready + # ideally, we should use default_get(), but it fails due to ir.values + # not being ready - # get old-style default + # get default value default = self._defaults.get(column_name) if callable(default): default = default(self, cr, SUPERUSER_ID, context) - # get new_style default if no old-style - if default is None: - record = self.new(cr, SUPERUSER_ID, context=context) - field = self._fields[column_name] - field.determine_default(record) - defaults = dict(record._cache) - if column_name in defaults: - default = field.convert_to_write(defaults[column_name]) - column = self._columns[column_name] ss = column._symbol_set db_default = ss[1](default)