Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from . import test_tracking_manager
from . import test_mail_tracking_value

View File

@@ -0,0 +1,183 @@
# Copyright 2025 Tecnativa - Víctor Martínez
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.base.tests.common import BaseCommon
class TestMailTracking(BaseCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.MailTracking = cls.env["mail.tracking.value"]
def test_create_tracking_values_html(self):
initial_value = "<p>Initial Value</p>"
new_value = "<p>New Value</p>"
col_name = "comment"
col_info = {"type": "html"}
record = self.env["res.partner"].create({"name": "Test Partner"})
values = self.MailTracking._create_tracking_values(
initial_value, new_value, col_name, col_info, record
)
self.assertEqual(values["old_value_char"], "Initial Value")
self.assertEqual(values["new_value_char"], "New Value")
def _test_create_tracking_values_property(self, values):
property_type_mapped = {
"char": "char",
"boolean": "integer",
"integer": "integer",
"float": "float",
"date": "datetime",
"datetime": "datetime",
"selection": "char",
"tags": "char",
"many2one": "integer",
"many2many": "char",
}
test_properties_info = {
"property_01": {"string": "property_01", "type": "char"},
"property_02": {"string": "property_02", "type": "boolean"},
"property_03": {"string": "property_03", "type": "integer"},
"property_04": {"string": "property_04", "type": "float"},
"property_05": {"string": "property_05", "type": "date"},
"property_06": {"string": "property_06", "type": "datetime"},
"property_07": {
"string": "property_07",
"type": "selection",
"selection": [["key1", "value1"], ["key2", "value2"]],
},
"property_08": {"string": "property_08", "type": "tags"},
"property_09": {
"string": "property_09",
"type": "many2one",
"comodel": self.partner._name,
},
"property_10": {
"string": "property_10",
"type": "many2many",
"comodel": self.partner._name,
},
}
for p_name, col_info in test_properties_info.items():
initial_value = values[p_name][0]
new_value = values[p_name][1]
res = self.MailTracking._create_tracking_values_property(
initial_value, new_value, "title", col_info, self.partner
)
del res["field_info"]
f_name = property_type_mapped[col_info["type"]]
expected_old_value = initial_value
expected_new_value = new_value
if col_info["type"] == "date":
expected_old_value = (
f"{expected_old_value} 00:00:00" if expected_old_value else False
)
expected_new_value = (
f"{expected_new_value} 00:00:00" if expected_new_value else False
)
elif col_info["type"] == "selection":
expected_old_value = values[p_name][2]
expected_new_value = values[p_name][3]
elif col_info["type"] == "tags":
expected_old_value = (
", ".join(value for value in expected_old_value)
if expected_old_value
else ""
)
expected_new_value = (
", ".join(value for value in expected_new_value)
if expected_new_value
else ""
)
elif col_info["type"] == "many2one":
del res["old_value_char"]
del res["new_value_char"]
elif col_info["type"] == "many2many":
comodel = self.env[col_info["comodel"]]
expected_old_value = (
comodel.browse(expected_old_value) if expected_old_value else False
)
expected_new_value = (
comodel.browse(expected_new_value) if expected_new_value else False
)
expected_old_value = (
", ".join(expected_old_value.mapped("display_name"))
if expected_old_value
else ""
)
expected_new_value = (
", ".join(expected_new_value.mapped("display_name"))
if expected_new_value
else ""
)
expected_values = {
f"old_value_{f_name}": expected_old_value,
f"new_value_{f_name}": expected_new_value,
}
self.assertEqual(res, expected_values)
def test_mail_tracking_value_properties(self):
partner_extra = self.env["res.partner"].create({"name": "Test partner extra"})
test_properties_01 = {
# property: initial_value, new_value
"property_01": ("", "value1"),
"property_02": (False, True),
"property_03": (0, 10),
"property_04": (0, 10.10),
"property_05": (False, "2025-01-01"),
"property_06": (False, "2025-01-01 00:00:00"),
"property_07": (False, "key1", "", "value1"),
"property_08": (False, ["tag1", "tag2"]),
"property_09": (False, self.partner.id),
"property_10": (False, [self.partner.id, partner_extra.id]),
}
# Test all the property types using as fake title field because there is no
# property field in base to test.
# We do not want to create a FakeModel and add the property field in partner
# because the partner_property module could have conflicts.
# 1- Test the case that all the initial values were empty and now have a value
self._test_create_tracking_values_property(test_properties_01)
# 2- Test the case that all the initial values had something set and now have
# a different value
test_properties_02 = {
# property: initial_value, new_value
"property_01": ("value1", "value2"),
"property_02": (True, False),
"property_03": (10, 11),
"property_04": (10.10, 11.10),
"property_05": ("2025-01-01", "2025-01-02"),
"property_06": ("2025-01-01 00:00:00", "2025-01-02 00:00:00"),
"property_07": ("key1", "key2", "value1", "value2"),
"property_08": (
["tag1", "tag2"],
[
"tag1",
],
),
"property_09": (self.partner.id, partner_extra.id),
"property_10": (
[self.partner.id, partner_extra.id],
[
self.partner.id,
],
),
}
self._test_create_tracking_values_property(test_properties_02)
# 3- Test the case that all initial values had something set and now has
# no value
test_properties_03 = {
# property: initial_value, new_value
"property_01": ("value2", ""),
"property_02": (False, True),
"property_03": (11, 0),
"property_04": (11.10, 0),
"property_05": ("2025-01-02", False),
"property_06": ("2025-01-02 00:00:00", False),
"property_07": ("key1", False, "value1", ""),
"property_08": (["tag1", "tag2"], False),
"property_09": (self.partner.id, False),
"property_10": ([self.partner.id, partner_extra.id], False),
}
self._test_create_tracking_values_property(test_properties_03)

View File

@@ -0,0 +1,273 @@
# Copyright 2022 Akretion (https://www.akretion.com).
# Copyright 2024 Tecnativa - Víctor Martínez
# @author Kévin Roche <kevin.roche@akretion.com>
# 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)