[IMP] fields: set the default value to the closest field.default or _defaults
authorRaphael Collet <rco@openerp.com>
Thu, 2 Oct 2014 15:01:03 +0000 (17:01 +0200)
committerRaphael Collet <rco@openerp.com>
Thu, 9 Oct 2014 07:18:02 +0000 (09:18 +0200)
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
openerp/addons/test_inherit/tests/test_inherit.py
openerp/fields.py
openerp/models.py

index 0896e64..1a386a2 100644 (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')])
index 9047c49..2b383ce 100644 (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
index 4a9f4a2..0fae78d 100644 (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)
 
index 929b634..2562e10 100644 (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)