[IMP] models: do not use compute methods to determine default values anymore
authorRaphael Collet <rco@openerp.com>
Thu, 9 Oct 2014 15:22:42 +0000 (17:22 +0200)
committerRaphael Collet <rco@openerp.com>
Mon, 13 Oct 2014 11:44:07 +0000 (13:44 +0200)
Compute methods could give results that should not be considered as default
values.  For instance, a related field usually defaults to a null value, which
is then set to the field with its inverse method by create().  This may violate
a non-null constraint if the original field is required.  Therefore, compute
methods are no longer used to determine default values.

openerp/addons/base/ir/ir_values.py
openerp/addons/test_new_api/models.py
openerp/addons/test_new_api/tests/test_new_fields.py
openerp/fields.py
openerp/models.py

index 4720f60..075612c 100644 (file)
@@ -335,7 +335,7 @@ class ir_values(osv.osv):
                 (row['id'], row['name'], pickle.loads(row['value'].encode('utf-8'))))
         return defaults.values()
 
-    # use ormcache: this is called a lot by BaseModel.add_default_value()!
+    # use ormcache: this is called a lot by BaseModel.default_get()!
     @tools.ormcache(skiparg=2)
     def get_defaults_dict(self, cr, uid, model, condition=False):
         """ Returns a dictionary mapping field names with their corresponding
index 3b15275..6a22334 100644 (file)
@@ -117,7 +117,7 @@ class Message(models.Model):
     @api.one
     @api.depends('author.name', 'discussion.name')
     def _compute_name(self):
-        self.name = "[%s] %s" % (self.discussion.name or '', self.author.name)
+        self.name = "[%s] %s" % (self.discussion.name or '', self.author.name or '')
 
     @api.one
     @api.depends('author.name', 'discussion.name', 'body')
index 14ff358..74f85fa 100644 (file)
@@ -349,20 +349,18 @@ class TestNewFields(common.TransactionCase):
         message.body = BODY = "May the Force be with you."
         self.assertEqual(message.discussion, discussion)
         self.assertEqual(message.body, BODY)
-
+        self.assertFalse(message.author)
         self.assertNotIn(message, discussion.messages)
 
         # check computed values of fields
-        user = self.env.user
-        self.assertEqual(message.author, user)
-        self.assertEqual(message.name, "[%s] %s" % (discussion.name, user.name))
+        self.assertEqual(message.name, "[%s] %s" % (discussion.name, ''))
         self.assertEqual(message.size, len(BODY))
 
     def test_41_defaults(self):
         """ test default values. """
         fields = ['discussion', 'body', 'author', 'size']
         defaults = self.env['test_new_api.message'].default_get(fields)
-        self.assertEqual(defaults, {'author': self.env.uid, 'size': 0})
+        self.assertEqual(defaults, {'author': self.env.uid})
 
         defaults = self.env['test_new_api.mixed'].default_get(['number'])
         self.assertEqual(defaults, {'number': 3.14})
index e747d55..748576c 100644 (file)
@@ -753,8 +753,8 @@ class Field(object):
             # normal record -> read or compute value for this field
             self.determine_value(record)
         else:
-            # new record -> compute default value for this field
-            record.add_default_value(self)
+            # draft record -> compute the value or let it be null
+            self.determine_draft_value(record)
 
         # the result should be in cache now
         return record._cache[self]
@@ -861,12 +861,9 @@ class Field(object):
             # this is a non-stored non-computed field
             record._cache[self] = self.null(env)
 
-    def determine_default(self, record):
-        """ determine the default value of field `self` on `record` """
-        if self.default:
-            value = self.default(record)
-            record._cache[self] = self.convert_to_cache(value, record)
-        elif self.compute:
+    def determine_draft_value(self, record):
+        """ Determine the value of `self` for the given draft `record`. """
+        if self.compute:
             self._compute_value(record)
         else:
             record._cache[self] = SpecialValue(self.null(record.env))
@@ -1470,15 +1467,6 @@ class Many2one(_Relational):
     def convert_to_display_name(self, value):
         return ustr(value.display_name)
 
-    def determine_default(self, record):
-        super(Many2one, self).determine_default(record)
-        if self.delegate:
-            # special case: fields that implement inheritance between models
-            value = record[self.name]
-            if not value:
-                # the default value cannot be null, use a new record instead
-                record[self.name] = record.env[self.comodel_name].new()
-
 
 class UnionUpdate(SpecialValue):
     """ Placeholder for a value update; when this value is taken from the cache,
