# Copyright 2022 Akretion (https://www.akretion.com). # Copyright 2024 Tecnativa - Víctor Martínez # @author Kévin Roche # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import Command from odoo.tests.common import TransactionCase from odoo.tools import mute_logger class TestTrackingManager(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() cls.partner_categ_1, cls.partner_categ_2, cls.partner_categ_3 = cls.env[ "res.partner.category" ].create( [ {"name": "FOO"}, {"name": "BAR"}, {"name": "TOOH"}, ] ) cls.partner = cls.env["res.partner"].create( { "name": "Foo", "user_ids": [(Command.CREATE, 0, {"login": "007"})], "category_id": [(Command.SET, 0, [cls.partner_categ_1.id])], } ) cls.partner_model = cls.env.ref("base.model_res_partner") cls._active_tracking(["user_ids", "category_id"]) cls.flush_tracking() cls.partner.message_ids.unlink() @classmethod def _active_tracking(cls, fields_list): cls.partner_model.active_custom_tracking = True for field in cls._get_fields(fields_list): field.custom_tracking = True @classmethod def _get_fields(cls, fields_list): return cls.partner_model.field_id.filtered(lambda s: s.name in fields_list) def test_not_tracked(self): field = self._get_fields(["mobile"])[0] self.assertFalse(field.native_tracking) self.assertFalse(field.custom_tracking) def test_native_tracked(self): field = self._get_fields(["email"])[0] self.assertTrue(field.native_tracking) self.assertTrue(field.custom_tracking) def test_update_tracked(self): field = self._get_fields(["mobile"])[0] self.assertFalse(field.native_tracking) self.partner_model.automatic_custom_tracking = True self.partner_model.update_custom_tracking() self.assertTrue(field.custom_tracking) @classmethod def flush_tracking(cls): """Force the creation of tracking values.""" cls.env["base"].flush_model() cls.env.cr.precommit.run() @property def messages(self): # Force the creation of tracking values self.flush_tracking() return self.partner.message_ids def test_m2m_add_line(self): self.partner = self.env["res.partner"].browse(self.partner.id) self.partner.write( {"category_id": [(Command.LINK, self.partner_categ_2.id, 0)]} ) self.assertEqual(len(self.messages), 1) tracking = self.messages.tracking_value_ids[0] self.assertEqual(len(tracking), 1) self.assertEqual(tracking.old_value_char, "FOO") self.assertEqual(tracking.new_value_char, "FOO, BAR") def test_m2m_delete_line(self): self.partner.write( {"category_id": [(Command.UNLINK, self.partner_categ_1.id, 0)]} ) self.assertEqual(len(self.messages), 1) tracking = self.messages.tracking_value_ids self.assertEqual(len(tracking), 1) self.assertEqual(tracking.old_value_char, "FOO") self.assertEqual(tracking.new_value_char, "") def test_m2m_multi_line(self): self.partner.write( { "category_id": [ ( Command.SET, 0, [ self.partner_categ_2.id, self.partner_categ_3.id, ], ) ] } ) self.assertEqual(len(self.messages), 1) tracking = self.messages.tracking_value_ids self.assertEqual(len(tracking), 1) self.assertEqual(tracking.old_value_char, "FOO") self.assertEqual(tracking.new_value_char, "BAR, TOOH") def test_o2m_create_indirectly(self): self.partner.write({"user_ids": [(Command.CREATE, 0, {"login": "1234567890"})]}) self.assertEqual(len(self.messages), 2) self.assertEqual(self.messages[0].body.count("New"), 1) @mute_logger("odoo.models.unlink") def test_o2m_unlink_indirectly(self): self.partner.write( {"user_ids": [(Command.DELETE, self.partner.user_ids[0].id)]} ) self.assertEqual(len(self.messages), 1) self.assertIn("Delete", self.messages.body) def test_o2m_write_indirectly(self): self.partner.write( { "user_ids": [ (Command.UPDATE, self.partner.user_ids[0].id, {"login": "123"}) ], } ) self.assertEqual(len(self.messages), 1) self.assertIn("Change", self.messages.body) def test_o2m_write_indirectly_on_not_tracked_fields(self): # Active custom tracking on res.users and remove tracking on login res_users_model = self.env["ir.model"].search([("model", "=", "res.users")]) res_users_model.active_custom_tracking = True login_field = res_users_model.field_id.filtered(lambda x: x.name == "login") login_field.custom_tracking = False self.partner.write( { "user_ids": [ (Command.UPDATE, self.partner.user_ids[0].id, {"login": "123"}) ], } ) self.assertEqual(len(self.messages), 0) @mute_logger("odoo.models.unlink") def test_o2m_create_and_unlink_indirectly(self): self.partner.write( { "user_ids": [ (Command.DELETE, self.partner.user_ids[0].id, 0), (Command.CREATE, 0, {"login": "1234567890"}), ] } ) self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("New"), 1) self.assertEqual(self.messages.body.count("Delete"), 1) def test_o2m_update_m2m_indirectly(self): self.group_extra = self.env["res.groups"].create({"name": "Test group"}) self.partner.write( { "user_ids": [ ( Command.UPDATE, self.partner.user_ids[0].id, { "groups_id": [ ( 6, 0, [ self.env.ref("base.group_user").id, self.group_extra.id, ], ) ] }, ), ] } ) self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("Changed"), 1) def test_o2m_update_m2o_indirectly(self): user = self.partner.user_ids[0] action = self.env["ir.actions.act_window"].create( {"name": "test", "type": "ir.actions.act_window", "res_model": user._name} ) self.partner.write( {"user_ids": [(Command.UPDATE, user.id, {"action_id": action.id})]} ) self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("Changed"), 1) @mute_logger("odoo.models.unlink") def test_o2m_write_and_unlink_indirectly(self): # when editing a o2m in some special case # like the computed field amount_tax of purchase order line # some write can be done on a line before behind deleted # line._compute_amount() is called manually inside see link behind # https://github.com/odoo/odoo/blob/009f35f3d3659792ef18ac510a6ec323708becec/addons/purchase/models/purchase.py#L28 # noqa # So we are in a case that we do some change and them we delete them # in that case we should only have one message of deletation # and no error self.partner.write( { "user_ids": [ (Command.UPDATE, self.partner.user_ids[0].id, {"login": "123"}) ], } ) self.partner.write( { "user_ids": [(Command.DELETE, self.partner.user_ids[0].id, 0)], } ) self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("Change"), 0) self.assertEqual(self.messages.body.count("Delete"), 1) def test_o2m_create_directly(self): # Add custom context to prevent message from mail addon self.env["res.users"].with_context( mail_create_nolog=True, mail_notrack=True ).create( { "name": "1234567890", "login": "1234567890", "partner_id": self.partner.id, } ) self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("New"), 1) @mute_logger("odoo.models.unlink") def test_o2m_unlink_directly(self): self.partner.user_ids.unlink() self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("Delete"), 1) def test_o2m_update_directly(self): self.partner.user_ids.write({"login": "0987654321"}) self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("Change :"), 1) @mute_logger("odoo.models.unlink") def test_o2m_write_and_unlink_directly(self): # see explanation of test_o2m_write_and_unlink_indirectly self.partner.user_ids.write({"login": "0987654321"}) self.partner.user_ids.unlink() self.assertEqual(len(self.messages), 1) self.assertEqual(self.messages.body.count("Change"), 0) self.assertEqual(self.messages.body.count("Delete"), 1) def test_o2m_update_record(self): self.env.ref("base.field_res_partner__child_ids").custom_tracking = True child = self.env["res.partner"].create( {"name": "Test child", "parent_id": self.partner.id} ) child.write({"parent_id": False}) self.assertEqual(len(self.messages), 1)