[FIX] fields: convert_to_cache() on *2many fields must take record's current value

The existing code was buggy when writing on *2many fields with a list of
commands: the value was converted for the cache, but taking an empty recordset
as the current value of the field.
This commit is contained in:
Raphael Collet 2014-07-09 17:06:29 +02:00
parent 836b0f864e
commit 4c18c5fb6e
2 changed files with 50 additions and 38 deletions

View File

@ -609,11 +609,13 @@ class Field(object):
""" return the null value for this field in the given environment """ """ return the null value for this field in the given environment """
return False return False
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
""" convert `value` to the cache level in `env`; `value` may come from """ convert `value` to the cache level in `env`; `value` may come from
an assignment, or have the format of methods :meth:`BaseModel.read` an assignment, or have the format of methods :meth:`BaseModel.read`
or :meth:`BaseModel.write` or :meth:`BaseModel.write`
:param record: the target record for the assignment, or an empty recordset
:param bool validate: when True, field-specific validation of :param bool validate: when True, field-specific validation of
`value` will be performed `value` will be performed
""" """
@ -698,7 +700,7 @@ class Field(object):
record.ensure_one() record.ensure_one()
# adapt value to the cache level # adapt value to the cache level
value = self.convert_to_cache(value, env) value = self.convert_to_cache(value, record)
if env.in_draft or not record.id: if env.in_draft or not record.id:
# determine dependent fields # determine dependent fields
@ -861,7 +863,7 @@ class Boolean(Field):
""" Boolean field. """ """ Boolean field. """
type = 'boolean' type = 'boolean'
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
return bool(value) return bool(value)
def convert_to_export(self, value, env): def convert_to_export(self, value, env):
@ -874,7 +876,7 @@ class Integer(Field):
""" Integer field. """ """ Integer field. """
type = 'integer' type = 'integer'
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
return int(value or 0) return int(value or 0)
def convert_to_read(self, value, use_name_get=True): def convert_to_read(self, value, use_name_get=True):
@ -914,7 +916,7 @@ class Float(Field):
_column_digits = property(lambda self: not callable(self._digits) and self._digits) _column_digits = property(lambda self: not callable(self._digits) and self._digits)
_column_digits_compute = property(lambda self: callable(self._digits) and self._digits) _column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
# apply rounding here, otherwise value in cache may be wrong! # apply rounding here, otherwise value in cache may be wrong!
if self.digits: if self.digits:
return float_round(float(value or 0.0), precision_digits=self.digits[1]) return float_round(float(value or 0.0), precision_digits=self.digits[1])
@ -948,7 +950,7 @@ class Char(_String):
_related_size = property(attrgetter('size')) _related_size = property(attrgetter('size'))
_description_size = property(attrgetter('size')) _description_size = property(attrgetter('size'))
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
return bool(value) and ustr(value)[:self.size] return bool(value) and ustr(value)[:self.size]
@ -962,7 +964,7 @@ class Text(_String):
""" """
type = 'text' type = 'text'
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
return bool(value) and ustr(value) return bool(value) and ustr(value)
@ -970,7 +972,7 @@ class Html(_String):
""" Html field. """ """ Html field. """
type = 'html' type = 'html'
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
return bool(value) and html_sanitize(value) return bool(value) and html_sanitize(value)
@ -1019,7 +1021,7 @@ class Date(Field):
""" Convert a :class:`date` value into the format expected by the ORM. """ """ Convert a :class:`date` value into the format expected by the ORM. """
return value.strftime(DATE_FORMAT) return value.strftime(DATE_FORMAT)
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
if not value: if not value:
return False return False
if isinstance(value, basestring): if isinstance(value, basestring):
@ -1084,7 +1086,7 @@ class Datetime(Field):
""" Convert a :class:`datetime` value into the format expected by the ORM. """ """ Convert a :class:`datetime` value into the format expected by the ORM. """
return value.strftime(DATETIME_FORMAT) return value.strftime(DATETIME_FORMAT)
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
if not value: if not value:
return False return False
if isinstance(value, basestring): if isinstance(value, basestring):
@ -1164,10 +1166,10 @@ class Selection(Field):
selection = selection(env[self.model_name]) selection = selection(env[self.model_name])
return [value for value, _ in selection] return [value for value, _ in selection]
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
if not validate: if not validate:
return value or False return value or False
if value in self.get_values(env): if value in self.get_values(record.env):
return value return value
elif not value: elif not value:
return False return False
@ -1204,14 +1206,14 @@ class Reference(Selection):
_column_size = property(attrgetter('size')) _column_size = property(attrgetter('size'))
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
if isinstance(value, BaseModel): if isinstance(value, BaseModel):
if ((not validate or value._name in self.get_values(env)) if ((not validate or value._name in self.get_values(record.env))
and len(value) <= 1): and len(value) <= 1):
return value.with_env(env) or False return value.with_env(record.env) or False
elif isinstance(value, basestring): elif isinstance(value, basestring):
res_model, res_id = value.split(',') res_model, res_id = value.split(',')
return env[res_model].browse(int(res_id)) return record.env[res_model].browse(int(res_id))
elif not value: elif not value:
return False return False
raise ValueError("Wrong value for %s: %r" % (self, value)) raise ValueError("Wrong value for %s: %r" % (self, value))
@ -1302,19 +1304,19 @@ class Many2one(_Relational):
""" Update the cached value of `self` for `records` with `value`. """ """ Update the cached value of `self` for `records` with `value`. """
records._cache[self] = value records._cache[self] = value
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
if isinstance(value, (NoneType, int)): if isinstance(value, (NoneType, int)):
return env[self.comodel_name].browse(value) return record.env[self.comodel_name].browse(value)
if isinstance(value, BaseModel): if isinstance(value, BaseModel):
if value._name == self.comodel_name and len(value) <= 1: if value._name == self.comodel_name and len(value) <= 1:
return value.with_env(env) return value.with_env(record.env)
raise ValueError("Wrong value for %s: %r" % (self, value)) raise ValueError("Wrong value for %s: %r" % (self, value))
elif isinstance(value, tuple): elif isinstance(value, tuple):
return env[self.comodel_name].browse(value[0]) return record.env[self.comodel_name].browse(value[0])
elif isinstance(value, dict): elif isinstance(value, dict):
return env[self.comodel_name].new(value) return record.env[self.comodel_name].new(value)
else: else:
return env[self.comodel_name].browse(value) return record.env[self.comodel_name].browse(value)
def convert_to_read(self, value, use_name_get=True): def convert_to_read(self, value, use_name_get=True):
if use_name_get and value: if use_name_get and value:
@ -1355,27 +1357,30 @@ class _RelationalMulti(_Relational):
for record in records: for record in records:
record._cache[self] = record[self.name] | value record._cache[self] = record[self.name] | value
def convert_to_cache(self, value, env, validate=True): def convert_to_cache(self, value, record, validate=True):
if isinstance(value, BaseModel): if isinstance(value, BaseModel):
if value._name == self.comodel_name: if value._name == self.comodel_name:
return value.with_env(env) return value.with_env(record.env)
elif isinstance(value, list): elif isinstance(value, list):
# value is a list of record ids or commands # value is a list of record ids or commands
result = env[self.comodel_name] if not record.id:
record = record.browse() # new record has no value
result = record[self.name]
# modify result with the commands;
# beware to not introduce duplicates in result
for command in value: for command in value:
if isinstance(command, (tuple, list)): if isinstance(command, (tuple, list)):
if command[0] == 0: if command[0] == 0:
result += result.new(command[2]) result += result.new(command[2])
elif command[0] == 1: elif command[0] == 1:
record = result.browse(command[1]) result.browse(command[1]).update(command[2])
record.update(command[2])
result += record
elif command[0] == 2: elif command[0] == 2:
pass # note: the record will be deleted by write()
result -= result.browse(command[1])
elif command[0] == 3: elif command[0] == 3:
pass result -= result.browse(command[1])
elif command[0] == 4: elif command[0] == 4:
result += result.browse(command[1]) result += result.browse(command[1]) - result
elif command[0] == 5: elif command[0] == 5:
result = result.browse() result = result.browse()
elif command[0] == 6: elif command[0] == 6:
@ -1383,10 +1388,10 @@ class _RelationalMulti(_Relational):
elif isinstance(command, dict): elif isinstance(command, dict):
result += result.new(command) result += result.new(command)
else: else:
result += result.browse(command) result += result.browse(command) - result
return result return result
elif not value: elif not value:
return self.null(env) return self.null(record.env)
raise ValueError("Wrong value for %s: %s" % (self, value)) raise ValueError("Wrong value for %s: %s" % (self, value))
def convert_to_read(self, value, use_name_get=True): def convert_to_read(self, value, use_name_get=True):