index 1457a4b..a4597a2 100644 (file)
@@ -1305,7 +1305,8 @@ class BaseModel(object):
                 except Exception, e:
                     raise ValidationError("Error while validating constraint\n\n%s" % tools.ustr(e))
 
-    def default_get(self, cr, uid, fields_list, context=None):
+    @api.model
+    def default_get(self, fields_list):
         """ default_get(fields) -> default_values
 
         Return default values for the fields in `fields_list`. Default
@@ -1314,60 +1315,56 @@ class BaseModel(object):
 
         :param fields_list: a list of field names
         :return: a dictionary mapping each field name to its corresponding
-            default value; the keys of the dictionary are the fields in
-            `fields_list` that have a default value different from ``False``.
+            default value, if it has one.
 
-        This method should not be overridden. In order to change the
-        mechanism for determining default values, you should override method
-        :meth:`add_default_value` instead.
         """
         # trigger view init hook
-        self.view_init(cr, uid, fields_list, context)
+        self.view_init(fields_list)
+
+        defaults = {}
+        parent_fields = defaultdict(list)
 
-        # use a new record to determine default values; evaluate fields on the
-        # new record and put default values in result
-        record = self.new(cr, uid, {}, context=context)
-        result = {}
         for name in fields_list:
-            if name in self._fields:
-                value = record[name]
-                if name in record._cache:
-                    result[name] = value        # it really is a default value
+            # 1. look up context
+            key = 'default_' + name
+            if key in self._context:
+                defaults[name] = self._context[key]
+                continue
 
-        # convert default values to the expected format
-        result = self._convert_to_write(result)
-        return result
+            # 2. look up ir_values
+            #    Note: performance is good, because get_defaults_dict is cached!
+            ir_values_dict = self.env['ir.values'].get_defaults_dict(self._name)
+            if name in ir_values_dict:
+                defaults[name] = ir_values_dict[name]
+                continue
 
-    def add_default_value(self, field):
-        """ Set the default value of `field` to the new record `self`.
-            The value must be assigned to `self`.
-        """
-        assert not self.id, "Expected new record: %s" % self
-        cr, uid, context = self.env.args
-        name = field.name
+            field = self._fields.get(name)
 
-        # 1. look up context
-        key = 'default_' + name
-        if key in context:
-            self[name] = context[key]
-            return
+            # 3. look up property fields
+            #    TODO: get rid of this one
+            if field and field.company_dependent:
+                defaults[name] = self.env['ir.property'].get(name, self._name)
+                continue
 
-        # 2. look up ir_values
-        #    Note: performance is good, because get_defaults_dict is cached!
-        ir_values_dict = self.env['ir.values'].get_defaults_dict(self._name)
-        if name in ir_values_dict:
-            self[name] = ir_values_dict[name]
-            return
+            # 4. look up field.default
+            if field and field.default:
+                defaults[name] = field.default(self)
+                continue
 
-        # 3. look up property fields
-        #    TODO: get rid of this one
-        column = self._columns.get(name)
-        if isinstance(column, fields.property):
-            self[name] = self.env['ir.property'].get(name, self._name)
-            return
+            # 5. delegate to parent model
+            if field and field.inherited:
+                field = field.related_field
+                parent_fields[field.model_name].append(field.name)
+
+        # convert default values to the right format
+        defaults = self._convert_to_cache(defaults, validate=False)
+        defaults = self._convert_to_write(defaults)
+
+        # add default values for inherited fields
+        for model, names in parent_fields.iteritems():
+            defaults.update(self.env[model].default_get(names))
 
-        # 4. delegate to field
-        field.determine_default(self)
+        return defaults
 
     def fields_get_keys(self, cr, user, context=None):
         res = self._columns.keys()
@@ -5260,7 +5257,7 @@ class BaseModel(object):
 
     #
     # New records - represent records that do not exist in the database yet;
-    # they are used to compute default values and perform onchanges.
+    # they are used to perform onchanges.
     #
 
     @api.model