[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.
This commit is contained in:
Raphael Collet 2014-10-02 17:01:03 +02:00
parent 3f31081bc2
commit 36174fcc6e
4 changed files with 60 additions and 38 deletions

View File

@ -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')])

View File

@ -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

View File

@ -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)

View File

@ -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)