From eb67feb590f48864b6693ebe5835c77fdef49080 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 20 Jan 2021 14:48:35 +0000 Subject: [PATCH] [FIX] *: cache consistency In module mail, invalidating 'message_ids' on a mail thread also invalidates its inverse field 'res_id' on messages. If you haven't flushed it before, your cache will be inconsistent, as shown by the test /mail:TestMailgateway.test_message_process_bounce_records_channel. In module purchase_stock, add depends on report.stock.quantity. This ensures that when the model is queried after changes in other models, the data on which the SQL view depends is flushed to the database before querying that model's table. closes odoo/odoo#66938 Related: odoo/enterprise#16722 Signed-off-by: Raphael Collet Co-authored-by: Vincent Schippefilt --- addons/account/tests/test_account_account.py | 6 +-- .../calendar/models/calendar_alarm_manager.py | 4 ++ addons/calendar/tests/test_calendar.py | 1 + addons/crm/tests/test_crm_pls.py | 7 ++++ addons/delivery/tests/test_delivery_cost.py | 2 +- addons/hr/models/hr_employee.py | 13 ++++-- .../tests/test_leave.py | 5 +-- addons/l10n_eg_edi_eta/tests/common.py | 25 +++++++----- addons/mail/models/mail_message.py | 1 + .../tests/test_subcontracting.py | 2 +- addons/project/tests/test_project_report.py | 3 ++ addons/purchase/models/purchase.py | 1 + .../tests/test_reordering_rule.py | 5 ++- addons/sale/tests/test_sale_order.py | 6 +-- .../sale_loyalty/tests/test_program_rules.py | 4 +- addons/sale_stock/tests/test_sale_stock.py | 7 ++-- addons/stock/report/report_stock_quantity.py | 9 +++++ addons/website/tests/test_views.py | 2 +- odoo/addons/base/models/ir_model.py | 12 +++--- odoo/addons/base/models/ir_rule.py | 3 +- odoo/addons/base/wizard/base_partner_merge.py | 5 +-- .../test_new_api/models/test_new_api.py | 3 +- .../test_new_api/tests/test_new_fields.py | 9 +++-- .../test_new_api/tests/test_onchange.py | 40 +++++++++---------- .../tests/test_term_count.py | 2 +- odoo/tests/common.py | 2 +- 26 files changed, 105 insertions(+), 74 deletions(-) diff --git a/addons/account/tests/test_account_account.py b/addons/account/tests/test_account_account.py index 5b88a14a82f..3b83e8c20d3 100644 --- a/addons/account/tests/test_account_account.py +++ b/addons/account/tests/test_account_account.py @@ -61,7 +61,7 @@ class TestAccountAccount(AccountTestInvoicingCommon): # Set the account as reconcile and fully reconcile something. account.reconcile = True - self.env['account.move.line'].invalidate_model() + self.env.invalidate_all() self.assertRecordValues(move.line_ids, [ {'reconciled': False, 'amount_residual': 100.0, 'amount_residual_currency': 200.0}, @@ -77,7 +77,7 @@ class TestAccountAccount(AccountTestInvoicingCommon): # Set back to a not reconcile account and check the journal items. move.line_ids.remove_move_reconcile() account.reconcile = False - self.env['account.move.line'].invalidate_model() + self.env.invalidate_all() self.assertRecordValues(move.line_ids, [ {'reconciled': False, 'amount_residual': 0.0, 'amount_residual_currency': 0.0}, @@ -119,7 +119,7 @@ class TestAccountAccount(AccountTestInvoicingCommon): # Set the account as reconcile and partially reconcile something. account.reconcile = True - self.env['account.move.line'].invalidate_model() + self.env.invalidate_all() move.line_ids.filtered(lambda line: line.account_id == account).reconcile() diff --git a/addons/calendar/models/calendar_alarm_manager.py b/addons/calendar/models/calendar_alarm_manager.py index 40d1318c16e..f67a32d5d5e 100644 --- a/addons/calendar/models/calendar_alarm_manager.py +++ b/addons/calendar/models/calendar_alarm_manager.py @@ -15,6 +15,10 @@ class AlarmManager(models.AbstractModel): _description = 'Event Alarm Manager' def _get_next_potential_limit_alarm(self, alarm_type, seconds=None, partners=None): + # flush models before making queries + for model_name in ('calendar.alarm', 'calendar.event', 'calendar.recurrence'): + self.env[model_name].flush_model() + result = {} delta_request = """ SELECT diff --git a/addons/calendar/tests/test_calendar.py b/addons/calendar/tests/test_calendar.py index a26aecbbc1a..473db2dc702 100644 --- a/addons/calendar/tests/test_calendar.py +++ b/addons/calendar/tests/test_calendar.py @@ -161,6 +161,7 @@ class TestCalendar(SavepointCaseWithUserDemo): 'stop_date': "2018-10-18", 'allday': True, }) + self.env.invalidate_all() self.assertEqual(str(event.start), '2018-10-16 08:00:00') self.assertEqual(str(event.stop), '2018-10-18 18:00:00') diff --git a/addons/crm/tests/test_crm_pls.py b/addons/crm/tests/test_crm_pls.py index 98e54f11e33..19d30718317 100644 --- a/addons/crm/tests/test_crm_pls.py +++ b/addons/crm/tests/test_crm_pls.py @@ -191,6 +191,9 @@ class TestCRMPLS(TransactionCase): # rebuild frequencies table and recompute automated_probability for all leads. Lead._cron_update_automated_probabilities() + # As the cron is computing and writing in SQL queries, we need to invalidate the cache + self.env.invalidate_all() + self.assertEqual(tools.float_compare(leads[3].automated_probability, 33.49, 2), 0) self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0) lead_13_team_3_proba = leads[13].automated_probability @@ -403,6 +406,7 @@ class TestCRMPLS(TransactionCase): # Force recompute - A priori, no need to do this as, for each won / lost, we increment tag frequency. Lead._cron_update_automated_probabilities() + self.env.invalidate_all() lead_tag_1 = leads_with_tags[30] lead_tag_2 = leads_with_tags[90] @@ -440,6 +444,7 @@ class TestCRMPLS(TransactionCase): leads.filtered(lambda lead: lead.id % 2 == 0).email_state = 'correct' leads.filtered(lambda lead: lead.id % 2 == 1).email_state = 'incorrect' Lead._cron_update_automated_probabilities() + self.env.invalidate_all() self.assertEqual(tools.float_compare(leads[3].automated_probability, 4.21, 2), 0) self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 2), 0) @@ -447,6 +452,7 @@ class TestCRMPLS(TransactionCase): # remove all pls fields self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", False) Lead._cron_update_automated_probabilities() + self.env.invalidate_all() self.assertEqual(tools.float_compare(leads[3].automated_probability, 34.38, 2), 0) self.assertEqual(tools.float_compare(leads[8].automated_probability, 50.0, 2), 0) @@ -454,6 +460,7 @@ class TestCRMPLS(TransactionCase): # check if the probabilities are the same with the old param self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", "country_id,state_id,email_state,phone_state,source_id") Lead._cron_update_automated_probabilities() + self.env.invalidate_all() self.assertEqual(tools.float_compare(leads[3].automated_probability, 4.21, 2), 0) self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 2), 0) diff --git a/addons/delivery/tests/test_delivery_cost.py b/addons/delivery/tests/test_delivery_cost.py index 262edd2a70c..1da41ea4440 100644 --- a/addons/delivery/tests/test_delivery_cost.py +++ b/addons/delivery/tests/test_delivery_cost.py @@ -45,10 +45,10 @@ class TestDeliveryCost(common.TransactionCase): # as the tests hereunder assume all the prices in USD, we must ensure # that the company actually uses USD # We do an invalidation so the cache is aware of it too. + self.env.company.invalidate_recordset() self.env.cr.execute( "UPDATE res_company SET currency_id = %s WHERE id = %s", [self.env.ref('base.USD').id, self.env.company.id]) - self.env.company.invalidate_recordset() self.pricelist.currency_id = self.env.ref('base.USD').id def test_00_delivery_cost(self): diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py index dd0fba67fba..5978a2ac7de 100644 --- a/addons/hr/models/hr_employee.py +++ b/addons/hr/models/hr_employee.py @@ -181,10 +181,15 @@ class HrEmployeePrivate(models.Model): if self.check_access_rights('read', raise_exception=False): return super(HrEmployeePrivate, self)._read(fields) - res = self.env['hr.employee.public'].browse(self.ids).read(fields) - for r in res: - record = self.browse(r['id']) - record._update_cache({k:v for k,v in r.items() if k in fields}, validate=False) + # HACK: retrieve publicly available values from hr.employee.public and + # copy them to the cache of self; non-public data will be missing from + # cache, and interpreted as an access error + self.flush_recordset(fields) + public = self.env['hr.employee.public'].browse(self._ids) + public._read(fields) + for fname in fields: + values = self.env.cache.get_values(public, public._fields[fname]) + self.env.cache.update(self, self._fields[fname], values) @api.model def _cron_check_work_permit_validity(self): diff --git a/addons/hr_work_entry_holidays/tests/test_leave.py b/addons/hr_work_entry_holidays/tests/test_leave.py index 1985e6faae2..7a3eba9967e 100644 --- a/addons/hr_work_entry_holidays/tests/test_leave.py +++ b/addons/hr_work_entry_holidays/tests/test_leave.py @@ -158,17 +158,16 @@ class TestWorkEntryLeave(TestWorkEntryHolidaysBase): 'date_to': datetime(2022, 3, 25, 20), 'number_of_days': 4, }) - can_cancel_field = leave._fields['can_cancel'] leave.with_user(SUPERUSER_ID).action_validate() # No work entries exist yet self.assertTrue(leave.can_cancel, "The leave should still be cancellable") # can not create in the future self.richard_emp.contract_ids._generate_work_entries(datetime(2022, 3, 21, 6), datetime(2022, 3, 25, 20)) work_entries = self.env['hr.work.entry'].search([('employee_id', '=', self.richard_emp.id)]) - self.env.cache.invalidate([(can_cancel_field, leave.ids)]) + leave.invalidate_recordset(['can_cancel']) # Work entries exist but are not locked yet self.assertTrue(leave.can_cancel, "The leave should still be cancellable") work_entries.action_validate() - self.env.cache.invalidate([(can_cancel_field, leave.ids)]) + leave.invalidate_recordset(['can_cancel']) # Work entries locked self.assertFalse(leave.can_cancel, "The leave should not be cancellable") diff --git a/addons/l10n_eg_edi_eta/tests/common.py b/addons/l10n_eg_edi_eta/tests/common.py index d949353dd32..1a7a0d72b15 100644 --- a/addons/l10n_eg_edi_eta/tests/common.py +++ b/addons/l10n_eg_edi_eta/tests/common.py @@ -89,13 +89,18 @@ class TestEGEdiCommon(AccountEdiTestCommon): @classmethod def create_invoice(cls, **kwargs): - return (cls.env['account.move'] - .with_context(edi_test_mode=True) - .create({ - 'move_type': 'out_invoice', - 'partner_id': cls.partner_a.id, - 'invoice_date': '2022-03-15', - 'date': '2022-03-15', - **kwargs, - 'invoice_line_ids': [Command.create({**line_vals, }) for line_vals in kwargs.get('invoice_line_ids', [])] - })) + invoice = ( + cls.env['account.move'] + .with_context(edi_test_mode=True) + .create({ + 'move_type': 'out_invoice', + 'partner_id': cls.partner_a.id, + 'invoice_date': '2022-03-15', + 'date': '2022-03-15', + **kwargs, + 'invoice_line_ids': [Command.create({**line_vals, }) for line_vals in kwargs.get('invoice_line_ids', [])] + }) + ) + # this fixes rounding issues in cache + cls.env.invalidate_all() + return invoice diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index b1aa97f0b21..7bf5af2a2b1 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -1093,6 +1093,7 @@ class Message(models.Model): def _invalidate_documents(self, model=None, res_id=None): """ Invalidate the cache of the documents followed by ``self``. """ fnames = ['message_ids', 'message_needaction', 'message_needaction_counter'] + self.flush_recordset(['model', 'res_id']) for record in self: model = model or record.model res_id = res_id or record.res_id diff --git a/addons/mrp_subcontracting/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py index 3f90c5114a8..f4d43680e02 100644 --- a/addons/mrp_subcontracting/tests/test_subcontracting.py +++ b/addons/mrp_subcontracting/tests/test_subcontracting.py @@ -357,7 +357,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): 'parent_id': main_partner_2.id, 'company_id': self.env.ref('base.main_company').id, }) - self.env.cache.invalidate() + self.env.invalidate_all() # We create a different BoM for the same product comp3 = self.env['product.product'].create({ diff --git a/addons/project/tests/test_project_report.py b/addons/project/tests/test_project_report.py index 8fd2006d44c..2e6f3fe594e 100644 --- a/addons/project/tests/test_project_report.py +++ b/addons/project/tests/test_project_report.py @@ -33,6 +33,9 @@ class TestProjectReport(TestProjectCommon): self.assertEqual(task_3.rating_avg, 0) self.assertEqual(task_3.rating_last_value, 0) + # fix cache consistency + self.env['project.task'].invalidate_model(['rating_avg', 'rating_last_value']) + tasks = [self.task_1, self.task_2, task_3] for task in tasks: rating_values = task.read(['rating_avg', 'rating_last_value'])[0] diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index dd03d670d18..3a03207fff5 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -638,6 +638,7 @@ class PurchaseOrder(models.Model): # Invoice_ids may be filtered depending on the user. To ensure we get all # invoices related to the purchase order, we read them in sudo to fill the # cache. + self.invalidate_model(['invoice_ids']) self.sudo()._read(['invoice_ids']) invoices = self.invoice_ids diff --git a/addons/purchase_stock/tests/test_reordering_rule.py b/addons/purchase_stock/tests/test_reordering_rule.py index 3f93e1c30fc..d0295b9cdb9 100644 --- a/addons/purchase_stock/tests/test_reordering_rule.py +++ b/addons/purchase_stock/tests/test_reordering_rule.py @@ -602,7 +602,7 @@ class TestReorderingRule(TransactionCase): "route_ids": [], } ) - self.env.cache.invalidate() + self.env.invalidate_all() self.env["procurement.group"].run([procurement]) @@ -707,10 +707,11 @@ class TestReorderingRule(TransactionCase): {'qty_forecast': -1, 'qty_to_order': 1}, ]) - delivery.scheduled_date += td(days=7) + # invalidate the fields that will eventually be inconsistent orderpoint.invalidate_model(fnames=['qty_forecast', 'qty_to_order']) orderpoint.product_id.invalidate_model(fnames=['virtual_available']) + delivery.scheduled_date += td(days=7) self.assertRecordValues(orderpoint, [ {'qty_forecast': 0, 'qty_to_order': 0}, ]) diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py index a1a0c6bde60..22664c079ea 100644 --- a/addons/sale/tests/test_sale_order.py +++ b/addons/sale/tests/test_sale_order.py @@ -176,6 +176,7 @@ class TestSaleOrder(TestSaleCommon): # upsell and invoice self.sol_serv_order.write({'product_uom_qty': 10}) + # There is a bug with `new` and `_origin` # If you create a first new from a record, then change a value on the origin record, than create another new, # this other new wont have the updated value of the origin record, but the one from the previous new @@ -184,10 +185,7 @@ class TestSaleOrder(TestSaleCommon): # Here, we update `qty_delivered` on the origin record, but the `new` records which are in cache with this order line # as origin are not updated, nor the fields that depends on it. self.env.flush_all() - for field in self.env['sale.order.line']._fields.values(): - for res_id in list(self.env.cache._data[field]): - if not res_id: - self.env.cache._data[field].pop(res_id) + self.env.invalidate_all() invoice3 = self.sale_order._create_invoices() self.assertEqual(len(invoice3.invoice_line_ids), 1, 'Sale: third invoice is missing lines') diff --git a/addons/sale_loyalty/tests/test_program_rules.py b/addons/sale_loyalty/tests/test_program_rules.py index 0e4d924ca2d..4cd92a42c20 100644 --- a/addons/sale_loyalty/tests/test_program_rules.py +++ b/addons/sale_loyalty/tests/test_program_rules.py @@ -269,8 +269,6 @@ class TestProgramRules(TestSaleCouponCommon): }) ]}) # Invalidate total_order_count - field_order_count = self.env['loyalty.program']._fields['order_count'] - field_total_order_count = self.env['loyalty.program']._fields['total_order_count'] - self.env.cache.invalidate([(field_order_count, self.immediate_promotion_program.ids), (field_total_order_count, self.immediate_promotion_program.ids)]) + self.immediate_promotion_program.invalidate_recordset(['order_count', 'total_order_count']) self._auto_rewards(order, self.immediate_promotion_program) self.assertEqual(len(order.order_line.ids), 2, "The promo offer shouldn't have been applied as the number of uses is exceeded") diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 896b996ed51..482fcd1074c 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -93,6 +93,7 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon): self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after complete delivery') # Without timesheet, we manually set the delivered qty for the product serv_del self.so.order_line.sorted()[1]['qty_delivered'] = 2.0 + # There is a bug with `new` and `_origin` # If you create a first new from a record, then change a value on the origin record, than create another new, # this other new wont have the updated value of the origin record, but the one from the previous new @@ -101,10 +102,8 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon): # Here, we update `qty_delivered` on the origin record, but the `new` records which are in cache with this order line # as origin are not updated, nor the fields that depends on it. self.env.flush_all() - for field in self.env['sale.order.line']._fields.values(): - for res_id in list(self.env.cache._data[field]): - if not res_id: - self.env.cache._data[field].pop(res_id) + self.env.invalidate_all() + inv_id = self.so._create_invoices() self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "fully invoiced" after complete delivery and invoicing') diff --git a/addons/stock/report/report_stock_quantity.py b/addons/stock/report/report_stock_quantity.py index 6592303f5cf..4b3314f97eb 100644 --- a/addons/stock/report/report_stock_quantity.py +++ b/addons/stock/report/report_stock_quantity.py @@ -9,6 +9,15 @@ class ReportStockQuantity(models.Model): _auto = False _description = 'Stock Quantity Report' + _depends = { + 'product.product': ['product_tmpl_id'], + 'product.template': ['type'], + 'stock.location': ['parent_path'], + 'stock.move': ['company_id', 'date', 'location_dest_id', 'location_id', 'product_id', 'product_qty', 'state'], + 'stock.quant': ['company_id', 'location_id', 'product_id', 'quantity'], + 'stock.warehouse': ['view_location_id'], + } + date = fields.Date(string='Date', readonly=True) product_tmpl_id = fields.Many2one('product.template', readonly=True) product_id = fields.Many2one('product.product', string='Product', readonly=True) diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py index a332e99ae02..43525222629 100644 --- a/addons/website/tests/test_views.py +++ b/addons/website/tests/test_views.py @@ -774,7 +774,7 @@ class TestCowViewSaving(TestViewSavingCommon): 'arch': '