View File

@ -3627,7 +3627,8 @@ class BaseModel(object):
# put the values of pure new-style fields into cache, and inverse them # put the values of pure new-style fields into cache, and inverse them
if new_vals: if new_vals:
self._cache.update(self._convert_to_cache(new_vals)) for record in self:
record._cache.update(record._convert_to_cache(new_vals, update=True))
for key in new_vals: for key in new_vals:
self._fields[key].determine_inverse(self) self._fields[key].determine_inverse(self)
@ -5098,11 +5099,17 @@ class BaseModel(object):
context = dict(args[0] if args else self._context, **kwargs) context = dict(args[0] if args else self._context, **kwargs)
return self.with_env(self.env(context=context)) return self.with_env(self.env(context=context))
def _convert_to_cache(self, values, validate=True): def _convert_to_cache(self, values, update=False, validate=True):
""" Convert the `values` dictionary into cached values. """ """ Convert the `values` dictionary into cached values.
:param update: whether the conversion is made for updating `self`;
this is necessary for interpreting the commands of *2many fields
:param validate: whether values must be checked
"""
fields = self._fields fields = self._fields
target = self if update else self.browse()
return { return {
name: fields[name].convert_to_cache(value, self.env, validate=validate) name: fields[name].convert_to_cache(value, target, validate=validate)
for name, value in values.iteritems() for name, value in values.iteritems()
if name in fields if name in fields
} }
@ -5191,7 +5198,7 @@ class BaseModel(object):
exist in the database. exist in the database.
""" """
record = self.browse([NewId()]) record = self.browse([NewId()])
record._cache.update(self._convert_to_cache(values)) record._cache.update(record._convert_to_cache(values, update=True))
if record.env.in_onchange: if record.env.in_onchange:
# The cache update does not set inverse fields, so do it manually. # The cache update does not set inverse fields, so do it manually.