From 2c75742b9991d9364799e3fe58294646f8edffa0 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Mon, 6 Oct 2025 14:47:26 +0200 Subject: [PATCH] [FIX] auditlog: swap out tocompute in ThrowAwayCache as well Steps to Reproduce: Install auditlog and purchase_tier_validation. Create an auditlog rule on the Tier Exception model. Create a new tier exception record for the purchase order model without any fields. In an export, check the value for the stored computed field `model_name`. Desired outcome: The value for `model_name` is 'purchase.order'. Actual outcome: There is no value for the stored computed field `model_name`. When trying to edit the `Fields` model, which depends on a valid value for this field, an error occurs saying 'Compute method failed to assign tier.validation.exception(1,).valid_model_field_ids'. Analysis: When `read` is executed under the scope of the new `ThrowAwayCache` decorator (#3374), recomputes may be performed and dequeued from `self.env.cache.transaction.tocompute`. To make sure the recomputed values still end up in the non-volatile as well, we need to preserve the tocompute attribute from the original transaction. --- auditlog/models/rule.py | 11 ++++++++++- test_auditlog/tests/test_account_move_reverse.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/auditlog/models/rule.py b/auditlog/models/rule.py index 1cbabdfa3c8..0336ed3e551 100644 --- a/auditlog/models/rule.py +++ b/auditlog/models/rule.py @@ -2,9 +2,11 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import copy +from collections import defaultdict from odoo import Command, _, api, fields, models from odoo.exceptions import UserError +from odoo.tools.misc import OrderedSet FIELDS_BLACKLIST = [ "id", @@ -61,7 +63,7 @@ def __init__(self, env): self._transaction = env.transaction def __enter__(self): - """Replace the cache on all envs and on the transaction. + """Replace the cache + tocompute on all envs and on the transaction. It is not enough to replace the cache on the current env, because once a sudo is executed under the scope of this context manager, another new @@ -69,6 +71,12 @@ def __enter__(self): don't swap them all out here. """ self._original_cache = self._transaction.cache + # Copy the sets of records, which are popped on recompute but do not + # copy the keys because they do not match the original field object + # afterwards. + self._original_tocompute = defaultdict(OrderedSet) + for key, value in self._transaction.tocompute.items(): + self._original_tocompute[key] = OrderedSet(value) temporary_cache = api.Cache() for env in self._transaction.envs: env.cache = temporary_cache @@ -80,6 +88,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): for env in self._transaction.envs: env.cache = self._original_cache self._transaction.cache = self._original_cache + self._transaction.tocompute = self._original_tocompute class AuditlogRule(models.Model): diff --git a/test_auditlog/tests/test_account_move_reverse.py b/test_auditlog/tests/test_account_move_reverse.py index aa62f32778a..ff68ab7df0f 100644 --- a/test_auditlog/tests/test_account_move_reverse.py +++ b/test_auditlog/tests/test_account_move_reverse.py @@ -145,6 +145,20 @@ def setUp(self): ) self.rule.subscribe() + def test_in_invoice_stored_related_field(self): + """Stored related fields are computed properly""" + self.invoice.name = "TEST" + line = self.env["account.move.line"].create( + { + "display_type": "line_note", + "name": __name__, + "move_id": self.invoice.id, + } + ) + self.assertEqual(line.move_name, "TEST") + self.invoice.name = "TEST2" + self.assertEqual(line.move_name, "TEST2") + def test_in_invoice_create_refund(self): """Test creating a refund from a vendor bill.