[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:
Raphael Collet
2022-05-25 18:00:46 +02:00
parent 5ceef05996
commit 32bc28aa66
7 changed files with 242 additions and 134 deletions
+18 -3
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+8 -8
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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'))