[IMP] core: better API for flush() and invalidate()
This provides a new API for those operations, in order to make the
distinction between the use cases more explicit. The former API was
using obscure parameter combinations to correspond to various cases.
In the summary below, `fnames` is an iterable of field names. If the
parameter is not given, it means "all fields" in the given context.
Note that method recompute() is now mostly private, as it should not be
used in business code.
# process pending computations and updates
records.env.flush_all() # all fields of all models
records.flush_model(fnames) # the fields of all records of the model
records.flush_recordset(fnames) # the fields of the given records
# process pending computations, became non-public methods
records.env._recompute_all() # all fields of all models
records._recompute_model(fnames) # the fields of all records of the model
records._recompute_recordset(fnames) # the fields of the given records
# invalidate the cache of fields
records.env.invalidate_all() # all fields of all models
records.invalidate_model(fnames) # the fields of all records of the model
records.invalidate_recordset(fnames) # the fields of the given records
Part-of: odoo/odoo#87527
This commit is contained in:
+18
-3
@@ -459,7 +459,7 @@ def call_kw(model, name, args, kwargs):
|
|||||||
result = _call_kw_model_create(method, model, args, kwargs)
|
result = _call_kw_model_create(method, model, args, kwargs)
|
||||||
else:
|
else:
|
||||||
result = _call_kw_multi(method, model, args, kwargs)
|
result = _call_kw_multi(method, model, args, kwargs)
|
||||||
model.flush()
|
model.env.flush_all()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -701,6 +701,21 @@ class Environment(Mapping):
|
|||||||
)
|
)
|
||||||
return self.cr.savepoint()
|
return self.cr.savepoint()
|
||||||
|
|
||||||
|
def invalidate_all(self):
|
||||||
|
""" Invalidate the cache of all records. """
|
||||||
|
self.cache.invalidate()
|
||||||
|
|
||||||
|
def _recompute_all(self):
|
||||||
|
""" Process all pending computations. """
|
||||||
|
for field in list(self.fields_to_compute()):
|
||||||
|
self[field.model_name]._recompute_field(field)
|
||||||
|
|
||||||
|
def flush_all(self):
|
||||||
|
""" Flush all pending computations and updates to the database. """
|
||||||
|
self._recompute_all()
|
||||||
|
for model_name in list(self.all.towrite):
|
||||||
|
self[model_name].flush_model()
|
||||||
|
|
||||||
def is_protected(self, field, record):
|
def is_protected(self, field, record):
|
||||||
""" Return whether `record` is protected against invalidation or
|
""" Return whether `record` is protected against invalidation or
|
||||||
recomputation for `field`.
|
recomputation for `field`.
|
||||||
@@ -834,7 +849,7 @@ class Transaction:
|
|||||||
if env.uid is not None:
|
if env.uid is not None:
|
||||||
break
|
break
|
||||||
if env_to_flush is not None:
|
if env_to_flush is not None:
|
||||||
env_to_flush['base'].flush()
|
env_to_flush.flush_all()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
""" Clear the caches and pending computations and updates in the translations. """
|
""" Clear the caches and pending computations and updates in the translations. """
|
||||||
@@ -983,7 +998,7 @@ class Cache(object):
|
|||||||
def check(self, env):
|
def check(self, env):
|
||||||
""" Check the consistency of the cache for the given environment. """
|
""" Check the consistency of the cache for the given environment. """
|
||||||
# flush fields to be recomputed before evaluating the cache
|
# flush fields to be recomputed before evaluating the cache
|
||||||
env['res.partner'].recompute()
|
env._recompute_all()
|
||||||
|
|
||||||
# make a copy of the cache, and invalidate it
|
# make a copy of the cache, and invalidate it
|
||||||
dump = dict(self._data)
|
dump = dict(self._data)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class Populate(Command):
|
|||||||
|
|
||||||
# force the flush to make sure population time still
|
# force the flush to make sure population time still
|
||||||
# considers flushing all values to database
|
# considers flushing all values to database
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
env.cr.commit()
|
env.cr.commit()
|
||||||
|
|||||||
+2
-2
@@ -1024,7 +1024,7 @@ class Field(MetaField('DummyField', (object,), {})):
|
|||||||
@model.pool.post_init
|
@model.pool.post_init
|
||||||
def add_not_null():
|
def add_not_null():
|
||||||
# flush values before adding NOT NULL constraint
|
# flush values before adding NOT NULL constraint
|
||||||
model.flush([self.name])
|
model.flush_model([self.name])
|
||||||
model.pool.post_constraint(apply_required, model, self.name)
|
model.pool.post_constraint(apply_required, model, self.name)
|
||||||
|
|
||||||
elif not self.required and has_notnull:
|
elif not self.required and has_notnull:
|
||||||
@@ -3472,7 +3472,7 @@ class One2many(_RelationalMulti):
|
|||||||
# cache value of self is consistent
|
# cache value of self is consistent
|
||||||
inverse_field = records.pool[self.comodel_name]._fields[self.inverse_name]
|
inverse_field = records.pool[self.comodel_name]._fields[self.inverse_name]
|
||||||
if inverse_field.compute:
|
if inverse_field.compute:
|
||||||
records.env[self.comodel_name].recompute([self.inverse_name])
|
records.env[self.comodel_name]._recompute_model([self.inverse_name])
|
||||||
return super().__get__(records, owner)
|
return super().__get__(records, owner)
|
||||||
|
|
||||||
def read(self, records):
|
def read(self, records):
|
||||||
|
|||||||
+195
-102
@@ -908,7 +908,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
psycopg2.extensions.set_wait_callback(callback)
|
psycopg2.extensions.set_wait_callback(callback)
|
||||||
self.env['ir.model.data'].invalidate_cache(fnames=fields)
|
self.env['ir.model.data'].invalidate_model(fields)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(record, to_xid(record.id))
|
(record, to_xid(record.id))
|
||||||
@@ -935,7 +935,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
sub = rs[idx:idx+1000]
|
sub = rs[idx:idx+1000]
|
||||||
for rec in sub:
|
for rec in sub:
|
||||||
yield rec
|
yield rec
|
||||||
rs.invalidate_cache(ids=sub.ids)
|
sub.invalidate_recordset()
|
||||||
if not _is_toplevel_call:
|
if not _is_toplevel_call:
|
||||||
splittor = lambda rs: rs
|
splittor = lambda rs: rs
|
||||||
|
|
||||||
@@ -1060,7 +1060,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
:type data: list(list(str))
|
:type data: list(list(str))
|
||||||
:returns: {ids: list(int)|False, messages: [Message][, lastrow: int]}
|
:returns: {ids: list(int)|False, messages: [Message][, lastrow: int]}
|
||||||
"""
|
"""
|
||||||
self.flush()
|
self.env.flush_all()
|
||||||
|
|
||||||
# determine values of mode, current_module and noupdate
|
# determine values of mode, current_module and noupdate
|
||||||
mode = self._context.get('mode', 'init')
|
mode = self._context.get('mode', 'init')
|
||||||
@@ -1171,15 +1171,15 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
|
|
||||||
# make 'flush' available to the methods below, in the case where XMLID
|
# make 'flush' available to the methods below, in the case where XMLID
|
||||||
# resolution fails, for instance
|
# resolution fails, for instance
|
||||||
flush_self = self.with_context(import_flush=flush, import_cache=LRU(1024))
|
flush_recordset = self.with_context(import_flush=flush, import_cache=LRU(1024))
|
||||||
|
|
||||||
# TODO: break load's API instead of smuggling via context?
|
# TODO: break load's API instead of smuggling via context?
|
||||||
limit = self._context.get('_import_limit')
|
limit = self._context.get('_import_limit')
|
||||||
if limit is None:
|
if limit is None:
|
||||||
limit = float('inf')
|
limit = float('inf')
|
||||||
extracted = flush_self._extract_records(fields, data, log=messages.append, limit=limit)
|
extracted = flush_recordset._extract_records(fields, data, log=messages.append, limit=limit)
|
||||||
|
|
||||||
converted = flush_self._convert_records(extracted, log=messages.append)
|
converted = flush_recordset._convert_records(extracted, log=messages.append)
|
||||||
|
|
||||||
info = {'rows': {'to': -1}}
|
info = {'rows': {'to': -1}}
|
||||||
for id, xid, record, info in converted:
|
for id, xid, record, info in converted:
|
||||||
@@ -2837,7 +2837,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
WHERE row.id = comp.id
|
WHERE row.id = comp.id
|
||||||
""".format(table=self._table, parent=self._parent_name)
|
""".format(table=self._table, parent=self._parent_name)
|
||||||
self.env.cr.execute(query)
|
self.env.cr.execute(query)
|
||||||
self.invalidate_cache(['parent_path'])
|
self.invalidate_model(['parent_path'])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_removed_columns(self, log=False):
|
def _check_removed_columns(self, log=False):
|
||||||
@@ -3419,7 +3419,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
|
|
||||||
# if a read() follows a write(), we must flush updates, as read() will
|
# if a read() follows a write(), we must flush updates, as read() will
|
||||||
# fetch from database and overwrites the cache (`test_update_with_id`)
|
# fetch from database and overwrites the cache (`test_update_with_id`)
|
||||||
self.flush(fields, self)
|
self.flush_recordset(fields)
|
||||||
|
|
||||||
field_names = []
|
field_names = []
|
||||||
inherited_field_names = []
|
inherited_field_names = []
|
||||||
@@ -3761,7 +3761,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
|
|
||||||
# mark fields that depend on 'self' to recompute them after 'self' has
|
# mark fields that depend on 'self' to recompute them after 'self' has
|
||||||
# been deleted (like updating a sum of lines after deleting one line)
|
# been deleted (like updating a sum of lines after deleting one line)
|
||||||
self.flush()
|
self.env.flush_all()
|
||||||
self.modified(self._fields, before=True)
|
self.modified(self._fields, before=True)
|
||||||
|
|
||||||
with self.env.norecompute():
|
with self.env.norecompute():
|
||||||
@@ -3819,14 +3819,14 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
|
|
||||||
# invalidate the *whole* cache, since the orm does not handle all
|
# invalidate the *whole* cache, since the orm does not handle all
|
||||||
# changes made in the database, like cascading delete!
|
# changes made in the database, like cascading delete!
|
||||||
self.invalidate_cache()
|
self.env.invalidate_all()
|
||||||
if ir_model_data_unlink:
|
if ir_model_data_unlink:
|
||||||
ir_model_data_unlink.unlink()
|
ir_model_data_unlink.unlink()
|
||||||
if ir_attachment_unlink:
|
if ir_attachment_unlink:
|
||||||
ir_attachment_unlink.unlink()
|
ir_attachment_unlink.unlink()
|
||||||
# DLE P93: flush after the unlink, for recompute fields depending on
|
# DLE P93: flush after the unlink, for recompute fields depending on
|
||||||
# the modified of the unlink
|
# the modified of the unlink
|
||||||
self.flush()
|
self.env.flush_all()
|
||||||
|
|
||||||
# auditing: deletions are infrequent and leave no trace in the database
|
# auditing: deletions are infrequent and leave no trace in the database
|
||||||
_unlink.info('User #%s deleted %s records with IDs: %r', self._uid, self._name, self.ids)
|
_unlink.info('User #%s deleted %s records with IDs: %r', self._uid, self._name, self.ids)
|
||||||
@@ -3943,7 +3943,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
for field in protected
|
for field in protected
|
||||||
if field.compute and field.name not in vals]
|
if field.compute and field.name not in vals]
|
||||||
if to_compute:
|
if to_compute:
|
||||||
self.recompute(to_compute, self)
|
self._recompute_recordset(to_compute)
|
||||||
|
|
||||||
# protect fields being written against recomputation
|
# protect fields being written against recomputation
|
||||||
with env.protecting(protected, self):
|
with env.protecting(protected, self):
|
||||||
@@ -3992,7 +3992,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
self.modified(vals)
|
self.modified(vals)
|
||||||
|
|
||||||
if self._parent_store and self._parent_name in vals:
|
if self._parent_store and self._parent_name in vals:
|
||||||
self.flush([self._parent_name])
|
self.flush_model([self._parent_name])
|
||||||
|
|
||||||
# validate non-inversed fields first
|
# validate non-inversed fields first
|
||||||
inverse_fields = [f.name for fs in determine_inverses.values() for f in fs]
|
inverse_fields = [f.name for fs in determine_inverses.values() for f in fs]
|
||||||
@@ -4840,7 +4840,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
models.append(self.env[model_name])
|
models.append(self.env[model_name])
|
||||||
|
|
||||||
for model_name, field_names in to_flush.items():
|
for model_name, field_names in to_flush.items():
|
||||||
self.env[model_name].flush(field_names)
|
self.env[model_name].flush_model(field_names)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _search(self, domain, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
|
def _search(self, domain, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
|
||||||
@@ -5067,7 +5067,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
|
|
||||||
# must ignore 'active' flag, ir.rules, etc. => direct SQL query
|
# must ignore 'active' flag, ir.rules, etc. => direct SQL query
|
||||||
cr = self._cr
|
cr = self._cr
|
||||||
self.flush([parent])
|
self.flush_model([parent])
|
||||||
query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table)
|
query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table)
|
||||||
for id in self.ids:
|
for id in self.ids:
|
||||||
current_id = id
|
current_id = id
|
||||||
@@ -5093,7 +5093,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
# field must be a many2many on itself
|
# field must be a many2many on itself
|
||||||
raise ValueError('invalid field_name: %r' % (field_name,))
|
raise ValueError('invalid field_name: %r' % (field_name,))
|
||||||
|
|
||||||
self.flush([field_name])
|
self.flush_model([field_name])
|
||||||
|
|
||||||
cr = self._cr
|
cr = self._cr
|
||||||
query = 'SELECT "%s", "%s" FROM "%s" WHERE "%s" IN %%s AND "%s" IS NOT NULL' % \
|
query = 'SELECT "%s", "%s" FROM "%s" WHERE "%s" IN %%s AND "%s" IS NOT NULL' % \
|
||||||
@@ -5775,63 +5775,92 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
:param Model records: if given (together with ``fnames``), limit the
|
:param Model records: if given (together with ``fnames``), limit the
|
||||||
processing to the given records.
|
processing to the given records.
|
||||||
"""
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"Deprecated method flush(), use flush_model(), flush_recordset() or env.flush_all() instead",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
if fnames is None:
|
||||||
|
self.env.flush_all()
|
||||||
|
elif records is None:
|
||||||
|
self.flush_model(fnames)
|
||||||
|
else:
|
||||||
|
records.flush_recordset(fnames)
|
||||||
|
|
||||||
|
def flush_model(self, fnames=None):
|
||||||
|
""" Process the pending computations and database updates on ``self``'s
|
||||||
|
model. When the parameter is given, the method guarantees that at least
|
||||||
|
the given fields are flushed to the database. More fields can be
|
||||||
|
flushed, though.
|
||||||
|
|
||||||
|
:param fnames: optional iterable of field names to flush
|
||||||
|
"""
|
||||||
|
self._recompute_model(fnames)
|
||||||
|
self._flush(fnames)
|
||||||
|
|
||||||
|
def flush_recordset(self, fnames=None):
|
||||||
|
""" Process the pending computations and database updates on the records
|
||||||
|
``self``. When the parameter is given, the method guarantees that at
|
||||||
|
least the given fields on records ``self`` are flushed to the database.
|
||||||
|
More fields and records can be flushed, though.
|
||||||
|
|
||||||
|
:param fnames: optional iterable of field names to flush
|
||||||
|
"""
|
||||||
|
self._recompute_recordset(fnames)
|
||||||
|
towrite = self.env.all.towrite.get(self._name)
|
||||||
|
if fnames is None:
|
||||||
|
must_flush = towrite and any(id_ in towrite for id_ in self._ids)
|
||||||
|
else:
|
||||||
|
must_flush = towrite and any(
|
||||||
|
fname in vals
|
||||||
|
for id_ in self._ids
|
||||||
|
for vals in towrite.get(id_, ())
|
||||||
|
for fname in fnames
|
||||||
|
)
|
||||||
|
if must_flush:
|
||||||
|
self._flush(fnames)
|
||||||
|
|
||||||
|
def _flush(self, fnames=None):
|
||||||
def process(model, id_vals):
|
def process(model, id_vals):
|
||||||
# group record ids by vals, to update in batch when possible
|
# group record ids by vals, to update in batch when possible
|
||||||
updates = defaultdict(list)
|
updates = defaultdict(list)
|
||||||
for rid, vals in id_vals.items():
|
for id_, vals in id_vals.items():
|
||||||
updates[frozendict(vals)].append(rid)
|
updates[frozendict(vals)].append(id_)
|
||||||
|
|
||||||
for vals, ids in updates.items():
|
for vals, ids in updates.items():
|
||||||
model.browse(ids)._write(vals)
|
model.browse(ids)._write(vals)
|
||||||
|
|
||||||
|
# DLE P76: test_onchange_one2many_with_domain_on_related_field
|
||||||
|
# ```
|
||||||
|
# email.important = True
|
||||||
|
# self.assertIn(email, discussion.important_emails)
|
||||||
|
# ```
|
||||||
|
# When a search on a field coming from a related occurs (the domain
|
||||||
|
# on discussion.important_emails field), make sure the related field
|
||||||
|
# is flushed
|
||||||
if fnames is None:
|
if fnames is None:
|
||||||
# flush everything
|
fields = self._fields.values()
|
||||||
self.recompute()
|
|
||||||
while self.env.all.towrite:
|
|
||||||
model_name, id_vals = self.env.all.towrite.popitem()
|
|
||||||
process(self.env[model_name], id_vals)
|
|
||||||
else:
|
else:
|
||||||
# flush self's model if any of the fields must be flushed
|
fields = [self._fields[fname] for fname in fnames]
|
||||||
self.recompute(fnames, records=records)
|
|
||||||
|
|
||||||
# check whether any of 'records' must be flushed
|
model_fields = defaultdict(list)
|
||||||
if records is not None:
|
for field in fields:
|
||||||
fnames = set(fnames)
|
model_fields[field.model_name].append(field)
|
||||||
towrite = self.env.all.towrite.get(self._name)
|
if field.related_field:
|
||||||
if not towrite or all(
|
model_fields[field.related_field.model_name].append(field.related_field)
|
||||||
fnames.isdisjoint(towrite.get(record.id, ()))
|
|
||||||
for record in records
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# DLE P76: test_onchange_one2many_with_domain_on_related_field
|
for model_name, fields_ in model_fields.items():
|
||||||
# ```
|
if any(
|
||||||
# email.important = True
|
field.name in vals
|
||||||
# self.assertIn(email, discussion.important_emails)
|
for vals in self.env.all.towrite.get(model_name, {}).values()
|
||||||
# ```
|
for field in fields_
|
||||||
# When a search on a field coming from a related occurs (the domain
|
):
|
||||||
# on discussion.important_emails field), make sure the related field
|
id_vals = self.env.all.towrite.pop(model_name)
|
||||||
# is flushed
|
process(self.env[model_name], id_vals)
|
||||||
model_fields = {}
|
|
||||||
for fname in fnames:
|
|
||||||
field = self._fields[fname]
|
|
||||||
model_fields.setdefault(field.model_name, []).append(field)
|
|
||||||
if field.related_field:
|
|
||||||
model_fields.setdefault(field.related_field.model_name, []).append(field.related_field)
|
|
||||||
for model_name, fields in model_fields.items():
|
|
||||||
if any(
|
|
||||||
field.name in vals
|
|
||||||
for vals in self.env.all.towrite.get(model_name, {}).values()
|
|
||||||
for field in fields
|
|
||||||
):
|
|
||||||
id_vals = self.env.all.towrite.pop(model_name)
|
|
||||||
process(self.env[model_name], id_vals)
|
|
||||||
|
|
||||||
# missing for one2many fields, flush their inverse
|
# flush the inverse of one2many fields, too
|
||||||
for fname in fnames:
|
for field in fields:
|
||||||
field = self._fields[fname]
|
if field.type == 'one2many' and field.inverse_name:
|
||||||
if field.type == 'one2many' and field.inverse_name:
|
self.env[field.comodel_name].flush_model([field.inverse_name])
|
||||||
self.env[field.comodel_name].flush([field.inverse_name])
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# New records - represent records that do not exist in the database yet;
|
# New records - represent records that do not exist in the database yet;
|
||||||
@@ -6099,7 +6128,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
"""
|
"""
|
||||||
warnings.warn('refresh() is deprecated method, use invalidate_cache() instead',
|
warnings.warn('refresh() is deprecated method, use invalidate_cache() instead',
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
self.invalidate_cache()
|
self.env.invalidate_all()
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def invalidate_cache(self, fnames=None, ids=None):
|
def invalidate_cache(self, fnames=None, ids=None):
|
||||||
@@ -6109,16 +6138,45 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
:param fnames: the list of modified fields, or ``None`` for all fields
|
:param fnames: the list of modified fields, or ``None`` for all fields
|
||||||
:param ids: the list of modified record ids, or ``None`` for all
|
:param ids: the list of modified record ids, or ``None`` for all
|
||||||
"""
|
"""
|
||||||
if fnames is None:
|
warnings.warn(
|
||||||
if ids is None:
|
"Deprecated method invalidate_cache(), use invalidate_model(), invalidate_recordset() or env.invalidate_all() instead",
|
||||||
return self.env.cache.invalidate()
|
DeprecationWarning,
|
||||||
fields = list(self._fields.values())
|
)
|
||||||
|
if ids is not None:
|
||||||
|
self.browse(ids).invalidate_recordset(fnames)
|
||||||
|
elif fnames is not None:
|
||||||
|
self.invalidate_model(fnames)
|
||||||
else:
|
else:
|
||||||
fields = [self._fields[n] for n in fnames]
|
self.env.invalidate_all()
|
||||||
|
|
||||||
# invalidate fields and inverse fields, too
|
def invalidate_model(self, fnames=None):
|
||||||
spec = [(f, ids) for f in fields] + \
|
""" Invalidate the cache of all records of ``self``'s model, when the
|
||||||
[(invf, None) for f in fields for invf in self.pool.field_inverses[f]]
|
cached values no longer correspond to the database values. If the
|
||||||
|
parameter is given, only the given fields are invalidated from cache.
|
||||||
|
|
||||||
|
:param fnames: optional iterable of field names to invalidate
|
||||||
|
"""
|
||||||
|
self._invalidate_cache(fnames)
|
||||||
|
|
||||||
|
def invalidate_recordset(self, fnames=None):
|
||||||
|
""" Invalidate the cache of the records in ``self``, when the cached
|
||||||
|
values no longer correspond to the database values. If the parameter
|
||||||
|
is given, only the given fields on ``self`` are invalidated from cache.
|
||||||
|
|
||||||
|
:param fnames: optional iterable of field names to invalidate
|
||||||
|
"""
|
||||||
|
self._invalidate_cache(fnames, self._ids)
|
||||||
|
|
||||||
|
def _invalidate_cache(self, fnames=None, ids=None):
|
||||||
|
if fnames is None:
|
||||||
|
fields = self._fields.values()
|
||||||
|
else:
|
||||||
|
fields = [self._fields[fname] for fname in fnames]
|
||||||
|
spec = []
|
||||||
|
for field in fields:
|
||||||
|
spec.append((field, ids))
|
||||||
|
for invf in self.pool.field_inverses[field]:
|
||||||
|
spec.append((invf, None))
|
||||||
self.env.cache.invalidate(spec)
|
self.env.cache.invalidate(spec)
|
||||||
|
|
||||||
def modified(self, fnames, create=False, before=False):
|
def modified(self, fnames, create=False, before=False):
|
||||||
@@ -6260,38 +6318,73 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
The fields and records to recompute have been determined by method
|
The fields and records to recompute have been determined by method
|
||||||
:meth:`modified`.
|
:meth:`modified`.
|
||||||
"""
|
"""
|
||||||
def process(field):
|
warnings.warn(
|
||||||
recs = self.env.records_to_compute(field)
|
"Deprecated method recompute(), use flush_model(), flush_recordset() or env.flush_all() instead",
|
||||||
if (not recs) or (records is not None and not (records & recs)):
|
DeprecationWarning,
|
||||||
return
|
stacklevel=2,
|
||||||
if field.store:
|
)
|
||||||
# do not force recomputation on new records; those will be
|
|
||||||
# recomputed by accessing the field on the records
|
|
||||||
recs = recs.filtered('id')
|
|
||||||
try:
|
|
||||||
field.recompute(recs)
|
|
||||||
except MissingError:
|
|
||||||
existing = recs.exists()
|
|
||||||
field.recompute(existing)
|
|
||||||
# mark the field as computed on missing records, otherwise
|
|
||||||
# they remain forever in the todo list, and lead to an
|
|
||||||
# infinite loop...
|
|
||||||
for f in recs.pool.field_computed[field]:
|
|
||||||
self.env.remove_to_compute(f, recs - existing)
|
|
||||||
else:
|
|
||||||
self.env.cache.invalidate([(field, recs._ids)])
|
|
||||||
self.env.remove_to_compute(field, recs)
|
|
||||||
|
|
||||||
if fnames is None:
|
if fnames is None:
|
||||||
# recompute everything
|
self.env._recompute()
|
||||||
for field in list(self.env.fields_to_compute()):
|
elif records is None:
|
||||||
process(field)
|
self._recompute_model(fnames)
|
||||||
else:
|
else:
|
||||||
# recompute the given fields on self's model
|
records._recompute_recordset(fnames)
|
||||||
for fname in fnames:
|
|
||||||
field = self._fields[fname]
|
def _recompute_model(self, fnames=None):
|
||||||
if field.compute:
|
""" Process the pending computations of the fields of ``self``'s model.
|
||||||
process(field)
|
|
||||||
|
:param fnames: optional iterable of field names to compute
|
||||||
|
"""
|
||||||
|
if fnames is None:
|
||||||
|
fields = self._fields.values()
|
||||||
|
else:
|
||||||
|
fields = [self._fields[fname] for fname in fnames]
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if field.compute:
|
||||||
|
self._recompute_field(field)
|
||||||
|
|
||||||
|
def _recompute_recordset(self, fnames=None):
|
||||||
|
""" Process the pending computations of the fields of the records in ``self``.
|
||||||
|
|
||||||
|
:param fnames: optional iterable of field names to compute
|
||||||
|
"""
|
||||||
|
if fnames is None:
|
||||||
|
fields = self._fields.values()
|
||||||
|
else:
|
||||||
|
fields = [self._fields[fname] for fname in fnames]
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if field.compute:
|
||||||
|
self._recompute_field(field, self._ids)
|
||||||
|
|
||||||
|
def _recompute_field(self, field, ids=None):
|
||||||
|
ids_to_compute = self.env.all.tocompute.get(field, ())
|
||||||
|
if ids is None:
|
||||||
|
ids = ids_to_compute
|
||||||
|
else:
|
||||||
|
ids = [id_ for id_ in ids if id_ in ids_to_compute]
|
||||||
|
if not ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
records = self.browse(ids)
|
||||||
|
if field.store:
|
||||||
|
# do not force recomputation on new records; those will be
|
||||||
|
# recomputed by accessing the field on the records
|
||||||
|
records = records.filtered('id')
|
||||||
|
try:
|
||||||
|
field.recompute(records)
|
||||||
|
except MissingError:
|
||||||
|
existing = records.exists()
|
||||||
|
field.recompute(existing)
|
||||||
|
# mark the field as computed on missing records, otherwise
|
||||||
|
# they remain forever in the todo list, and lead to an
|
||||||
|
# infinite loop...
|
||||||
|
for f in records.pool.field_computed[field]:
|
||||||
|
self.env.remove_to_compute(f, records - existing)
|
||||||
|
else:
|
||||||
|
self.env.cache.invalidate([(field, records._ids)])
|
||||||
|
self.env.remove_to_compute(field, records)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Generic onchange method
|
# Generic onchange method
|
||||||
@@ -6388,7 +6481,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
methods to them, and return all the fields in ``field_onchange``.
|
methods to them, and return all the fields in ``field_onchange``.
|
||||||
"""
|
"""
|
||||||
# this is for tests using `Form`
|
# this is for tests using `Form`
|
||||||
self.flush()
|
self.env.flush_all()
|
||||||
|
|
||||||
env = self.env
|
env = self.env
|
||||||
if isinstance(field_name, list):
|
if isinstance(field_name, list):
|
||||||
@@ -6653,7 +6746,7 @@ class BaseModel(metaclass=MetaModel):
|
|||||||
snapshot1 = Snapshot(record, nametree)
|
snapshot1 = Snapshot(record, nametree)
|
||||||
|
|
||||||
# determine values that have changed by comparing snapshots
|
# determine values that have changed by comparing snapshots
|
||||||
self.invalidate_cache()
|
self.env.invalidate_all()
|
||||||
result['value'] = snapshot1.diff(snapshot0, force=first_call)
|
result['value'] = snapshot1.diff(snapshot0, force=first_call)
|
||||||
|
|
||||||
# format warnings
|
# format warnings
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ def force_demo(cr):
|
|||||||
load_demo(cr, package, {}, 'init')
|
load_demo(cr, package, {}, 'init')
|
||||||
|
|
||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
env['ir.module.module'].invalidate_cache(['demo'])
|
env['ir.module.module'].invalidate_model(['demo'])
|
||||||
env['res.groups']._update_user_groups_view()
|
env['res.groups']._update_user_groups_view()
|
||||||
|
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
|
|||||||
migrations.migrate_module(package, 'pre')
|
migrations.migrate_module(package, 'pre')
|
||||||
if package.name != 'base':
|
if package.name != 'base':
|
||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
|
|
||||||
load_openerp_module(package.name)
|
load_openerp_module(package.name)
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
|
|||||||
load_data(cr, idref, mode, kind='data', package=package)
|
load_data(cr, idref, mode, kind='data', package=package)
|
||||||
demo_loaded = package.dbdemo = load_demo(cr, package, idref, mode)
|
demo_loaded = package.dbdemo = load_demo(cr, package, idref, mode)
|
||||||
cr.execute('update ir_module_module set demo=%s where id=%s', (demo_loaded, module_id))
|
cr.execute('update ir_module_module set demo=%s where id=%s', (demo_loaded, module_id))
|
||||||
module.invalidate_cache(['demo'])
|
module.invalidate_model(['demo'])
|
||||||
|
|
||||||
migrations.migrate_module(package, 'post')
|
migrations.migrate_module(package, 'post')
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True,
|
|||||||
for kind in ('init', 'demo', 'update'):
|
for kind in ('init', 'demo', 'update'):
|
||||||
if hasattr(package, kind):
|
if hasattr(package, kind):
|
||||||
delattr(package, kind)
|
delattr(package, kind)
|
||||||
module.flush()
|
module.env.flush_all()
|
||||||
|
|
||||||
extra_queries = odoo.sql_db.sql_counter - module_extra_query_count - test_queries
|
extra_queries = odoo.sql_db.sql_counter - module_extra_query_count - test_queries
|
||||||
extras = []
|
extras = []
|
||||||
@@ -454,9 +454,9 @@ def load_modules(registry, force_demo=False, status=None, update_module=False):
|
|||||||
if modules:
|
if modules:
|
||||||
modules.button_upgrade()
|
modules.button_upgrade()
|
||||||
|
|
||||||
|
env.flush_all()
|
||||||
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
|
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
|
||||||
Module.invalidate_cache(['state'])
|
Module.invalidate_model(['state'])
|
||||||
Module.flush()
|
|
||||||
|
|
||||||
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
|
# STEP 3: Load marked modules (skipping base which was done in STEP 1)
|
||||||
# IMPORTANT: this is done in two parts, first loading all installed or
|
# IMPORTANT: this is done in two parts, first loading all installed or
|
||||||
@@ -519,7 +519,7 @@ def load_modules(registry, force_demo=False, status=None, update_module=False):
|
|||||||
|
|
||||||
# Cleanup orphan records
|
# Cleanup orphan records
|
||||||
env['ir.model.data']._process_end(processed_modules)
|
env['ir.model.data']._process_end(processed_modules)
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
|
|
||||||
for kind in ('init', 'demo', 'update'):
|
for kind in ('init', 'demo', 'update'):
|
||||||
tools.config[kind] = {}
|
tools.config[kind] = {}
|
||||||
@@ -587,7 +587,7 @@ def load_modules(registry, force_demo=False, status=None, update_module=False):
|
|||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
for model in env.values():
|
for model in env.values():
|
||||||
model._register_hook()
|
model._register_hook()
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
|
|
||||||
# STEP 9: save installed/updated modules for post-install tests
|
# STEP 9: save installed/updated modules for post-install tests
|
||||||
registry.updated_modules += processed_modules
|
registry.updated_modules += processed_modules
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ class Registry(Mapping):
|
|||||||
if self.ready:
|
if self.ready:
|
||||||
for model in env.values():
|
for model in env.values():
|
||||||
model._register_hook()
|
model._register_hook()
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
def field_computed(self):
|
def field_computed(self):
|
||||||
@@ -445,7 +445,7 @@ class Registry(Mapping):
|
|||||||
self.check_indexes(cr, model_names)
|
self.check_indexes(cr, model_names)
|
||||||
self.check_foreign_keys(cr)
|
self.check_foreign_keys(cr)
|
||||||
|
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
|
|
||||||
# make sure all tables are present
|
# make sure all tables are present
|
||||||
self.check_tables_exist(cr)
|
self.check_tables_exist(cr)
|
||||||
@@ -561,7 +561,7 @@ class Registry(Mapping):
|
|||||||
for name in missing:
|
for name in missing:
|
||||||
_logger.info("Recreate table of model %s.", name)
|
_logger.info("Recreate table of model %s.", name)
|
||||||
env[name].init()
|
env[name].init()
|
||||||
env['base'].flush()
|
env.flush_all()
|
||||||
# check again, and log errors if tables are still missing
|
# check again, and log errors if tables are still missing
|
||||||
missing_tables = set(table2model).difference(existing_tables(cr, table2model))
|
missing_tables = set(table2model).difference(existing_tables(cr, table2model))
|
||||||
for table in missing_tables:
|
for table in missing_tables:
|
||||||
|
|||||||
+15
-15
@@ -496,14 +496,14 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase):
|
|||||||
return lambda x: x
|
return lambda x: x
|
||||||
|
|
||||||
if flush:
|
if flush:
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
self.env.cr.flush()
|
self.env.cr.flush()
|
||||||
|
|
||||||
with patch('odoo.sql_db.Cursor.execute', execute):
|
with patch('odoo.sql_db.Cursor.execute', execute):
|
||||||
with patch('odoo.osv.expression.get_unaccent_wrapper', get_unaccent_wrapper):
|
with patch('odoo.osv.expression.get_unaccent_wrapper', get_unaccent_wrapper):
|
||||||
yield actual_queries
|
yield actual_queries
|
||||||
if flush:
|
if flush:
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
self.env.cr.flush()
|
self.env.cr.flush()
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -538,12 +538,12 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase):
|
|||||||
login = self.env.user.login
|
login = self.env.user.login
|
||||||
expected = counters.get(login, default)
|
expected = counters.get(login, default)
|
||||||
if flush:
|
if flush:
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
self.env.cr.flush()
|
self.env.cr.flush()
|
||||||
count0 = self.cr.sql_log_count
|
count0 = self.cr.sql_log_count
|
||||||
yield
|
yield
|
||||||
if flush:
|
if flush:
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
self.env.cr.flush()
|
self.env.cr.flush()
|
||||||
count = self.cr.sql_log_count - count0
|
count = self.cr.sql_log_count - count0
|
||||||
if count != expected:
|
if count != expected:
|
||||||
@@ -562,11 +562,11 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase):
|
|||||||
# flush before and after during warmup, in order to reproduce the
|
# flush before and after during warmup, in order to reproduce the
|
||||||
# same operations, otherwise the caches might not be ready!
|
# same operations, otherwise the caches might not be ready!
|
||||||
if flush:
|
if flush:
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
self.env.cr.flush()
|
self.env.cr.flush()
|
||||||
yield
|
yield
|
||||||
if flush:
|
if flush:
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
self.env.cr.flush()
|
self.env.cr.flush()
|
||||||
|
|
||||||
def assertRecordValues(self, records, expected_values):
|
def assertRecordValues(self, records, expected_values):
|
||||||
@@ -772,7 +772,7 @@ class TransactionCase(BaseCase):
|
|||||||
self.addCleanup(self.env.clear)
|
self.addCleanup(self.env.clear)
|
||||||
|
|
||||||
# flush everything in setUpClass before introducing a savepoint
|
# flush everything in setUpClass before introducing a savepoint
|
||||||
self.env['base'].flush()
|
self.env.flush_all()
|
||||||
|
|
||||||
self._savepoint_id = next(savepoint_seq)
|
self._savepoint_id = next(savepoint_seq)
|
||||||
self.cr.execute('SAVEPOINT test_%d' % self._savepoint_id)
|
self.cr.execute('SAVEPOINT test_%d' % self._savepoint_id)
|
||||||
@@ -816,7 +816,7 @@ class SingleTransactionCase(BaseCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SingleTransactionCase, self).setUp()
|
super(SingleTransactionCase, self).setUp()
|
||||||
self.env.user.flush()
|
self.env.flush_all()
|
||||||
|
|
||||||
|
|
||||||
class ChromeBrowserException(Exception):
|
class ChromeBrowserException(Exception):
|
||||||
@@ -1509,7 +1509,7 @@ class HttpCase(TransactionCase):
|
|||||||
|
|
||||||
ICP = cls.env['ir.config_parameter']
|
ICP = cls.env['ir.config_parameter']
|
||||||
ICP.set_param('web.base.url', cls.base_url())
|
ICP.set_param('web.base.url', cls.base_url())
|
||||||
ICP.flush()
|
ICP.env.flush_all()
|
||||||
# v8 api with correct xmlrpc exception handling.
|
# v8 api with correct xmlrpc exception handling.
|
||||||
cls.xmlrpc_url = f'http://{HOST}:{odoo.tools.config["http_port"]:d}/xmlrpc/2/'
|
cls.xmlrpc_url = f'http://{HOST}:{odoo.tools.config["http_port"]:d}/xmlrpc/2/'
|
||||||
cls._logger = logging.getLogger('%s.%s' % (cls.__module__, cls.__name__))
|
cls._logger = logging.getLogger('%s.%s' % (cls.__module__, cls.__name__))
|
||||||
@@ -1737,16 +1737,16 @@ def warmup(func, *args, **kwargs):
|
|||||||
effects of the warmup phase are rolled back thanks to a savepoint.
|
effects of the warmup phase are rolled back thanks to a savepoint.
|
||||||
"""
|
"""
|
||||||
self = args[0]
|
self = args[0]
|
||||||
self.env['base'].flush()
|
self.env.flush_all()
|
||||||
self.env.cache.invalidate()
|
self.env.invalidate_all()
|
||||||
# run once to warm up the caches
|
# run once to warm up the caches
|
||||||
self.warm = False
|
self.warm = False
|
||||||
self.cr.execute('SAVEPOINT test_warmup')
|
self.cr.execute('SAVEPOINT test_warmup')
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
self.env['base'].flush()
|
self.env.flush_all()
|
||||||
# run once for real
|
# run once for real
|
||||||
self.cr.execute('ROLLBACK TO SAVEPOINT test_warmup')
|
self.cr.execute('ROLLBACK TO SAVEPOINT test_warmup')
|
||||||
self.env.cache.invalidate()
|
self.env.invalidate_all()
|
||||||
self.warm = True
|
self.warm = True
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
|
|
||||||
@@ -2127,7 +2127,7 @@ class Form(object):
|
|||||||
record_to_values(self._view['fields'], r)
|
record_to_values(self._view['fields'], r)
|
||||||
)
|
)
|
||||||
self._changed.clear()
|
self._changed.clear()
|
||||||
self._model.flush()
|
self._model.env.flush_all()
|
||||||
self._model.env.clear() # discard cache and pending recomputations
|
self._model.env.clear() # discard cache and pending recomputations
|
||||||
return r
|
return r
|
||||||
|
|
||||||
@@ -2244,7 +2244,7 @@ class Form(object):
|
|||||||
|
|
||||||
record = self._model.browse(self._values.get('id'))
|
record = self._model.browse(self._values.get('id'))
|
||||||
result = record.onchange(self._onchange_values(), fields, spec)
|
result = record.onchange(self._onchange_values(), fields, spec)
|
||||||
self._model.flush()
|
self._model.env.flush_all()
|
||||||
self._model.env.clear() # discard cache and pending recomputations
|
self._model.env.clear() # discard cache and pending recomputations
|
||||||
if result.get('warning'):
|
if result.get('warning'):
|
||||||
_logger.getChild('onchange').warning("%(title)s %(message)s" % result.get('warning'))
|
_logger.getChild('onchange').warning("%(title)s %(message)s" % result.get('warning'))
|
||||||
|
|||||||
Reference in New Issue
Block a user