diff options
Diffstat (limited to 'odoo/fields.py')
| -rw-r--r-- | odoo/fields.py | 172 |
1 files changed, 99 insertions, 73 deletions
diff --git a/odoo/fields.py b/odoo/fields.py index 214d1812b21..1267e33a217 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -23,8 +23,8 @@ except ImportError: import psycopg2 from .sql_db import LazyCursor -from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar,\ - ustr, OrderedSet, pycompat, sql, date_utils, unique +from .tools import float_repr, float_round, frozendict, html_sanitize, human_size, pg_varchar, \ + ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator from .tools import DEFAULT_SERVER_DATE_FORMAT as DATE_FORMAT from .tools import DEFAULT_SERVER_DATETIME_FORMAT as DATETIME_FORMAT from .tools.translate import html_translate, _ @@ -1019,11 +1019,17 @@ class Field(MetaField('DummyField', (object,), {})): # determine dependent fields spec = self.modified_draft(record) - # set value in cache, inverse field, and mark record as dirty + # set value in cache record.env.cache.set(record, self, value) - if env.in_onchange: - for invf in record._field_inverses[self]: - invf._update(record[self.name], record) + + if not record.id or env.in_onchange: + # set inverse fields on new records in the comodel + if self.relational: + inv_recs = record[self.name].filtered(lambda r: not r.id) + if inv_recs: + for invf in record._field_inverses[self]: + invf._update(inv_recs, record) + # mark field as dirty env.dirty[record].add(self.name) # determine more dependent fields, and invalidate them @@ -1125,6 +1131,18 @@ class Field(MetaField('DummyField', (object,), {})): self.compute_value(record) return + origin = record._origin + if origin: + # retrieve value from original record + value = self.convert_to_cache(origin[self.name], record) + return record.env.cache.set(record, self, value) + + if self.type == 'many2one' and self.delegate: + # special case: parent records are new as well + parent = record.env[self.comodel_name].new() + value = self.convert_to_cache(parent, record) + return record.env.cache.set(record, self, value) + null = self.convert_to_cache(False, record, validate=False) record.env.cache.set_special(record, self, lambda: null) @@ -1157,14 +1175,14 @@ class Field(MetaField('DummyField', (object,), {})): # Notification when fields are modified # - def modified_draft(self, records): + def modified_draft(self, record): """ Same as :meth:`modified`, but in draft mode. """ - env = records.env + env = record.env # invalidate the fields on the records in cache that depend on - # ``records``, except fields currently being computed + # ``record``, except fields currently being computed spec = [] - for field, path in records._field_triggers[self]: + for field, path in record._field_triggers[self]: if not field.compute: # Note: do not invalidate non-computed fields. Such fields may # require invalidation in general (like *2many fields with @@ -1172,16 +1190,12 @@ class Field(MetaField('DummyField', (object,), {})): # we would simply lose their values during an onchange! continue - target = env[field.model_name] - protected = env.protected(field) - if path == 'id' and field.model_name == records._name: - target = records - protected - elif path and env.in_onchange: - target = (env.cache.get_records(target, field) - protected).filtered( - lambda rec: rec if path == 'id' else rec._mapped_cache(path) & records - ) + if path == 'id' and field.model_name == record._name: + target = record else: - target = env.cache.get_records(target, field) - protected + target = env.cache.get_records(env[field.model_name], field) + target = target.filtered(lambda t: not t.id) + target -= env.protected(field) if target: spec.append((field, target._ids)) @@ -2173,22 +2187,28 @@ class Many2one(_Relational): def convert_to_cache(self, value, record, validate=True): # cache format: tuple(ids) if type(value) in IdType: - return (value,) + ids = (value,) elif isinstance(value, BaseModel): - if not validate or (value._name == self.comodel_name and len(value) <= 1): - return value._ids - raise ValueError("Wrong value for %s: %r" % (self, value)) + if validate and (value._name != self.comodel_name or len(value) > 1): + raise ValueError("Wrong value for %s: %r" % (self, value)) + ids = value._ids elif isinstance(value, tuple): # value is either a pair (id, name), or a tuple of ids - return value[:1] + ids = value[:1] elif isinstance(value, dict): - return record.env[self.comodel_name].new(value)._ids + ids = record.env[self.comodel_name].new(value)._ids else: - return () + ids = () + + if self.delegate and record and not record.id: + # the parent record of a new record is a new record + ids = tuple(it and NewId(it) for it in ids) + + return ids def convert_to_record(self, value, record): # use registry to avoid creating a recordset for the model - prefetch_ids = PrefetchValueIds(record, self) + prefetch_ids = IterableGenerator(prefetch_value_ids, record, self) return record.pool[self.comodel_name]._browse(record.env, value, prefetch_ids) def convert_to_read(self, value, record, use_name_get=True): @@ -2274,45 +2294,56 @@ class _RelationalMulti(_Relational): def convert_to_cache(self, value, record, validate=True): # cache format: tuple(ids) if isinstance(value, BaseModel): - if not validate or (value._name == self.comodel_name): - return value._ids + if validate and value._name != self.comodel_name: + raise ValueError("Wrong value for %s: %s" % (self, value)) + ids = value._ids + if record and not record.id: + # x2many field value of new record is new records + ids = tuple(it and NewId(it) for it in ids) + return ids + elif isinstance(value, (list, tuple)): # value is a list/tuple of commands, dicts or record ids comodel = record.env[self.comodel_name] + # if record is new, the field's value is new records + if record and not record.id: + browse = lambda it: comodel.browse([it and NewId(it)]) + else: + browse = comodel.browse # determine the value ids ids = OrderedSet(record[self.name]._ids) # modify ids with the commands for command in value: if isinstance(command, (tuple, list)): if command[0] == 0: - ids.add(comodel.new(command[2], command[1]).id) + ids.add(comodel.new(command[2], ref=command[1]).id) elif command[0] == 1: - comodel.browse(command[1]).update(command[2]) - ids.add(command[1]) - elif command[0] == 2: - # note: the record will be deleted by write() - ids.discard(command[1]) - elif command[0] == 3: - ids.discard(command[1]) + line = browse(command[1]) + line.update(command[2]) + ids.add(line.id) + elif command[0] in (2, 3): + ids.discard(browse(command[1]).id) elif command[0] == 4: - ids.add(command[1]) + ids.add(browse(command[1]).id) elif command[0] == 5: ids.clear() elif command[0] == 6: - ids = OrderedSet(command[2]) + ids = OrderedSet(browse(it).id for it in command[2]) elif isinstance(command, dict): ids.add(comodel.new(command).id) else: - ids.add(command) + ids.add(browse(command).id) # return result as a tuple return tuple(ids) + elif not value: return () + raise ValueError("Wrong value for %s: %s" % (self, value)) def convert_to_record(self, value, record): # use registry to avoid creating a recordset for the model - prefetch_ids = PrefetchValueIds(record, self) + prefetch_ids = IterableGenerator(prefetch_value_ids, record, self) return record.pool[self.comodel_name]._browse(record.env, value, prefetch_ids) def convert_to_read(self, value, record, use_name_get=True): @@ -2322,16 +2353,23 @@ class _RelationalMulti(_Relational): # make result with new and existing records result = [(6, 0, [])] for record in value: - if not record.id: - values = {name: record[name] for name in record._cache} - values = record._convert_to_write(values) + origin = record._origin + if not origin: + values = record._convert_to_write({ + name: record[name] + for name in record._cache + }) result.append((0, 0, values)) - elif record._is_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[0][2].append(record.id) + result[0][2].append(origin.id) + if record != origin: + values = record._convert_to_write({ + name: record[name] + for name in record._cache + if record[name] != origin[name] + }) + if values: + result.append((1, origin.id, values)) return result def convert_to_onchange(self, value, record, names): @@ -2350,12 +2388,12 @@ class _RelationalMulti(_Relational): result = [(5,)] for record in value: - if not record.id: + if not record.id and not record._origin: result.append((0, record.id.ref or 0, vals[record])) elif vals[record]: - result.append((1, record.id, vals[record])) + result.append((1, record._origin.id, vals[record])) else: - result.append((4, record.id)) + result.append((4, record._origin.id)) return result def convert_to_export(self, value, record): @@ -2369,9 +2407,8 @@ class _RelationalMulti(_Relational): super(_RelationalMulti, self)._compute_related(records) if self.related_sudo: # determine which records in the relation are actually accessible - target = records[self.name] - target_ids = set(target.search([('id', 'in', target.ids)]).ids) - accessible = lambda target: target.id in target_ids + line_ids = set(records[self.name]._filter_access_rules('read')._ids) + accessible = lambda line: line.id in line_ids # filter values to keep the accessible records only for record in records: record[self.name] = record[self.name].filtered(accessible) @@ -2873,27 +2910,16 @@ class Id(Field): raise TypeError("field 'id' cannot be assigned") -class PrefetchValueIds(object): - """ An iterable on the ids of the cached values of a relational field for - the prefetch set of a record. +def prefetch_value_ids(record, field): + """ Return an iterator over the ids of the cached values of a relational + field for the prefetch set of a record. """ - __slots__ = ('_record', '_field') - - def __init__(self, record, field): - self._record = record - self._field = field - - def __iter__(self): - record = self._record - records = record.browse(record._prefetch_ids) - return unique( - id_ - for ids in record.env.cache.get_values(records, self._field, ()) - for id_ in ids - ) + records = record.browse(record._prefetch_ids) + ids_seq = record.env.cache.get_values(records, field, ()) + return unique(id_ for ids in ids_seq for id_ in ids) # imported here to avoid dependency cycle issues from odoo import SUPERUSER_ID from .exceptions import AccessError, MissingError, UserError -from .models import check_pg_name, BaseModel, IdType +from .models import check_pg_name, BaseModel, NewId, IdType |
