[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 <rco@odoo.com>
Co-authored-by: Vincent Schippefilt <vsc@odoo.com>
This commit is contained in:
Raphael Collet
2022-07-05 11:35:01 +02:00
co-authored by Vincent Schippefilt
parent 9c3b9a4926
commit eb67feb590
26 changed files with 105 additions and 74 deletions
+3 -3
View File
@@ -61,7 +61,7 @@ class TestAccountAccount(AccountTestInvoicingCommon):
# Set the account as reconcile and fully reconcile something. # Set the account as reconcile and fully reconcile something.
account.reconcile = True account.reconcile = True
self.env['account.move.line'].invalidate_model() self.env.invalidate_all()
self.assertRecordValues(move.line_ids, [ self.assertRecordValues(move.line_ids, [
{'reconciled': False, 'amount_residual': 100.0, 'amount_residual_currency': 200.0}, {'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. # Set back to a not reconcile account and check the journal items.
move.line_ids.remove_move_reconcile() move.line_ids.remove_move_reconcile()
account.reconcile = False account.reconcile = False
self.env['account.move.line'].invalidate_model() self.env.invalidate_all()
self.assertRecordValues(move.line_ids, [ self.assertRecordValues(move.line_ids, [
{'reconciled': False, 'amount_residual': 0.0, 'amount_residual_currency': 0.0}, {'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. # Set the account as reconcile and partially reconcile something.
account.reconcile = True 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() move.line_ids.filtered(lambda line: line.account_id == account).reconcile()
@@ -15,6 +15,10 @@ class AlarmManager(models.AbstractModel):
_description = 'Event Alarm Manager' _description = 'Event Alarm Manager'
def _get_next_potential_limit_alarm(self, alarm_type, seconds=None, partners=None): 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 = {} result = {}
delta_request = """ delta_request = """
SELECT SELECT
+1
View File
@@ -161,6 +161,7 @@ class TestCalendar(SavepointCaseWithUserDemo):
'stop_date': "2018-10-18", 'stop_date': "2018-10-18",
'allday': True, 'allday': True,
}) })
self.env.invalidate_all()
self.assertEqual(str(event.start), '2018-10-16 08:00:00') self.assertEqual(str(event.start), '2018-10-16 08:00:00')
self.assertEqual(str(event.stop), '2018-10-18 18:00:00') self.assertEqual(str(event.stop), '2018-10-18 18:00:00')
+7
View File
@@ -191,6 +191,9 @@ class TestCRMPLS(TransactionCase):
# rebuild frequencies table and recompute automated_probability for all leads. # rebuild frequencies table and recompute automated_probability for all leads.
Lead._cron_update_automated_probabilities() 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[3].automated_probability, 33.49, 2), 0)
self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0) self.assertEqual(tools.float_compare(leads[8].automated_probability, 7.74, 2), 0)
lead_13_team_3_proba = leads[13].automated_probability 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. # Force recompute - A priori, no need to do this as, for each won / lost, we increment tag frequency.
Lead._cron_update_automated_probabilities() Lead._cron_update_automated_probabilities()
self.env.invalidate_all()
lead_tag_1 = leads_with_tags[30] lead_tag_1 = leads_with_tags[30]
lead_tag_2 = leads_with_tags[90] 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 == 0).email_state = 'correct'
leads.filtered(lambda lead: lead.id % 2 == 1).email_state = 'incorrect' leads.filtered(lambda lead: lead.id % 2 == 1).email_state = 'incorrect'
Lead._cron_update_automated_probabilities() 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[3].automated_probability, 4.21, 2), 0)
self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 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 # remove all pls fields
self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", False) self.env['ir.config_parameter'].sudo().set_param("crm.pls_fields", False)
Lead._cron_update_automated_probabilities() 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[3].automated_probability, 34.38, 2), 0)
self.assertEqual(tools.float_compare(leads[8].automated_probability, 50.0, 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 # 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") 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() 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[3].automated_probability, 4.21, 2), 0)
self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 2), 0) self.assertEqual(tools.float_compare(leads[8].automated_probability, 0.23, 2), 0)
+1 -1
View File
@@ -45,10 +45,10 @@ class TestDeliveryCost(common.TransactionCase):
# as the tests hereunder assume all the prices in USD, we must ensure # as the tests hereunder assume all the prices in USD, we must ensure
# that the company actually uses USD # that the company actually uses USD
# We do an invalidation so the cache is aware of it too. # We do an invalidation so the cache is aware of it too.
self.env.company.invalidate_recordset()
self.env.cr.execute( self.env.cr.execute(
"UPDATE res_company SET currency_id = %s WHERE id = %s", "UPDATE res_company SET currency_id = %s WHERE id = %s",
[self.env.ref('base.USD').id, self.env.company.id]) [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 self.pricelist.currency_id = self.env.ref('base.USD').id
def test_00_delivery_cost(self): def test_00_delivery_cost(self):
+9 -4
View File
@@ -181,10 +181,15 @@ class HrEmployeePrivate(models.Model):
if self.check_access_rights('read', raise_exception=False): if self.check_access_rights('read', raise_exception=False):
return super(HrEmployeePrivate, self)._read(fields) return super(HrEmployeePrivate, self)._read(fields)
res = self.env['hr.employee.public'].browse(self.ids).read(fields) # HACK: retrieve publicly available values from hr.employee.public and
for r in res: # copy them to the cache of self; non-public data will be missing from
record = self.browse(r['id']) # cache, and interpreted as an access error
record._update_cache({k:v for k,v in r.items() if k in fields}, validate=False) 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 @api.model
def _cron_check_work_permit_validity(self): def _cron_check_work_permit_validity(self):
@@ -158,17 +158,16 @@ class TestWorkEntryLeave(TestWorkEntryHolidaysBase):
'date_to': datetime(2022, 3, 25, 20), 'date_to': datetime(2022, 3, 25, 20),
'number_of_days': 4, 'number_of_days': 4,
}) })
can_cancel_field = leave._fields['can_cancel']
leave.with_user(SUPERUSER_ID).action_validate() leave.with_user(SUPERUSER_ID).action_validate()
# No work entries exist yet # No work entries exist yet
self.assertTrue(leave.can_cancel, "The leave should still be cancellable") self.assertTrue(leave.can_cancel, "The leave should still be cancellable")
# can not create in the future # can not create in the future
self.richard_emp.contract_ids._generate_work_entries(datetime(2022, 3, 21, 6), datetime(2022, 3, 25, 20)) 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)]) 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 # Work entries exist but are not locked yet
self.assertTrue(leave.can_cancel, "The leave should still be cancellable") self.assertTrue(leave.can_cancel, "The leave should still be cancellable")
work_entries.action_validate() work_entries.action_validate()
self.env.cache.invalidate([(can_cancel_field, leave.ids)]) leave.invalidate_recordset(['can_cancel'])
# Work entries locked # Work entries locked
self.assertFalse(leave.can_cancel, "The leave should not be cancellable") self.assertFalse(leave.can_cancel, "The leave should not be cancellable")
+15 -10
View File
@@ -89,13 +89,18 @@ class TestEGEdiCommon(AccountEdiTestCommon):
@classmethod @classmethod
def create_invoice(cls, **kwargs): def create_invoice(cls, **kwargs):
return (cls.env['account.move'] invoice = (
.with_context(edi_test_mode=True) cls.env['account.move']
.create({ .with_context(edi_test_mode=True)
'move_type': 'out_invoice', .create({
'partner_id': cls.partner_a.id, 'move_type': 'out_invoice',
'invoice_date': '2022-03-15', 'partner_id': cls.partner_a.id,
'date': '2022-03-15', 'invoice_date': '2022-03-15',
**kwargs, 'date': '2022-03-15',
'invoice_line_ids': [Command.create({**line_vals, }) for line_vals in kwargs.get('invoice_line_ids', [])] **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
+1
View File
@@ -1093,6 +1093,7 @@ class Message(models.Model):
def _invalidate_documents(self, model=None, res_id=None): def _invalidate_documents(self, model=None, res_id=None):
""" Invalidate the cache of the documents followed by ``self``. """ """ Invalidate the cache of the documents followed by ``self``. """
fnames = ['message_ids', 'message_needaction', 'message_needaction_counter'] fnames = ['message_ids', 'message_needaction', 'message_needaction_counter']
self.flush_recordset(['model', 'res_id'])
for record in self: for record in self:
model = model or record.model model = model or record.model
res_id = res_id or record.res_id res_id = res_id or record.res_id
@@ -357,7 +357,7 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
'parent_id': main_partner_2.id, 'parent_id': main_partner_2.id,
'company_id': self.env.ref('base.main_company').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 # We create a different BoM for the same product
comp3 = self.env['product.product'].create({ comp3 = self.env['product.product'].create({
@@ -33,6 +33,9 @@ class TestProjectReport(TestProjectCommon):
self.assertEqual(task_3.rating_avg, 0) self.assertEqual(task_3.rating_avg, 0)
self.assertEqual(task_3.rating_last_value, 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] tasks = [self.task_1, self.task_2, task_3]
for task in tasks: for task in tasks:
rating_values = task.read(['rating_avg', 'rating_last_value'])[0] rating_values = task.read(['rating_avg', 'rating_last_value'])[0]
+1
View File
@@ -638,6 +638,7 @@ class PurchaseOrder(models.Model):
# Invoice_ids may be filtered depending on the user. To ensure we get all # 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 # invoices related to the purchase order, we read them in sudo to fill the
# cache. # cache.
self.invalidate_model(['invoice_ids'])
self.sudo()._read(['invoice_ids']) self.sudo()._read(['invoice_ids'])
invoices = self.invoice_ids invoices = self.invoice_ids
@@ -602,7 +602,7 @@ class TestReorderingRule(TransactionCase):
"route_ids": [], "route_ids": [],
} }
) )
self.env.cache.invalidate() self.env.invalidate_all()
self.env["procurement.group"].run([procurement]) self.env["procurement.group"].run([procurement])
@@ -707,10 +707,11 @@ class TestReorderingRule(TransactionCase):
{'qty_forecast': -1, 'qty_to_order': 1}, {'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.invalidate_model(fnames=['qty_forecast', 'qty_to_order'])
orderpoint.product_id.invalidate_model(fnames=['virtual_available']) orderpoint.product_id.invalidate_model(fnames=['virtual_available'])
delivery.scheduled_date += td(days=7)
self.assertRecordValues(orderpoint, [ self.assertRecordValues(orderpoint, [
{'qty_forecast': 0, 'qty_to_order': 0}, {'qty_forecast': 0, 'qty_to_order': 0},
]) ])
+2 -4
View File
@@ -176,6 +176,7 @@ class TestSaleOrder(TestSaleCommon):
# upsell and invoice # upsell and invoice
self.sol_serv_order.write({'product_uom_qty': 10}) self.sol_serv_order.write({'product_uom_qty': 10})
# There is a bug with `new` and `_origin` # 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, # 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 # 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 # 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. # as origin are not updated, nor the fields that depends on it.
self.env.flush_all() self.env.flush_all()
for field in self.env['sale.order.line']._fields.values(): self.env.invalidate_all()
for res_id in list(self.env.cache._data[field]):
if not res_id:
self.env.cache._data[field].pop(res_id)
invoice3 = self.sale_order._create_invoices() invoice3 = self.sale_order._create_invoices()
self.assertEqual(len(invoice3.invoice_line_ids), 1, 'Sale: third invoice is missing lines') self.assertEqual(len(invoice3.invoice_line_ids), 1, 'Sale: third invoice is missing lines')
@@ -269,8 +269,6 @@ class TestProgramRules(TestSaleCouponCommon):
}) })
]}) ]})
# Invalidate total_order_count # Invalidate total_order_count
field_order_count = self.env['loyalty.program']._fields['order_count'] self.immediate_promotion_program.invalidate_recordset(['order_count', 'total_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._auto_rewards(order, self.immediate_promotion_program) 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") self.assertEqual(len(order.order_line.ids), 2, "The promo offer shouldn't have been applied as the number of uses is exceeded")
+3 -4
View File
@@ -93,6 +93,7 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
self.assertEqual(del_qties, del_qties_truth, 'Sale Stock: delivered quantities are wrong after complete delivery') 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 # Without timesheet, we manually set the delivered qty for the product serv_del
self.so.order_line.sorted()[1]['qty_delivered'] = 2.0 self.so.order_line.sorted()[1]['qty_delivered'] = 2.0
# There is a bug with `new` and `_origin` # 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, # 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 # 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 # 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. # as origin are not updated, nor the fields that depends on it.
self.env.flush_all() self.env.flush_all()
for field in self.env['sale.order.line']._fields.values(): self.env.invalidate_all()
for res_id in list(self.env.cache._data[field]):
if not res_id:
self.env.cache._data[field].pop(res_id)
inv_id = self.so._create_invoices() inv_id = self.so._create_invoices()
self.assertEqual(self.so.invoice_status, 'invoiced', self.assertEqual(self.so.invoice_status, 'invoiced',
'Sale Stock: so invoice_status should be "fully invoiced" after complete delivery and invoicing') 'Sale Stock: so invoice_status should be "fully invoiced" after complete delivery and invoicing')
@@ -9,6 +9,15 @@ class ReportStockQuantity(models.Model):
_auto = False _auto = False
_description = 'Stock Quantity Report' _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) date = fields.Date(string='Date', readonly=True)
product_tmpl_id = fields.Many2one('product.template', readonly=True) product_tmpl_id = fields.Many2one('product.template', readonly=True)
product_id = fields.Many2one('product.product', string='Product', readonly=True) product_id = fields.Many2one('product.product', string='Product', readonly=True)
+1 -1
View File
@@ -774,7 +774,7 @@ class TestCowViewSaving(TestViewSavingCommon):
'arch': '<div position="replace"><p>COMPARE</p></div>', 'arch': '<div position="replace"><p>COMPARE</p></div>',
'key': '_website_sale_comparison.product_add_to_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 # Simulate end of installation/update
View._create_all_specific_views(['_website_sale_comparison']) View._create_all_specific_views(['_website_sale_comparison'])
+7 -5
View File
@@ -1346,11 +1346,13 @@ class IrModelSelection(models.Model):
if selection.value == vals['value']: if selection.value == vals['value']:
continue continue
if selection.field_id.store: 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 # replace the value by the new one in the field's corresponding column
query = 'UPDATE "{table}" SET "{field}"=%s WHERE "{field}"=%s'.format( query = f'UPDATE "{model._table}" SET "{fname}"=%s WHERE "{fname}"=%s'
table=self.env[selection.field_id.model]._table,
field=selection.field_id.name,
)
self.env.cr.execute(query, [vals['value'], selection.value]) self.env.cr.execute(query, [vals['value'], selection.value])
result = super().write(vals) 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 """ Reflect the table of a many2many field for the given model, to make
it possible to delete it later when the module is uninstalled. it possible to delete it later when the module is uninstalled.
""" """
self.env.invalidate_all()
cr = self._cr cr = self._cr
query = """ SELECT 1 FROM ir_model_relation r, ir_module_module m 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 """ 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_module_module WHERE name=%s),
(SELECT id FROM ir_model WHERE model=%s)) """ (SELECT id FROM ir_model WHERE model=%s)) """
cr.execute(query, (table, self.env.uid, self.env.uid, module, model._name)) cr.execute(query, (table, self.env.uid, self.env.uid, module, model._name))
self.env.invalidate_all()
class IrModelAccess(models.Model): class IrModelAccess(models.Model):
+1 -2
View File
@@ -233,8 +233,7 @@ class IrRule(models.Model):
failing_rules += "\n\n" + _('Note: this might be a multi-company issue.') failing_rules += "\n\n" + _('Note: this might be a multi-company issue.')
# clean up the cache of records prefetched with display_name above # clean up the cache of records prefetched with display_name above
for record in records[:6]: records_sudo.invalidate_recordset()
record._cache.clear()
msg = f"{operation_error}\n\n{failing_records}\n{failing_user}\n\n{failing_rules}\n\n{resolution_info}" msg = f"{operation_error}\n\n{failing_records}\n{failing_user}\n\n{failing_rules}\n\n{resolution_info}"
return AccessError(msg) return AccessError(msg)
@@ -112,7 +112,8 @@ class MergePartnerAutomatic(models.TransientModel):
Partner = self.env['res.partner'] Partner = self.env['res.partner']
relations = self._get_fk_on('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: for table, column in relations:
if 'base_partner_merge_' in table: # ignore two tables 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 query = 'DELETE FROM "%(table)s" WHERE "%(column)s" IN %%s' % query_dic
self._cr.execute(query, (tuple(src_partners.ids),)) self._cr.execute(query, (tuple(src_partners.ids),))
self.env.invalidate_all()
@api.model @api.model
def _update_reference_fields(self, src_partners, dst_partner): def _update_reference_fields(self, src_partners, dst_partner):
""" Update all reference fields from the src_partner to dst_partner. """ Update all reference fields from the src_partner to dst_partner.
@@ -935,9 +935,8 @@ class Attachment(models.Model):
return return
comodel = self.env[self.res_model] comodel = self.env[self.res_model]
if 'res_id' in fnames and 'attachment_ids' in comodel: if 'res_id' in fnames and 'attachment_ids' in comodel:
field = comodel._fields['attachment_ids']
record = comodel.browse(self.res_id) record = comodel.browse(self.res_id)
self.env.cache.invalidate([(field, record._ids)]) record.invalidate_recordset(['attachment_ids'])
record.modified(['attachment_ids']) record.modified(['attachment_ids'])
return super(Attachment, self).modified(fnames, *args, **kwargs) return super(Attachment, self).modified(fnames, *args, **kwargs)
@@ -244,6 +244,7 @@ class TestFields(TransactionCaseWithUserDemo):
(0, 0, {'name': 'x_stuff_id', 'ttype': 'many2one', 'relation': 'ir.model'}), (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 # set 'x_stuff_id' refer to a model not loaded yet
self.cr.execute(""" self.cr.execute("""
UPDATE ir_model_fields UPDATE ir_model_fields
@@ -791,7 +792,6 @@ class TestFields(TransactionCaseWithUserDemo):
# switch to environment with user demo # switch to environment with user demo
records = records.with_user(self.user_demo) records = records.with_user(self.user_demo)
records.env.cache.invalidate()
# check that records are not accessible # check that records are not accessible
with self.assertRaises(AccessError): with self.assertRaises(AccessError):
@@ -1611,7 +1611,7 @@ class TestFields(TransactionCaseWithUserDemo):
""" test field access on new records vs real records. """ """ test field access on new records vs real records. """
Model = self.env['test_new_api.category'] Model = self.env['test_new_api.category']
real_record = Model.create({'name': 'Foo'}) real_record = Model.create({'name': 'Foo'})
self.env.cache.invalidate() self.env.invalidate_all()
new_origin = Model.new({'name': 'Bar'}, origin=real_record) new_origin = Model.new({'name': 'Bar'}, origin=real_record)
new_record = Model.new({'name': 'Baz'}) new_record = Model.new({'name': 'Baz'})
@@ -2080,6 +2080,7 @@ class TestFields(TransactionCaseWithUserDemo):
self.assertEqual(demo_discussion.messages, discussion.messages) self.assertEqual(demo_discussion.messages, discussion.messages)
# See YTI FIXME # See YTI FIXME
self.env.flush_all()
self.env.invalidate_all() self.env.invalidate_all()
# add a message as user demo # 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) self.assertEqual(parent.with_context(active_test=False).active_children_ids, act_children)
# check read() # check read()
self.env.cache.invalidate() self.env.invalidate_all()
self.assertEqual(parent.children_ids, act_children) self.assertEqual(parent.children_ids, act_children)
self.assertEqual(parent.all_children_ids, all_children) self.assertEqual(parent.all_children_ids, all_children)
self.assertEqual(parent.active_children_ids, act_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).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).all_children_ids, all_children)
self.assertEqual(parent.with_context(active_test=False).active_children_ids, act_children) self.assertEqual(parent.with_context(active_test=False).active_children_ids, act_children)
+20 -20
View File
@@ -49,7 +49,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'author': USER.id, 'author': USER.id,
'size': 0, 'size': 0,
} }
self.env.cache.invalidate() self.env.invalidate_all()
result = self.Message.onchange(values, 'discussion', field_onchange) result = self.Message.onchange(values, 'discussion', field_onchange)
self.assertIn('name', result['value']) self.assertIn('name', result['value'])
self.assertEqual(result['value']['name'], "[%s] %s" % (discussion.name, USER.name)) self.assertEqual(result['value']['name'], "[%s] %s" % (discussion.name, USER.name))
@@ -62,7 +62,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'author': USER.id, 'author': USER.id,
'size': 0, 'size': 0,
} }
self.env.cache.invalidate() self.env.invalidate_all()
result = self.Message.onchange(values, 'body', field_onchange) result = self.Message.onchange(values, 'body', field_onchange)
self.assertIn('size', result['value']) self.assertIn('size', result['value'])
self.assertEqual(result['value']['size'], len(BODY)) self.assertEqual(result['value']['size'], len(BODY))
@@ -76,7 +76,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'author': USER.id, 'author': USER.id,
'size': 0, 'size': 0,
} }
self.env.cache.invalidate() self.env.invalidate_all()
result = self.Message.onchange(values, 'body', field_onchange) result = self.Message.onchange(values, 'body', field_onchange)
self.assertNotIn('name', result['value']) self.assertNotIn('name', result['value'])
@@ -94,7 +94,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'root_categ': False, 'root_categ': False,
} }
self.env.cache.invalidate() self.env.invalidate_all()
result = Category.onchange(values, 'parent', field_onchange).get('value', {}) result = Category.onchange(values, 'parent', field_onchange).get('value', {})
self.assertIn('root_categ', result) self.assertIn('root_categ', result)
self.assertEqual(result['root_categ'], root.name_get()[0]) self.assertEqual(result['root_categ'], root.name_get()[0])
@@ -102,7 +102,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
values.update(result) values.update(result)
values['parent'] = False values['parent'] = False
self.env.cache.invalidate() self.env.invalidate_all()
result = Category.onchange(values, 'parent', field_onchange).get('value', {}) result = Category.onchange(values, 'parent', field_onchange).get('value', {})
self.assertIn('root_categ', result) self.assertIn('root_categ', result)
self.assertIs(result['root_categ'], False) 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) result = self.Discussion.onchange(values, 'name', field_onchange)
self.assertIn('messages', result['value']) self.assertIn('messages', result['value'])
self.assertEqual(result['value']['messages'], [ 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) result = self.Discussion.onchange(values, 'name', field_onchange)
self.assertIn('messages', result['value']) self.assertIn('messages', result['value'])
self.assertItemsEqual(result['value']['messages'], [ self.assertItemsEqual(result['value']['messages'], [
@@ -263,7 +263,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'lines': [Command.set([line1.id]), 'lines': [Command.set([line1.id]),
Command.create({'name': False, 'partner': False, 'tags': [Command.clear()]})], Command.create({'name': False, 'partner': False, 'tags': [Command.clear()]})],
} }
self.env.cache.invalidate() self.env.invalidate_all()
result = multi.onchange(values, 'partner', field_onchange) result = multi.onchange(values, 'partner', field_onchange)
self.assertEqual(result['value'], { self.assertEqual(result['value'], {
@@ -292,7 +292,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'partner': False, 'partner': False,
'tags': [Command.clear(), Command.create({'name': 'Tag'})]})], 'tags': [Command.clear(), Command.create({'name': 'Tag'})]})],
} }
self.env.cache.invalidate() self.env.invalidate_all()
result = multi.onchange(values, 'partner', field_onchange) result = multi.onchange(values, 'partner', field_onchange)
expected_value = { expected_value = {
'name': partner2.name, 'name': partner2.name,
@@ -313,13 +313,13 @@ class TestOnChange(SavepointCaseWithUserDemo):
self.assertEqual(result['value'], expected_value) self.assertEqual(result['value'], expected_value)
# ensure ID is not returned when asked and a many2many record is set to be created # 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})) result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.tags.id': None}))
self.assertEqual(result['value'], expected_value) self.assertEqual(result['value'], expected_value)
# ensure inverse of one2many field is not returned # 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})) result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.multi': None}))
self.assertEqual(result['value'], expected_value) self.assertEqual(result['value'], expected_value)
@@ -348,7 +348,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'messages': [Command.link(msg.id) for msg in discussion.messages], 'messages': [Command.link(msg.id) for msg in discussion.messages],
'participants': [Command.link(usr.id) for usr in discussion.participants], '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) result = discussion.onchange(values, 'moderator', field_onchange)
self.assertIn('participants', result['value']) 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') self.env['ir.default'].set('test_new_api.foo', 'value2', 666, condition='value1=42')
# setting 'value1' to 42 should trigger the change of 'value2' # 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} values = {'name': 'X', 'value1': 42, 'value2': False}
result = Foo.onchange(values, 'value1', field_onchange) result = Foo.onchange(values, 'value1', field_onchange)
self.assertEqual(result['value'], {'value2': 666}) self.assertEqual(result['value'], {'value2': 666})
# setting 'value1' to 24 should not trigger the change of 'value2' # 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} values = {'name': 'X', 'value1': 24, 'value2': False}
result = Foo.onchange(values, 'value1', field_onchange) result = Foo.onchange(values, 'value1', field_onchange)
self.assertEqual(result['value'], {}) self.assertEqual(result['value'], {})
@@ -442,7 +442,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
}) })
# check if server-side cache is working correctly # check if server-side cache is working correctly
self.env.cache.invalidate() self.env.invalidate_all()
self.assertIn(email, discussion.emails) self.assertIn(email, discussion.emails)
self.assertNotIn(email, discussion.important_emails) self.assertNotIn(email, discussion.important_emails)
email.important = True email.important = True
@@ -451,7 +451,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
# check that when trigger an onchange, we don't reset important emails # 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 # (force `invalidate` as but appear in onchange only when we get a cache
# miss) # miss)
self.env.cache.invalidate() self.env.invalidate_all()
self.assertEqual(len(discussion.messages), 4) self.assertEqual(len(discussion.messages), 4)
values = { values = {
'name': "Foo Bar", 'name': "Foo Bar",
@@ -462,7 +462,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
'important_messages': [Command.link(msg.id) for msg in discussion.important_messages], 'important_messages': [Command.link(msg.id) for msg in discussion.important_messages],
'important_emails': [Command.link(eml.id) for eml in discussion.important_emails], '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) result = discussion.onchange(values, 'name', field_onchange)
self.assertEqual( self.assertEqual(
@@ -494,13 +494,13 @@ class TestOnChange(SavepointCaseWithUserDemo):
'message_currency': self.env.user.name_get()[0], 'message_currency': self.env.user.name_get()[0],
} }
self.env.cache.invalidate() self.env.invalidate_all()
Message = self.env['test_new_api.related'] Message = self.env['test_new_api.related']
result = Message.onchange(value, 'message', field_onchange) result = Message.onchange(value, 'message', field_onchange)
self.assertEqual(result['value'], onchange_result) 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'] Message = self.env(user=self.user_demo.id)['test_new_api.related']
result = Message.onchange(value, 'message', field_onchange) result = Message.onchange(value, 'message', field_onchange)
@@ -530,7 +530,7 @@ class TestOnChange(SavepointCaseWithUserDemo):
# changing 'discussion' on message should not read 'messages' on discussion # changing 'discussion' on message should not read 'messages' on discussion
with patch.object(type(discussion), 'read', mock_read, create=True): 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.Message.onchange(values, 'discussion', field_onchange)
self.assertFalse(called[0], "discussion.messages has been read") self.assertFalse(called[0], "discussion.messages has been read")
@@ -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) 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 # 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.name, "New Name")
self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom") self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom")
+1 -1
View File
@@ -1734,7 +1734,7 @@ def users(*logins):
func(*args, **kwargs) func(*args, **kwargs)
# Invalidate the cache between subtests, in order to not reuse # Invalidate the cache between subtests, in order to not reuse
# the former user's cache (`test_read_mail`, `test_write_mail`) # the former user's cache (`test_read_mail`, `test_write_mail`)
self.env.cache.invalidate() self.env.invalidate_all()
finally: finally:
self.uid = old_uid self.uid = old_uid