COMPARE

', 'key': '_website_sale_comparison.product_add_to_compare', })]) - Website.with_context(load_all_views=True).viewref('_website_sale_comparison.product_add_to_compare').invalidate_model() + View.invalidate_model() # Simulate end of installation/update View._create_all_specific_views(['_website_sale_comparison']) diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index 3b052960a39..f99a94b8fbf 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -1346,11 +1346,13 @@ class IrModelSelection(models.Model): if selection.value == vals['value']: continue if selection.field_id.store: + # in order to keep the cache consistent, flush the + # corresponding field, and invalidate it from cache + model = self.env[selection.field_id.model] + fname = selection.field_id.name + model.invalidate_model([fname]) # replace the value by the new one in the field's corresponding column - query = 'UPDATE "{table}" SET "{field}"=%s WHERE "{field}"=%s'.format( - table=self.env[selection.field_id.model]._table, - field=selection.field_id.name, - ) + query = f'UPDATE "{model._table}" SET "{fname}"=%s WHERE "{fname}"=%s' self.env.cr.execute(query, [vals['value'], selection.value]) result = super().write(vals) @@ -1655,6 +1657,7 @@ class IrModelRelation(models.Model): """ Reflect the table of a many2many field for the given model, to make it possible to delete it later when the module is uninstalled. """ + self.env.invalidate_all() cr = self._cr query = """ SELECT 1 FROM ir_model_relation r, ir_module_module m WHERE r.module=m.id AND r.name=%s AND m.name=%s """ @@ -1669,7 +1672,6 @@ class IrModelRelation(models.Model): (SELECT id FROM ir_module_module WHERE name=%s), (SELECT id FROM ir_model WHERE model=%s)) """ cr.execute(query, (table, self.env.uid, self.env.uid, module, model._name)) - self.env.invalidate_all() class IrModelAccess(models.Model): diff --git a/odoo/addons/base/models/ir_rule.py b/odoo/addons/base/models/ir_rule.py index 23c09aa7027..6fbadee77f0 100644 --- a/odoo/addons/base/models/ir_rule.py +++ b/odoo/addons/base/models/ir_rule.py @@ -233,8 +233,7 @@ class IrRule(models.Model): failing_rules += "\n\n" + _('Note: this might be a multi-company issue.') # clean up the cache of records prefetched with display_name above - for record in records[:6]: - record._cache.clear() + records_sudo.invalidate_recordset() msg = f"{operation_error}\n\n{failing_records}\n{failing_user}\n\n{failing_rules}\n\n{resolution_info}" return AccessError(msg) diff --git a/odoo/addons/base/wizard/base_partner_merge.py b/odoo/addons/base/wizard/base_partner_merge.py index d612120685e..424a731492c 100644 --- a/odoo/addons/base/wizard/base_partner_merge.py +++ b/odoo/addons/base/wizard/base_partner_merge.py @@ -112,7 +112,8 @@ class MergePartnerAutomatic(models.TransientModel): Partner = self.env['res.partner'] relations = self._get_fk_on('res_partner') - self.env.flush_all() + # this guarantees cache consistency + self.env.invalidate_all() for table, column in relations: if 'base_partner_merge_' in table: # ignore two tables @@ -175,8 +176,6 @@ class MergePartnerAutomatic(models.TransientModel): query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic self._cr.execute(query, (tuple(src_partners.ids),)) - self.env.invalidate_all() - @api.model def _update_reference_fields(self, src_partners, dst_partner): """ Update all reference fields from the src_partner to dst_partner. diff --git a/odoo/addons/test_new_api/models/test_new_api.py b/odoo/addons/test_new_api/models/test_new_api.py index 713301dd37d..710b00fa2d9 100644 --- a/odoo/addons/test_new_api/models/test_new_api.py +++ b/odoo/addons/test_new_api/models/test_new_api.py @@ -935,9 +935,8 @@ class Attachment(models.Model): return comodel = self.env[self.res_model] if 'res_id' in fnames and 'attachment_ids' in comodel: - field = comodel._fields['attachment_ids'] record = comodel.browse(self.res_id) - self.env.cache.invalidate([(field, record._ids)]) + record.invalidate_recordset(['attachment_ids']) record.modified(['attachment_ids']) return super(Attachment, self).modified(fnames, *args, **kwargs) diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 04529592445..1a57d51bdcd 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -244,6 +244,7 @@ class TestFields(TransactionCaseWithUserDemo): (0, 0, {'name': 'x_stuff_id', 'ttype': 'many2one', 'relation': 'ir.model'}), ], }) + self.env.invalidate_all() # set 'x_stuff_id' refer to a model not loaded yet self.cr.execute(""" UPDATE ir_model_fields @@ -791,7 +792,6 @@ class TestFields(TransactionCaseWithUserDemo): # switch to environment with user demo records = records.with_user(self.user_demo) - records.env.cache.invalidate() # check that records are not accessible with self.assertRaises(AccessError): @@ -1611,7 +1611,7 @@ class TestFields(TransactionCaseWithUserDemo): """ test field access on new records vs real records. """ Model = self.env['test_new_api.category'] real_record = Model.create({'name': 'Foo'}) - self.env.cache.invalidate() + self.env.invalidate_all() new_origin = Model.new({'name': 'Bar'}, origin=real_record) new_record = Model.new({'name': 'Baz'}) @@ -2080,6 +2080,7 @@ class TestFields(TransactionCaseWithUserDemo): self.assertEqual(demo_discussion.messages, discussion.messages) # See YTI FIXME + self.env.flush_all() self.env.invalidate_all() # add a message as user demo @@ -2744,12 +2745,12 @@ class TestX2many(common.TransactionCase): self.assertEqual(parent.with_context(active_test=False).active_children_ids, act_children) # check read() - self.env.cache.invalidate() + self.env.invalidate_all() self.assertEqual(parent.children_ids, act_children) self.assertEqual(parent.all_children_ids, all_children) self.assertEqual(parent.active_children_ids, act_children) - self.env.cache.invalidate() + self.env.invalidate_all() self.assertEqual(parent.with_context(active_test=False).children_ids, all_children) self.assertEqual(parent.with_context(active_test=False).all_children_ids, all_children) self.assertEqual(parent.with_context(active_test=False).active_children_ids, act_children) diff --git a/odoo/addons/test_new_api/tests/test_onchange.py b/odoo/addons/test_new_api/tests/test_onchange.py index 0cd2d9282ac..b43e495b0f7 100644 --- a/odoo/addons/test_new_api/tests/test_onchange.py +++ b/odoo/addons/test_new_api/tests/test_onchange.py @@ -49,7 +49,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'author': USER.id, 'size': 0, } - self.env.cache.invalidate() + self.env.invalidate_all() result = self.Message.onchange(values, 'discussion', field_onchange) self.assertIn('name', result['value']) self.assertEqual(result['value']['name'], "[%s] %s" % (discussion.name, USER.name)) @@ -62,7 +62,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'author': USER.id, 'size': 0, } - self.env.cache.invalidate() + self.env.invalidate_all() result = self.Message.onchange(values, 'body', field_onchange) self.assertIn('size', result['value']) self.assertEqual(result['value']['size'], len(BODY)) @@ -76,7 +76,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'author': USER.id, 'size': 0, } - self.env.cache.invalidate() + self.env.invalidate_all() result = self.Message.onchange(values, 'body', field_onchange) self.assertNotIn('name', result['value']) @@ -94,7 +94,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'root_categ': False, } - self.env.cache.invalidate() + self.env.invalidate_all() result = Category.onchange(values, 'parent', field_onchange).get('value', {}) self.assertIn('root_categ', result) self.assertEqual(result['root_categ'], root.name_get()[0]) @@ -102,7 +102,7 @@ class TestOnChange(SavepointCaseWithUserDemo): values.update(result) values['parent'] = False - self.env.cache.invalidate() + self.env.invalidate_all() result = Category.onchange(values, 'parent', field_onchange).get('value', {}) self.assertIn('root_categ', result) self.assertIs(result['root_categ'], False) @@ -143,7 +143,7 @@ class TestOnChange(SavepointCaseWithUserDemo): }), ], } - self.env.cache.invalidate() + self.env.invalidate_all() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) self.assertEqual(result['value']['messages'], [ @@ -214,7 +214,7 @@ class TestOnChange(SavepointCaseWithUserDemo): }), ], } - self.env.cache.invalidate() + self.env.invalidate_all() result = self.Discussion.onchange(values, 'name', field_onchange) self.assertIn('messages', result['value']) self.assertItemsEqual(result['value']['messages'], [ @@ -263,7 +263,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'lines': [Command.set([line1.id]), Command.create({'name': False, 'partner': False, 'tags': [Command.clear()]})], } - self.env.cache.invalidate() + self.env.invalidate_all() result = multi.onchange(values, 'partner', field_onchange) self.assertEqual(result['value'], { @@ -292,7 +292,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'partner': False, 'tags': [Command.clear(), Command.create({'name': 'Tag'})]})], } - self.env.cache.invalidate() + self.env.invalidate_all() result = multi.onchange(values, 'partner', field_onchange) expected_value = { 'name': partner2.name, @@ -313,13 +313,13 @@ class TestOnChange(SavepointCaseWithUserDemo): self.assertEqual(result['value'], expected_value) # ensure ID is not returned when asked and a many2many record is set to be created - self.env.cache.invalidate() + self.env.invalidate_all() result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.tags.id': None})) self.assertEqual(result['value'], expected_value) # ensure inverse of one2many field is not returned - self.env.cache.invalidate() + self.env.invalidate_all() result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.multi': None})) self.assertEqual(result['value'], expected_value) @@ -348,7 +348,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'messages': [Command.link(msg.id) for msg in discussion.messages], 'participants': [Command.link(usr.id) for usr in discussion.participants], } - self.env.cache.invalidate() + self.env.invalidate_all() result = discussion.onchange(values, 'moderator', field_onchange) self.assertIn('participants', result['value']) @@ -368,13 +368,13 @@ class TestOnChange(SavepointCaseWithUserDemo): self.env['ir.default'].set('test_new_api.foo', 'value2', 666, condition='value1=42') # setting 'value1' to 42 should trigger the change of 'value2' - self.env.cache.invalidate() + self.env.invalidate_all() values = {'name': 'X', 'value1': 42, 'value2': False} result = Foo.onchange(values, 'value1', field_onchange) self.assertEqual(result['value'], {'value2': 666}) # setting 'value1' to 24 should not trigger the change of 'value2' - self.env.cache.invalidate() + self.env.invalidate_all() values = {'name': 'X', 'value1': 24, 'value2': False} result = Foo.onchange(values, 'value1', field_onchange) self.assertEqual(result['value'], {}) @@ -442,7 +442,7 @@ class TestOnChange(SavepointCaseWithUserDemo): }) # check if server-side cache is working correctly - self.env.cache.invalidate() + self.env.invalidate_all() self.assertIn(email, discussion.emails) self.assertNotIn(email, discussion.important_emails) email.important = True @@ -451,7 +451,7 @@ class TestOnChange(SavepointCaseWithUserDemo): # check that when trigger an onchange, we don't reset important emails # (force `invalidate` as but appear in onchange only when we get a cache # miss) - self.env.cache.invalidate() + self.env.invalidate_all() self.assertEqual(len(discussion.messages), 4) values = { 'name': "Foo Bar", @@ -462,7 +462,7 @@ class TestOnChange(SavepointCaseWithUserDemo): 'important_messages': [Command.link(msg.id) for msg in discussion.important_messages], 'important_emails': [Command.link(eml.id) for eml in discussion.important_emails], } - self.env.cache.invalidate() + self.env.invalidate_all() result = discussion.onchange(values, 'name', field_onchange) self.assertEqual( @@ -494,13 +494,13 @@ class TestOnChange(SavepointCaseWithUserDemo): 'message_currency': self.env.user.name_get()[0], } - self.env.cache.invalidate() + self.env.invalidate_all() Message = self.env['test_new_api.related'] result = Message.onchange(value, 'message', field_onchange) self.assertEqual(result['value'], onchange_result) - self.env.cache.invalidate() + self.env.invalidate_all() Message = self.env(user=self.user_demo.id)['test_new_api.related'] result = Message.onchange(value, 'message', field_onchange) @@ -530,7 +530,7 @@ class TestOnChange(SavepointCaseWithUserDemo): # changing 'discussion' on message should not read 'messages' on discussion with patch.object(type(discussion), 'read', mock_read, create=True): - self.env.cache.invalidate() + self.env.invalidate_all() self.Message.onchange(values, 'discussion', field_onchange) self.assertFalse(called[0], "discussion.messages has been read") diff --git a/odoo/addons/test_translation_import/tests/test_term_count.py b/odoo/addons/test_translation_import/tests/test_term_count.py index c3c530a9c17..1ae05ac3228 100644 --- a/odoo/addons/test_translation_import/tests/test_term_count.py +++ b/odoo/addons/test_translation_import/tests/test_term_count.py @@ -66,7 +66,7 @@ class TestTermCount(common.TransactionCase): odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False, overwrite=True) # trans_load invalidates ormcache but not record cache - menu.env.cache.invalidate() + self.env.invalidate_all() self.assertEqual(menu.name, "New Name") self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom") diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 396c726e235..cbb5719e757 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -1734,7 +1734,7 @@ def users(*logins): func(*args, **kwargs) # Invalidate the cache between subtests, in order to not reuse # the former user's cache (`test_read_mail`, `test_write_mail`) - self.env.cache.invalidate() + self.env.invalidate_all() finally: self.uid = old_uid