[REF] fields: change API and semantics of converter methods

The cache data format has been split between a cache format and a record format
(returned by accessing a field on a record).  We have the following converters:

                                   ANY FORMAT
                                       |
                                       | convert_to_cache
                                       v
                                  CACHE FORMAT
                                       |
                                       | convert_to_record
                                       v
                                 RECORD FORMAT
                                       |
                                       | convert_to_read
                                       | convert_to_write
                                       | convert_to_onchange
                                       | convert_to_export
                                       | convert_to_display_name
                                       v
                                 OTHER FORMATS

Modify the API of the converter methods to make them uniform (pass value and
record), and adapt their documentation.
This commit is contained in:
Raphael Collet
2016-04-25 13:37:11 +02:00
parent cb8d432bb2
commit 6037f3a56d
5 changed files with 98 additions and 98 deletions
@@ -41,7 +41,6 @@ class SaleOrderEventRegistration(models.TransientModel):
'sale_order_line_id': so_line.id,
}])
res['event_registration_ids'] = attendee_list
res = self._convert_to_cache(res, validate=False)
res = self._convert_to_write(res)
return res
+1 -1
View File
@@ -398,7 +398,7 @@ class MailComposer(models.TransientModel):
# ORM handle the assignation of command list on new onchange (api.v8),
# this force the complete replacement of x2many field with
# command and is compatible with onchange api.v7
values = self._convert_to_write(self._convert_to_cache(values))
values = self._convert_to_write(values)
return {'value': values}
+1 -1
View File
@@ -402,7 +402,7 @@ class IrValues(models.Model):
# FIXME: needs cleanup
try:
action_def = {
field: action._fields[field].convert_to_read(action[field])
field: action._fields[field].convert_to_read(action[field], action)
for field in fields
}
if action._name in ('ir.actions.report.xml', 'ir.actions.act_window'):
+84 -85
View File
@@ -81,7 +81,7 @@ def default_new_to_old(field, value):
""" Convert the new-API default ``value`` to the old API. """
if callable(value):
from openerp import api
return api.model(lambda model: field.convert_to_write(value(model)))
return api.model(lambda model: field.convert_to_write(value(model), model))
else:
return value
@@ -758,57 +758,56 @@ class Field(object):
# Conversion of values
#
def null(self, env):
""" return the null value for this field in the given environment """
def null(self, record):
""" Return the null value for this field in the record format. """
return False
def convert_to_cache(self, value, record, validate=True):
""" convert ``value`` to the cache level in ``env``; ``value`` may come from
an assignment, or have the format of methods :meth:`BaseModel.read`
or :meth:`BaseModel.write`
""" Convert ``value`` to the cache format; ``value`` may come from an
assignment, or have the format of methods :meth:`BaseModel.read` 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
``value`` will be performed
:param bool validate: when True, field-specific validation of ``value``
will be performed
"""
return value
def convert_to_read(self, value, use_name_get=True):
""" convert ``value`` from the cache to a value as returned by method
:meth:`BaseModel.read`
def convert_to_record(self, value, record):
""" Convert ``value`` from the cache format to the record format. """
return value
:param bool use_name_get: when True, value's diplay name will
be computed using :meth:`BaseModel.name_get`, if relevant
for the field
def convert_to_read(self, value, record, use_name_get=True):
""" Convert ``value`` from the record format to the format returned by
method :meth:`BaseModel.read`.
:param bool use_name_get: when True, the value's display name will be
computed using :meth:`BaseModel.name_get`, if relevant for the field
"""
return False if value is None else value
def convert_to_write(self, value):
""" convert ``value`` from the cache to a valid value for method
:meth:`BaseModel.write`.
def convert_to_write(self, value, record):
""" Convert ``value`` from the record format to the format of method
:meth:`BaseModel.write`.
"""
return self.convert_to_read(value)
return self.convert_to_read(value, record)
def convert_to_onchange(self, value, fnames=None):
""" convert ``value`` from the cache to a value as returned by method
:meth:`BaseModel.onchange`.
def convert_to_onchange(self, value, record, fnames=()):
""" Convert ``value`` from the record format to the format returned by
method :meth:`BaseModel.onchange`.
:param fnames: an optional collection of field names to convert
(for relational fields only)
:param fnames: an optional collection of field names to convert
(for relational fields only)
"""
return self.convert_to_read(value)
return self.convert_to_read(value, record)
def convert_to_export(self, value, env):
""" convert ``value`` from the cache to a valid value for export. The
parameter ``env`` is given for managing translations.
"""
def convert_to_export(self, value, record):
""" Convert ``value`` from the record format to the export format. """
if not value:
return ''
return value if env.context.get('export_raw_data') else ustr(value)
return value if record._context.get('export_raw_data') else ustr(value)
def convert_to_display_name(self, value, record=None):
""" convert ``value`` from the cache to a suitable display name. """
def convert_to_display_name(self, value, record):
""" Convert ``value`` from the record format to a suitable display name. """
return ustr(value)
############################################################################
@@ -823,26 +822,22 @@ class Field(object):
if not record:
# null record -> return the null value for this field
return self.null(record.env)
return self.null(record)
# only a single record may be accessed
record.ensure_one()
try:
return record._cache[self]
value = record._cache[self]
except KeyError:
pass
# cache miss, determine value and retrieve it
if record.id:
self.determine_value(record)
else:
self.determine_draft_value(record)
value = record._cache[self]
# cache miss, retrieve value
if record.id:
# normal record -> read or compute value for this field
self.determine_value(record)
else:
# 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]
return self.convert_to_record(value, record)
def __set__(self, record, value):
""" set the value of field ``self`` on ``record`` """
@@ -872,7 +867,8 @@ class Field(object):
else:
# simply write to the database, and update cache
record.write({self.name: self.convert_to_write(value)})
write_value = self.convert_to_write(self.convert_to_record(value, record), record)
record.write({self.name: write_value})
record._cache[self] = value
############################################################################
@@ -885,7 +881,8 @@ class Field(object):
# initialize the fields to their corresponding null value in cache
computed = records._field_computed[self]
for field in computed:
records._cache[field] = field.null(records.env)
for record in records:
record._cache[field] = field.convert_to_cache(False, record, validate=False)
records.env.computed[field].update(records._ids)
if isinstance(self.compute, basestring):
getattr(records, self.compute)()
@@ -947,14 +944,15 @@ class Field(object):
else:
# this is a non-stored non-computed field
record._cache[self] = self.null(env)
record._cache[self] = self.convert_to_cache(False, record, validate=False)
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))
null = self.convert_to_cache(False, record, validate=False)
record._cache[self] = SpecialValue(null)
def determine_inverse(self, records):
""" Given the value of ``self`` on ``records``, inverse the computation. """
@@ -1047,8 +1045,8 @@ class Boolean(Field):
def convert_to_cache(self, value, record, validate=True):
return bool(value)
def convert_to_export(self, value, env):
if env.context.get('export_raw_data'):
def convert_to_export(self, value, record):
if record._context.get('export_raw_data'):
return value
return ustr(value)
@@ -1068,7 +1066,7 @@ class Integer(Field):
return value.get('id', False)
return int(value or 0)
def convert_to_read(self, value, use_name_get=True):
def convert_to_read(self, value, record, use_name_get=True):
# Integer values greater than 2^31-1 are not supported in pure XMLRPC,
# so we have to pass them as floats :-(
if value and value > xmlrpclib.MAXINT:
@@ -1079,9 +1077,9 @@ class Integer(Field):
# special case, when an integer field is used as inverse for a one2many
records._cache[self] = value.id or 0
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
if value or value == 0:
return value if env.context.get('export_raw_data') else ustr(value)
return value if record._context.get('export_raw_data') else ustr(value)
return ''
@@ -1125,9 +1123,9 @@ class Float(Field):
digits = self.digits
return float_round(value, precision_digits=digits[1]) if digits else value
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
if value or value == 0.0:
return value if env.context.get('export_raw_data') else ustr(value)
return value if record._context.get('export_raw_data') else ustr(value)
return ''
@@ -1365,10 +1363,10 @@ class Date(Field):
return value[:DATE_LENGTH]
return self.to_string(value)
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
if not value:
return ''
return self.from_string(value) if env.context.get('export_raw_data') else ustr(value)
return self.from_string(value) if record._context.get('export_raw_data') else ustr(value)
class Datetime(Field):
@@ -1436,12 +1434,12 @@ class Datetime(Field):
return value
return self.to_string(value)
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
if not value:
return ''
return self.from_string(value) if env.context.get('export_raw_data') else ustr(value)
return self.from_string(value) if record._context.get('export_raw_data') else ustr(value)
def convert_to_display_name(self, value, record=None):
def convert_to_display_name(self, value, record):
assert record, 'Record expected'
return Datetime.to_string(Datetime.context_timestamp(record, Datetime.from_string(value)))
@@ -1547,11 +1545,11 @@ class Selection(Field):
return False
raise ValueError("Wrong value for %s: %r" % (self, value))
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
if not isinstance(self.selection, list):
# FIXME: this reproduces an existing buggy behavior!
return value if value else ''
for item in self._description_selection(env):
for item in self._description_selection(record.env):
if item[0] == value:
return item[1]
return False
@@ -1582,13 +1580,13 @@ class Reference(Selection):
return False
raise ValueError("Wrong value for %s: %r" % (self, value))
def convert_to_read(self, value, use_name_get=True):
def convert_to_read(self, value, record, use_name_get=True):
return "%s,%s" % (value._name, value.id) if value else False
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
return value.name_get()[0][1] if value else ''
def convert_to_display_name(self, value, record=None):
def convert_to_display_name(self, value, record):
return ustr(value and value.display_name)
@@ -1627,8 +1625,8 @@ class _Relational(Field):
_column_domain = property(attrgetter('domain'))
_column_context = property(attrgetter('context'))
def null(self, env):
return env[self.comodel_name]
def null(self, record):
return record.env[self.comodel_name]
def modified(self, records):
# Invalidate cache for inverse fields, too. Note that the recomputation
@@ -1699,9 +1697,9 @@ class Many2one(_Relational):
elif isinstance(value, dict):
return record.env[self.comodel_name].new(value)
else:
return self.null(record.env)
return record.env[self.comodel_name]
def convert_to_read(self, value, use_name_get=True):
def convert_to_read(self, value, record, use_name_get=True):
if use_name_get and value:
# evaluate name_get() as superuser, because the visibility of a
# many2one field value (id and name) depends on the current record's
@@ -1718,13 +1716,13 @@ class Many2one(_Relational):
else:
return value.id
def convert_to_write(self, value):
def convert_to_write(self, value, record):
return value.id
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
return value.name_get()[0][1] if value else ''
def convert_to_display_name(self, value, record=None):
def convert_to_display_name(self, value, record):
return ustr(value.display_name)
@@ -1790,13 +1788,13 @@ class _RelationalMulti(_Relational):
# return result as a recordset
return comodel.browse(list(ids))
elif not value:
return self.null(record.env)
return record.env[self.comodel_name]
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, record, use_name_get=True):
return value.ids
def convert_to_write(self, value):
def convert_to_write(self, value, record):
# make result with new and existing records
result = [(5,)]
for record in value:
@@ -1805,21 +1803,22 @@ class _RelationalMulti(_Relational):
values = record._convert_to_write(values)
result.append((0, 0, values))
elif record._is_dirty():
values = {k: record._cache[k] for k in record._get_dirty()}
values = {name: record[name] for name in record._get_dirty()}
values = record._convert_to_write(values)
result.append((1, record.id, values))
else:
result.append((4, record.id))
return result
def convert_to_onchange(self, value, fnames=None):
def convert_to_onchange(self, value, record, fnames=()):
# return the recordset value as a list of commands; the commands may
# give all fields values, the client is responsible for figuring out
# which fields are actually dirty
fields = [(name, value._fields[name]) for name in (fnames or []) if name != 'id']
converters = [(name, value._fields[name].convert_to_onchange)
for name in fnames if name != 'id']
result = [(5,)]
for record in value:
vals = {name: field.convert_to_onchange(record[name]) for name, field in fields}
vals = {name: convert(record[name], record) for name, convert in converters}
if not record.id:
result.append((0, 0, vals))
elif vals:
@@ -1828,10 +1827,10 @@ class _RelationalMulti(_Relational):
result.append((4, record.id))
return result
def convert_to_export(self, value, env):
def convert_to_export(self, value, record):
return ','.join(name for id, name in value.name_get()) if value else ''
def convert_to_display_name(self, value, record=None):
def convert_to_display_name(self, value, record):
raise NotImplementedError()
def _compute_related(self, records):
@@ -1900,11 +1899,11 @@ class One2many(_RelationalMulti):
_column_auto_join = property(attrgetter('auto_join'))
_column_limit = property(attrgetter('limit'))
def convert_to_onchange(self, value, fnames=None):
def convert_to_onchange(self, value, record, fnames=()):
if fnames:
# do not serialize self's inverse field
fnames = [name for name in fnames if name != self.inverse_name]
return super(One2many, self).convert_to_onchange(value, fnames)
return super(One2many, self).convert_to_onchange(value, record, fnames)
class Many2many(_RelationalMulti):
+12 -10
View File
@@ -945,7 +945,7 @@ class BaseModel(object):
# this part could be simpler, but it has to be done this way
# in order to reproduce the former behavior
if not isinstance(value, BaseModel):
current[i] = field.convert_to_export(value, self.env)
current[i] = field.convert_to_export(value, record)
else:
primary_done.append(name)
@@ -1383,7 +1383,6 @@ class BaseModel(object):
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
@@ -3393,7 +3392,7 @@ class BaseModel(object):
try:
values = {'id': record.id}
for name, field in name_fields:
values[name] = field.convert_to_read(record[name], use_name_get)
values[name] = field.convert_to_read(record[name], record, use_name_get)
result.append(values)
except MissingError:
pass
@@ -5650,7 +5649,10 @@ class BaseModel(object):
result = {}
for name, value in values.iteritems():
if name in fields:
value = fields[name].convert_to_write(value)
field = fields[name]
value = field.convert_to_cache(value, self, validate=False)
value = field.convert_to_record(value, self)
value = field.convert_to_write(value, self)
if not isinstance(value, NewId):
result[name] = value
return result
@@ -5696,8 +5698,8 @@ class BaseModel(object):
recs = self
for name in name_seq.split('.'):
field = recs._fields[name]
null = field.null(self.env)
recs = recs.mapped(lambda rec: rec._cache.get(field, null))
null = field.convert_to_cache(False, self, validate=False)
recs = recs.mapped(lambda rec: field.convert_to_record(rec._cache.get(field, null), rec))
return recs
def filtered(self, func):
@@ -6111,9 +6113,9 @@ class BaseModel(object):
def __init__(self, record):
self._record = record
def __getitem__(self, name):
field = self._record._fields[name]
value = self._record[name]
return field.convert_to_write(value)
record = self._record
field = record._fields[name]
return field.convert_to_write(record[name], record)
def __getattr__(self, name):
return self[name]
@@ -6228,7 +6230,7 @@ class BaseModel(object):
# collect values from dirty fields
result['value'] = {
name: self._fields[name].convert_to_onchange(record[name], subfields.get(name))
name: self._fields[name].convert_to_onchange(record[name], record, subfields[name])
for name in dirty
}