From 32bc28aa665c479e31a40afffca2f03f6f2cc805 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 28 Mar 2022 08:38:44 +0000 Subject: [PATCH] [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 --- odoo/api.py | 21 ++- odoo/cli/populate.py | 2 +- odoo/fields.py | 4 +- odoo/models.py | 297 +++++++++++++++++++++++++-------------- odoo/modules/loading.py | 16 +-- odoo/modules/registry.py | 6 +- odoo/tests/common.py | 30 ++-- 7 files changed, 242 insertions(+), 134 deletions(-) diff --git a/odoo/api.py b/odoo/api.py index 4ed88ec7e8f..d4e45fca339 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -459,7 +459,7 @@ def call_kw(model, name, args, kwargs): result = _call_kw_model_create(method, model, args, kwargs) else: result = _call_kw_multi(method, model, args, kwargs) - model.flush() + model.env.flush_all() return result @@ -701,6 +701,21 @@ class Environment(Mapping): ) 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): """ Return whether `record` is protected against invalidation or recomputation for `field`. @@ -834,7 +849,7 @@ class Transaction: if env.uid is not None: break if env_to_flush is not None: - env_to_flush['base'].flush() + env_to_flush.flush_all() def clear(self): """ Clear the caches and pending computations and updates in the translations. """ @@ -983,7 +998,7 @@ class Cache(object): def check(self, env): """ Check the consistency of the cache for the given environment. """ # 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 dump = dict(self._data) diff --git a/odoo/cli/populate.py b/odoo/cli/populate.py index 6fce9a51bd5..9dcf669adf1 100644 --- a/odoo/cli/populate.py +++ b/odoo/cli/populate.py @@ -80,7 +80,7 @@ class Populate(Command): # force the flush to make sure population time still # considers flushing all values to database - env['base'].flush() + env.flush_all() if commit: env.cr.commit() diff --git a/odoo/fields.py b/odoo/fields.py index bdf2ea0082b..85eb6f94a1b 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -1024,7 +1024,7 @@ class Field(MetaField('DummyField', (object,), {})): @model.pool.post_init def add_not_null(): # 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) elif not self.required and has_notnull: @@ -3472,7 +3472,7 @@ class One2many(_RelationalMulti): # cache value of self is consistent inverse_field = records.pool[self.comodel_name]._fields[self.inverse_name] 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) def read(self, records): diff --git a/odoo/models.py b/odoo/models.py index 45a34a9bf64..7f118014a4e 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -908,7 +908,7 @@ class BaseModel(metaclass=MetaModel): ) finally: psycopg2.extensions.set_wait_callback(callback) - self.env['ir.model.data'].invalidate_cache(fnames=fields) + self.env['ir.model.data'].invalidate_model(fields) return ( (record, to_xid(record.id)) @@ -935,7 +935,7 @@ class BaseModel(metaclass=MetaModel): sub = rs[idx:idx+1000] for rec in sub: yield rec - rs.invalidate_cache(ids=sub.ids) + sub.invalidate_recordset() if not _is_toplevel_call: splittor = lambda rs: rs @@ -1060,7 +1060,7 @@ class BaseModel(metaclass=MetaModel): :type data: list(list(str)) :returns: {ids: list(int)|False, messages: [Message][, lastrow: int]} """ - self.flush() + self.env.flush_all() # determine values of mode, current_module and noupdate 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 # 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? limit = self._context.get('_import_limit') if limit is None: 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}} for id, xid, record, info in converted: @@ -2837,7 +2837,7 @@ class BaseModel(metaclass=MetaModel): WHERE row.id = comp.id """.format(table=self._table, parent=self._parent_name) self.env.cr.execute(query) - self.invalidate_cache(['parent_path']) + self.invalidate_model(['parent_path']) return True 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 # fetch from database and overwrites the cache (`test_update_with_id`) - self.flush(fields, self) + self.flush_recordset(fields) 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 # been deleted (like updating a sum of lines after deleting one line) - self.flush() + self.env.flush_all() self.modified(self._fields, before=True) with self.env.norecompute(): @@ -3819,14 +3819,14 @@ class BaseModel(metaclass=MetaModel): # invalidate the *whole* cache, since the orm does not handle all # changes made in the database, like cascading delete! - self.invalidate_cache() + self.env.invalidate_all() if ir_model_data_unlink: ir_model_data_unlink.unlink() if ir_attachment_unlink: ir_attachment_unlink.unlink() # DLE P93: flush after the unlink, for recompute fields depending on # the modified of the unlink - self.flush() + self.env.flush_all() # 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) @@ -3943,7 +3943,7 @@ class BaseModel(metaclass=MetaModel): for field in protected if field.compute and field.name not in vals] if to_compute: - self.recompute(to_compute, self) + self._recompute_recordset(to_compute) # protect fields being written against recomputation with env.protecting(protected, self): @@ -3992,7 +3992,7 @@ class BaseModel(metaclass=MetaModel): self.modified(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 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]) 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 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 cr = self._cr - self.flush([parent]) + self.flush_model([parent]) query = 'SELECT "%s" FROM "%s" WHERE id = %%s' % (parent, self._table) for id in self.ids: current_id = id @@ -5093,7 +5093,7 @@ class BaseModel(metaclass=MetaModel): # field must be a many2many on itself raise ValueError('invalid field_name: %r' % (field_name,)) - self.flush([field_name]) + self.flush_model([field_name]) cr = self._cr 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 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): # group record ids by vals, to update in batch when possible updates = defaultdict(list) - for rid, vals in id_vals.items(): - updates[frozendict(vals)].append(rid) + for id_, vals in id_vals.items(): + updates[frozendict(vals)].append(id_) for vals, ids in updates.items(): 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: - # flush everything - self.recompute() - while self.env.all.towrite: - model_name, id_vals = self.env.all.towrite.popitem() - process(self.env[model_name], id_vals) + fields = self._fields.values() else: - # flush self's model if any of the fields must be flushed - self.recompute(fnames, records=records) + fields = [self._fields[fname] for fname in fnames] - # check whether any of 'records' must be flushed - if records is not None: - fnames = set(fnames) - towrite = self.env.all.towrite.get(self._name) - if not towrite or all( - fnames.isdisjoint(towrite.get(record.id, ())) - for record in records - ): - return + model_fields = defaultdict(list) + for field in fields: + model_fields[field.model_name].append(field) + if field.related_field: + model_fields[field.related_field.model_name].append(field.related_field) - # 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 - 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) + 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 - for fname in fnames: - field = self._fields[fname] - if field.type == 'one2many' and field.inverse_name: - self.env[field.comodel_name].flush([field.inverse_name]) + # flush the inverse of one2many fields, too + for field in fields: + if field.type == 'one2many' and field.inverse_name: + self.env[field.comodel_name].flush_model([field.inverse_name]) # # 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', DeprecationWarning) - self.invalidate_cache() + self.env.invalidate_all() @api.model 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 ids: the list of modified record ids, or ``None`` for all """ - if fnames is None: - if ids is None: - return self.env.cache.invalidate() - fields = list(self._fields.values()) + warnings.warn( + "Deprecated method invalidate_cache(), use invalidate_model(), invalidate_recordset() or env.invalidate_all() instead", + DeprecationWarning, + ) + if ids is not None: + self.browse(ids).invalidate_recordset(fnames) + elif fnames is not None: + self.invalidate_model(fnames) else: - fields = [self._fields[n] for n in fnames] + self.env.invalidate_all() - # invalidate fields and inverse fields, too - spec = [(f, ids) for f in fields] + \ - [(invf, None) for f in fields for invf in self.pool.field_inverses[f]] + def invalidate_model(self, fnames=None): + """ Invalidate the cache of all records of ``self``'s model, when the + 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) 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 :meth:`modified`. """ - def process(field): - recs = self.env.records_to_compute(field) - if (not recs) or (records is not None and not (records & recs)): - return - 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) - + warnings.warn( + "Deprecated method recompute(), use flush_model(), flush_recordset() or env.flush_all() instead", + DeprecationWarning, + stacklevel=2, + ) if fnames is None: - # recompute everything - for field in list(self.env.fields_to_compute()): - process(field) + self.env._recompute() + elif records is None: + self._recompute_model(fnames) else: - # recompute the given fields on self's model - for fname in fnames: - field = self._fields[fname] - if field.compute: - process(field) + records._recompute_recordset(fnames) + + def _recompute_model(self, fnames=None): + """ Process the pending computations of the fields of ``self``'s model. + + :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 @@ -6388,7 +6481,7 @@ class BaseModel(metaclass=MetaModel): methods to them, and return all the fields in ``field_onchange``. """ # this is for tests using `Form` - self.flush() + self.env.flush_all() env = self.env if isinstance(field_name, list): @@ -6653,7 +6746,7 @@ class BaseModel(metaclass=MetaModel): snapshot1 = Snapshot(record, nametree) # determine values that have changed by comparing snapshots - self.invalidate_cache() + self.env.invalidate_all() result['value'] = snapshot1.diff(snapshot0, force=first_call) # format warnings diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index 3a9da98231e..2c4c9ae35d6 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -119,7 +119,7 @@ def force_demo(cr): load_demo(cr, package, {}, 'init') 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() @@ -183,7 +183,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, migrations.migrate_module(package, 'pre') if package.name != 'base': env = api.Environment(cr, SUPERUSER_ID, {}) - env['base'].flush() + env.flush_all() 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) 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)) - module.invalidate_cache(['demo']) + module.invalidate_model(['demo']) 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'): if hasattr(package, kind): delattr(package, kind) - module.flush() + module.env.flush_all() extra_queries = odoo.sql_db.sql_counter - module_extra_query_count - test_queries extras = [] @@ -454,9 +454,9 @@ def load_modules(registry, force_demo=False, status=None, update_module=False): if modules: modules.button_upgrade() + env.flush_all() cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base')) - Module.invalidate_cache(['state']) - Module.flush() + Module.invalidate_model(['state']) # 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 @@ -519,7 +519,7 @@ def load_modules(registry, force_demo=False, status=None, update_module=False): # Cleanup orphan records env['ir.model.data']._process_end(processed_modules) - env['base'].flush() + env.flush_all() for kind in ('init', 'demo', 'update'): 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, {}) for model in env.values(): model._register_hook() - env['base'].flush() + env.flush_all() # STEP 9: save installed/updated modules for post-install tests registry.updated_modules += processed_modules diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index decc8e4ac06..a7d1c88ff40 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -306,7 +306,7 @@ class Registry(Mapping): if self.ready: for model in env.values(): model._register_hook() - env['base'].flush() + env.flush_all() @lazy_property def field_computed(self): @@ -445,7 +445,7 @@ class Registry(Mapping): self.check_indexes(cr, model_names) self.check_foreign_keys(cr) - env['base'].flush() + env.flush_all() # make sure all tables are present self.check_tables_exist(cr) @@ -561,7 +561,7 @@ class Registry(Mapping): for name in missing: _logger.info("Recreate table of model %s.", name) env[name].init() - env['base'].flush() + env.flush_all() # check again, and log errors if tables are still missing missing_tables = set(table2model).difference(existing_tables(cr, table2model)) for table in missing_tables: diff --git a/odoo/tests/common.py b/odoo/tests/common.py index f2bb653cf1e..94662d03d33 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -496,14 +496,14 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase): return lambda x: x if flush: - self.env.user.flush() + self.env.flush_all() self.env.cr.flush() with patch('odoo.sql_db.Cursor.execute', execute): with patch('odoo.osv.expression.get_unaccent_wrapper', get_unaccent_wrapper): yield actual_queries if flush: - self.env.user.flush() + self.env.flush_all() self.env.cr.flush() self.assertEqual( @@ -538,12 +538,12 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase): login = self.env.user.login expected = counters.get(login, default) if flush: - self.env.user.flush() + self.env.flush_all() self.env.cr.flush() count0 = self.cr.sql_log_count yield if flush: - self.env.user.flush() + self.env.flush_all() self.env.cr.flush() count = self.cr.sql_log_count - count0 if count != expected: @@ -562,11 +562,11 @@ class BaseCase(unittest.TestCase, metaclass=MetaCase): # flush before and after during warmup, in order to reproduce the # same operations, otherwise the caches might not be ready! if flush: - self.env.user.flush() + self.env.flush_all() self.env.cr.flush() yield if flush: - self.env.user.flush() + self.env.flush_all() self.env.cr.flush() def assertRecordValues(self, records, expected_values): @@ -772,7 +772,7 @@ class TransactionCase(BaseCase): self.addCleanup(self.env.clear) # flush everything in setUpClass before introducing a savepoint - self.env['base'].flush() + self.env.flush_all() self._savepoint_id = next(savepoint_seq) self.cr.execute('SAVEPOINT test_%d' % self._savepoint_id) @@ -816,7 +816,7 @@ class SingleTransactionCase(BaseCase): def setUp(self): super(SingleTransactionCase, self).setUp() - self.env.user.flush() + self.env.flush_all() class ChromeBrowserException(Exception): @@ -1509,7 +1509,7 @@ class HttpCase(TransactionCase): ICP = cls.env['ir.config_parameter'] ICP.set_param('web.base.url', cls.base_url()) - ICP.flush() + ICP.env.flush_all() # v8 api with correct xmlrpc exception handling. cls.xmlrpc_url = f'http://{HOST}:{odoo.tools.config["http_port"]:d}/xmlrpc/2/' 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. """ self = args[0] - self.env['base'].flush() - self.env.cache.invalidate() + self.env.flush_all() + self.env.invalidate_all() # run once to warm up the caches self.warm = False self.cr.execute('SAVEPOINT test_warmup') func(*args, **kwargs) - self.env['base'].flush() + self.env.flush_all() # run once for real self.cr.execute('ROLLBACK TO SAVEPOINT test_warmup') - self.env.cache.invalidate() + self.env.invalidate_all() self.warm = True func(*args, **kwargs) @@ -2127,7 +2127,7 @@ class Form(object): record_to_values(self._view['fields'], r) ) self._changed.clear() - self._model.flush() + self._model.env.flush_all() self._model.env.clear() # discard cache and pending recomputations return r @@ -2244,7 +2244,7 @@ class Form(object): record = self._model.browse(self._values.get('id')) 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 if result.get('warning'): _logger.getChild('onchange').warning("%(title)s %(message)s" % result.get('warning'))