Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,5 @@
from . import test_changeset_flow
from . import test_changeset_field_type
from . import test_changeset_origin
from . import test_changeset_field_rule
from . import test_changeset_security

View file

@ -0,0 +1,86 @@
# Copyright 2015-2017 Camptocamp SA
# Copyright 2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
class ChangesetTestCommon(object):
def assert_changeset(self, record, expected_source, expected_changes):
"""Check if a changeset has been created according to expected values
The record should have no prior changeset than the one created in the
test (so it has exactly 1 changeset).
The expected changes are tuples with (field, origin_value,
new_value, state)
:param record: record of record having a changeset
:param expected_changes: contains tuples with the changes
:type expected_changes: list(tuple))
"""
changeset = self.env["record.changeset"].search(
[("model", "=", record._name), ("res_id", "=", record.id)]
)
self.assertEqual(
len(changeset), 1, "1 changeset expected, got {}".format(changeset)
)
self.assertEqual(changeset.source, expected_source)
changes = changeset.change_ids
missing = []
for expected_change in expected_changes:
for change in changes:
if (
change.field_id,
change.get_origin_value(),
change.get_new_value(),
change.state,
) == expected_change:
changes -= change
break
else:
missing.append(expected_change)
message = ""
for field, origin_value, new_value, state in missing:
message += (
"- field: '%s', origin_value: '%s', "
"new_value: '%s', state: '%s'\n"
% (field.name, origin_value, new_value, state)
)
for change in changes:
message += (
"+ field: '%s', origin_value: '%s', "
"new_value: '%s', state: '%s'\n"
% (
change.field_id.name,
change.get_origin_value(),
change.get_new_value(),
change.state,
)
)
if message:
raise AssertionError("Changes do not match\n\n:%s" % message)
def _create_changeset(self, record, changes):
"""Create a changeset and its associated changes
:param record: 'record' record
:param changes: list of changes [(field, new value, state)]
:returns: 'record.changeset' record
"""
ChangesetChange = self.env["record.changeset.change"]
get_field = ChangesetChange.get_field_for_type
change_values = []
for field, value, state in changes:
change = {
"field_id": field.id,
# write in the field of the appropriate type for the
# origin field (char, many2one, ...)
get_field(field, "new"): value,
"state": state,
}
change_values.append((0, 0, change))
values = {
"model": record._name,
"res_id": record.id,
"change_ids": change_values,
}
return self.env["record.changeset"].create(values)

View file

@ -0,0 +1,73 @@
# Copyright 2015-2017 Camptocamp SA
# Copyright 2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests import common
class TestChangesetFieldRule(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company_model_id = cls.env.ref("base.model_res_company").id
cls.field_name = cls.env.ref("base.field_res_partner__name")
cls.field_street = cls.env.ref("base.field_res_partner__street")
def test_get_rules(self):
ChangesetFieldRule = self.env["changeset.field.rule"]
ChangesetFieldRule.search([]).unlink()
rule1 = ChangesetFieldRule.create(
{"field_id": self.field_name.id, "action": "validate"}
)
rule2 = ChangesetFieldRule.create(
{"field_id": self.field_street.id, "action": "never"}
)
get_rules = ChangesetFieldRule.get_rules(None, "res.partner")
self.assertEqual(get_rules, {"name": rule1, "street": rule2})
def test_get_rules_source(self):
ChangesetFieldRule = self.env["changeset.field.rule"]
ChangesetFieldRule.search([]).unlink()
rule1 = ChangesetFieldRule.create(
{"field_id": self.field_name.id, "action": "validate"}
)
rule2 = ChangesetFieldRule.create(
{"field_id": self.field_street.id, "action": "never"}
)
rule3 = ChangesetFieldRule.create(
{
"source_model_id": self.company_model_id,
"field_id": self.field_street.id,
"action": "never",
}
)
model = ChangesetFieldRule
rules = model.get_rules(None, "res.partner")
self.assertEqual(rules, {"name": rule1, "street": rule2})
rules = model.get_rules("res.company", "res.partner")
self.assertEqual(rules, {"name": rule1, "street": rule3})
def test_get_rules_cache(self):
ChangesetFieldRule = self.env["changeset.field.rule"]
ChangesetFieldRule.search([]).unlink()
rule = ChangesetFieldRule.create(
{"field_id": self.field_name.id, "action": "validate"}
)
self.assertEqual(
ChangesetFieldRule.get_rules(None, "res.partner")["name"].action, "validate"
)
# Write on cursor to bypass the cache invalidation for the
# matter of the test
self.env.cr.execute(
"UPDATE changeset_field_rule " "SET action = 'never' " "WHERE id = %s",
(rule.id,),
)
self.assertEqual(
ChangesetFieldRule.get_rules(None, "res.partner")["name"].action, "validate"
)
rule.action = "auto"
self.assertEqual(
ChangesetFieldRule.get_rules(None, "res.partner")["name"].action, "auto"
)
rule.unlink()
self.assertFalse(ChangesetFieldRule.get_rules(None, "res.partner"))

View file

@ -0,0 +1,301 @@
# Copyright 2015-2017 Camptocamp SA
# Copyright 2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from markupsafe import Markup
from odoo import fields
from odoo.tests.common import TransactionCase
from ..models.base import disable_changeset
from .common import ChangesetTestCommon
class TestChangesetFieldType(ChangesetTestCommon, TransactionCase):
"""Check that changeset changes are stored expectingly to their types"""
@classmethod
def _setup_rules(cls):
ChangesetFieldRule = cls.env["changeset.field.rule"]
ChangesetFieldRule.search([]).unlink()
fields = (
("char", "ref"),
("text", "comment"),
("boolean", "is_company"),
("date", "date"),
("integer", "color"),
("float", "partner_latitude"),
("selection", "type"),
("many2one", "country_id"),
("many2many", "category_id"),
("one2many", "user_ids"),
("binary", "image_1920"),
)
for field_type, field in fields:
attr_name = "field_%s" % field_type
field_record = cls.env["ir.model.fields"].search(
[("model", "=", "res.partner"), ("name", "=", field)]
)
cls.assertTrue(field_record, "Field %s not available" % field)
# set attribute such as 'self.field_char' is a
# ir.model.fields record of the field res_partner.ref
setattr(cls, attr_name, field_record)
ChangesetFieldRule.create(
{"field_id": field_record.id, "action": "validate"}
)
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._setup_rules()
cls.partner = cls.env["res.partner"].create(
{"name": "Original Name", "street": "Original Street"}
)
# Add context for this test for compatibility with other modules' tests
cls.partner = cls.partner.with_context(test_record_changeset=True)
def test_new_changeset_char(self):
"""Add a new changeset on a Char field"""
self.partner.write({self.field_char.name: "New value"})
self.assert_changeset(
self.partner,
self.env.user,
[
(
self.field_char,
self.partner[self.field_char.name],
"New value",
"draft",
)
],
)
def test_new_changeset_text(self):
"""Add a new changeset on a Text field"""
self.partner.write({self.field_text.name: "New comment\non 2 lines"})
self.assert_changeset(
self.partner,
self.env.user,
[
(
self.field_text,
self.partner[self.field_text.name],
"New comment\non 2 lines",
"draft",
)
],
)
def test_new_changeset_boolean(self):
"""Add a new changeset on a Boolean field"""
# ensure the changeset has to change the value
self.partner.with_context(__no_changeset=disable_changeset).write(
{self.field_boolean.name: False}
)
self.partner.write({self.field_boolean.name: True})
self.assert_changeset(
self.partner,
self.env.user,
[
(
self.field_boolean,
self.partner[self.field_boolean.name],
True,
"draft",
)
],
)
def test_new_changeset_date(self):
"""Add a new changeset on a Date field"""
self.partner.write({self.field_date.name: "2015-09-15"})
self.assert_changeset(
self.partner,
self.env.user,
[
(
self.field_date,
self.partner[self.field_date.name],
fields.Date.from_string("2015-09-15"),
"draft",
)
],
)
def test_new_changeset_integer(self):
"""Add a new changeset on a Integer field"""
self.partner.write({self.field_integer.name: 42})
self.assert_changeset(
self.partner,
self.env.user,
[(self.field_integer, self.partner[self.field_integer.name], 42, "draft")],
)
def test_new_changeset_float(self):
"""Add a new changeset on a Float field"""
self.partner.write({self.field_float.name: 3.1415})
self.assert_changeset(
self.partner,
self.env.user,
[(self.field_float, self.partner[self.field_float.name], 3.1415, "draft")],
)
def test_new_changeset_selection(self):
"""Add a new changeset on a Selection field"""
self.partner.write({self.field_selection.name: "delivery"})
self.assert_changeset(
self.partner,
self.env.user,
[
(
self.field_selection,
self.partner[self.field_selection.name],
"delivery",
"draft",
)
],
)
def test_new_changeset_many2one(self):
"""Add a new changeset on a Many2one field"""
self.partner.with_context(__no_changeset=disable_changeset).write(
{self.field_many2one.name: self.env.ref("base.fr").id}
)
self.partner.write({self.field_many2one.name: self.env.ref("base.ch").id})
self.assert_changeset(
self.partner,
self.env.user,
[
(
self.field_many2one,
self.partner[self.field_many2one.name],
self.env.ref("base.ch"),
"draft",
)
],
)
def test_new_changeset_many2many(self):
"""Add a new changeset on a Many2many field is not supported"""
with self.assertRaises(NotImplementedError):
self.partner.write(
{self.field_many2many.name: [self.env.ref("base.ch").id]}
)
def test_new_changeset_one2many(self):
"""Add a new changeset on a One2many field is not supported"""
with self.assertRaises(NotImplementedError):
self.partner.write(
{self.field_one2many.name: [self.env.ref("base.user_root").id]}
)
def test_new_changeset_binary(self):
"""Add a new changeset on a Binary field is not supported"""
with self.assertRaises(NotImplementedError):
self.partner.write({self.field_binary.name: "xyz"})
def test_apply_char(self):
"""Apply a change on a Char field"""
changes = [(self.field_char, "New Ref", "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertEqual(self.partner[self.field_char.name], "New Ref")
def test_apply_text(self):
"""Apply a change on a Text field"""
changes = [(self.field_text, "New comment\non 2 lines", "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertEqual(
self.partner[self.field_text.name], Markup("<p>New comment\non 2 lines</p>")
)
def test_apply_boolean(self):
"""Apply a change on a Boolean field"""
# ensure the changeset has to change the value
self.partner.write({self.field_boolean.name: False})
changes = [(self.field_boolean, True, "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertEqual(self.partner[self.field_boolean.name], True)
# Cannot do this while it is on the same transaction. The cache may not
# be updated
# changes = [(self.field_boolean, False, 'draft')]
# changeset = self._create_changeset(self.partner, changes)
# changeset.change_ids.apply()
# self.assertEqual(self.partner[self.field_boolean.name], False)
def test_apply_date(self):
"""Apply a change on a Date field"""
changes = [(self.field_date, "2015-09-15", "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertAlmostEqual(
self.partner[self.field_date.name], fields.Date.from_string("2015-09-15")
)
def test_apply_integer(self):
"""Apply a change on a Integer field"""
changes = [(self.field_integer, 42, "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertAlmostEqual(self.partner[self.field_integer.name], 42)
def test_apply_float(self):
"""Apply a change on a Float field"""
changes = [(self.field_float, 52.47, "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertAlmostEqual(self.partner[self.field_float.name], 52.47)
def test_apply_selection(self):
"""Apply a change on a Selection field"""
changes = [(self.field_selection, "delivery", "draft")]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertAlmostEqual(self.partner[self.field_selection.name], "delivery")
def test_apply_many2one(self):
"""Apply a change on a Many2one field"""
self.partner.with_context(__no_changeset=disable_changeset).write(
{self.field_many2one.name: self.env.ref("base.fr").id}
)
changes = [
(
self.field_many2one,
"res.country,%d" % self.env.ref("base.ch").id,
"draft",
)
]
changeset = self._create_changeset(self.partner, changes)
changeset.change_ids.apply()
self.assertEqual(
self.partner[self.field_many2one.name], self.env.ref("base.ch")
)
def test_apply_many2many(self):
"""Apply a change on a Many2many field is not supported"""
changes = [(self.field_many2many, self.env.ref("base.ch").id, "draft")]
with self.assertRaises(NotImplementedError):
self._create_changeset(self.partner, changes)
def test_apply_one2many(self):
"""Apply a change on a One2many field is not supported"""
changes = [
(
self.field_one2many,
[self.env.ref("base.user_root").id, self.env.ref("base.user_demo").id],
"draft",
)
]
with self.assertRaises(NotImplementedError):
self._create_changeset(self.partner, changes)
def test_apply_binary(self):
"""Apply a change on a Binary field is not supported"""
changes = [(self.field_one2many, "", "draft")]
with self.assertRaises(NotImplementedError):
self._create_changeset(self.partner, changes)

View file

@ -0,0 +1,485 @@
# Copyright 2015-2017 Camptocamp SA
# Copyright 2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime, timedelta
from lxml import etree
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
from ..models.base import disable_changeset
from .common import ChangesetTestCommon
class TestChangesetFlow(ChangesetTestCommon, TransactionCase):
"""Check how changeset are generated and applied based on the rules.
We do not really care about the types of the fields in this test
suite, so we only use 'char' fields. We have to ensure that the
general changeset flows work as expected, that is:
* create a changeset when a manual/system write is made on partner
* create a changeset according to the changeset rules when a source model
is specified
* apply a changeset change writes the value on the partner
* apply a whole changeset writes all the changes' values on the partner
* changes in state 'cancel' or 'done' do not write on the partner
* when all the changes are either 'cancel' or 'done', the changeset
becomes 'done'
"""
@classmethod
def _setup_rules(cls):
ChangesetFieldRule = cls.env["changeset.field.rule"]
ChangesetFieldRule.search([]).unlink()
cls.field_name = cls.env.ref("base.field_res_partner__name")
cls.field_street = cls.env.ref("base.field_res_partner__street")
cls.field_street2 = cls.env.ref("base.field_res_partner__street2")
ChangesetFieldRule.create({"field_id": cls.field_name.id, "action": "auto"})
ChangesetFieldRule.create(
{"field_id": cls.field_street.id, "action": "validate"}
)
ChangesetFieldRule.create({"field_id": cls.field_street2.id, "action": "never"})
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._setup_rules()
cls.demo_user = cls.env.ref("base.user_demo")
cls.partner = (
cls.env["res.partner"]
.with_user(cls.demo_user)
.create({"name": "X", "street": "street X", "street2": "street2 X"})
)
# Add context for this test for compatibility with other modules' tests
cls.partner = cls.partner.with_context(test_record_changeset=True)
def test_get_view(self):
"""For privileged users, the smart button is present on the form"""
view = self.env.ref("base.view_partner_form")
def get_nodes(user):
arch = etree.XML(
self.env["res.partner"]
.with_user(user)
.get_view(view_id=view.id)["arch"]
)
return len(
arch.xpath(
"//div[@name='button_box']"
"/button[@name='action_record_changeset_change_view']"
)
)
self.assertTrue(get_nodes(self.env.ref("base.user_admin")))
self.assertFalse(get_nodes(self.env.ref("base.user_demo")))
def test_new_changeset(self):
"""Add a new changeset on a partner
A new changeset is created when we write on a partner
"""
self.partner.write({"name": "Y", "street": "street Y", "street2": "street2 Y"})
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(self.partner.count_pending_changeset_changes, 1)
self.assert_changeset(
self.partner,
self.demo_user,
[
(self.field_name, "X", "Y", "done"),
(self.field_street, "street X", "street Y", "draft"),
(self.field_street2, "street2 X", "street2 Y", "cancel"),
],
)
self.assertEqual(self.partner.name, "Y")
self.assertEqual(self.partner.street, "street X")
self.assertEqual(self.partner.street2, "street2 X")
# Pending Changes widget can be rendered for the unprivileged user
self.env.invalidate_all()
self.env["record.changeset.change"].with_user(
self.demo_user
).get_changeset_changes_by_field(self.partner._name, self.partner.id)
def test_create_new_changeset(self):
"""Create a new partner with a changeset"""
new = (
self.env["res.partner"]
.with_context(test_record_changeset=True)
.create(
{
"name": "partner",
"street": "street",
"street2": "street2",
}
)
)
new._compute_changeset_ids()
new._compute_count_pending_changesets()
self.assertEqual(new.count_pending_changesets, 1)
self.assert_changeset(
new,
self.env.user,
[
(self.field_name, False, "partner", "done"),
(self.field_street, False, "street", "draft"),
(self.field_street2, False, "street2", "cancel"),
],
)
self.assertEqual(new.name, "partner")
self.assertFalse(new.street)
self.assertFalse(new.street2)
def test_create_new_changeset_empty_value(self):
"""No change is created for empty values on create"""
new = (
self.env["res.partner"]
.with_context(test_record_changeset=True)
.create(
{
"name": "partner",
"street": "street",
"street2": False,
}
)
)
new._compute_changeset_ids()
new._compute_count_pending_changesets()
self.assertEqual(new.count_pending_changesets, 1)
self.assert_changeset(
new,
self.env.user,
[
(self.field_name, False, "partner", "done"),
(self.field_street, False, "street", "draft"),
],
)
self.assertEqual(new.name, "partner")
self.assertFalse(new.street)
self.assertFalse(new.street2)
def test_new_changeset_empty_value(self):
"""Create a changeset change that empty a value"""
self.partner.write({"street": False})
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assert_changeset(
self.partner,
self.demo_user,
[(self.field_street, "street X", False, "draft")],
)
def test_no_changeset_empty_value_both_sides(self):
"""No changeset created when both sides have an empty value"""
# we have to ensure that even if we write '' to a False field, we won't
# write a changeset
self.partner.with_context(__no_changeset=disable_changeset).write(
{"street": False}
)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.partner.write({"street": ""})
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertFalse(self.partner.changeset_ids)
def test_apply_change(self):
"""Apply a changeset change on a partner"""
changes = [(self.field_name, "Y", "draft")]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
for change in changeset.change_ids:
change.get_changeset_changes_by_field(changeset.model, changeset.res_id)
changeset.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.name, "Y")
self.assertEqual(changeset.change_ids.state, "done")
# All computed fields are assigned
changeset.change_ids.read()
def test_apply_change_with_prevent_self_validation(self):
"""Don't apply a changeset change and prevent self validation"""
self.partner.write({"street": "street Z"})
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(self.partner.count_pending_changeset_changes, 1)
self.partner.changeset_ids.change_ids.rule_id.prevent_self_validation = True
with self.assertRaises(
UserError, msg="You don't have the rights to reject the changes."
):
self.partner.changeset_ids.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(self.partner.count_pending_changeset_changes, 1)
self.assertEqual(self.partner.street, "street X")
self.assertEqual(self.partner.changeset_ids.change_ids.state, "draft")
# Copy the user to have another user with similar rights, so that
# self validation prevention doesn't kick in.
other_demo_user = self.demo_user.copy()
other_demo_user.groups_id += self.env.ref("base_changeset.group_changeset_user")
self.partner.changeset_ids.change_ids.with_user(other_demo_user).apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.count_pending_changeset_changes, 0)
self.assertEqual(self.partner.street, "street Z")
self.assertEqual(self.partner.changeset_ids.change_ids.state, "done")
def test_apply_done_change(self):
"""Done changes do not apply (already applied)"""
changes = [(self.field_name, "Y", "done")]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
with self.assertRaises(UserError):
changeset.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.name, "X")
changeset.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.name, "X")
def test_apply_cancel_change(self):
"""Cancel changes do not apply"""
changes = [(self.field_name, "Y", "cancel")]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
with self.assertRaises(UserError):
changeset.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.name, "X")
changeset.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.name, "X")
def test_apply_empty_value(self):
"""Apply a change that empty a value"""
changes = [(self.field_street, False, "draft")]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
for change in changeset.change_ids:
change.get_changeset_changes_by_field(changeset.model, changeset.res_id)
changeset.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertFalse(self.partner.street)
def test_apply_change_loop(self):
"""Test multiple changes"""
changes = [
(self.field_name, "Y", "draft"),
(self.field_street, "street Y", "draft"),
(self.field_street2, "street2 Y", "draft"),
]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
for change in changeset.change_ids:
change.get_changeset_changes_by_field(changeset.model, changeset.res_id)
changeset.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.name, "Y")
self.assertEqual(self.partner.street, "street Y")
self.assertEqual(self.partner.street2, "street2 Y")
def test_apply(self):
"""Apply a full changeset on a partner"""
changes = [
(self.field_name, "Y", "draft"),
(self.field_street, "street Y", "draft"),
(self.field_street2, "street2 Y", "draft"),
]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(self.partner.count_pending_changeset_changes, 3)
for change in changeset.change_ids:
change.get_changeset_changes_by_field(changeset.model, changeset.res_id)
changeset.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.count_pending_changeset_changes, 0)
self.assertEqual(self.partner.name, "Y")
self.assertEqual(self.partner.street, "street Y")
self.assertEqual(self.partner.street2, "street2 Y")
def test_changeset_state_on_done(self):
"""Check that changeset state becomes done when changes are done"""
changes = [(self.field_name, "Y", "draft")]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(changeset.state, "draft")
changeset.change_ids.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(changeset.state, "done")
def test_changeset_state_on_cancel(self):
"""Check that rev. state becomes done when changes are canceled"""
changes = [(self.field_name, "Y", "draft")]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(changeset.state, "draft")
changeset.change_ids.cancel()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(changeset.state, "done")
def test_changeset_state(self):
"""Check that changeset state becomes done with multiple changes"""
changes = [
(self.field_name, "Y", "draft"),
(self.field_street, "street Y", "draft"),
(self.field_street2, "street2 Y", "draft"),
]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(self.partner.count_pending_changeset_changes, 3)
self.assertEqual(changeset.state, "draft")
changeset.apply()
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 0)
self.assertEqual(self.partner.count_pending_changeset_changes, 0)
self.assertEqual(changeset.state, "done")
def test_apply_changeset_with_other_pending(self):
"""Error when applying when previous pending changesets exist"""
changes = [(self.field_name, "Y", "draft")]
old_changeset = self._create_changeset(self.partner, changes)
# if the date is the same, both changeset can be applied
to_string = fields.Datetime.to_string
old_changeset.date = to_string(datetime.now() - timedelta(days=1))
changes = [(self.field_name, "Z", "draft")]
changeset = self._create_changeset(self.partner, changes)
with self.assertRaises(UserError):
changeset.change_ids.with_context(
require_previous_changesets_done=True
).apply()
changeset.change_ids.apply()
def test_apply_different_changesets(self):
"""Apply different changesets at once"""
partner2 = self.env["res.partner"].create({"name": "P2"})
changes = [
(self.field_name, "Y", "draft"),
(self.field_street, "street Y", "draft"),
(self.field_street2, "street2 Y", "draft"),
]
changeset = self._create_changeset(self.partner, changes)
self.partner._compute_changeset_ids()
self.partner._compute_count_pending_changesets()
self.assertEqual(self.partner.count_pending_changesets, 1)
self.assertEqual(self.partner.count_pending_changeset_changes, 3)
for change in changeset.change_ids:
change.get_changeset_changes_by_field(changeset.model, changeset.res_id)
changeset2 = self._create_changeset(partner2, changes)
partner2._compute_changeset_ids()
partner2._compute_count_pending_changesets()
self.assertEqual(changeset.state, "draft")
self.assertEqual(changeset2.state, "draft")
self.assertEqual(partner2.count_pending_changesets, 1)
self.assertEqual(partner2.count_pending_changeset_changes, 3)
for change in changeset2.change_ids:
change.get_changeset_changes_by_field(changeset2.model, changeset2.res_id)
(changeset + changeset2).apply()
self.assertEqual(self.partner.name, "Y")
self.assertEqual(self.partner.street, "street Y")
self.assertEqual(self.partner.street2, "street2 Y")
self.assertEqual(partner2.name, "Y")
self.assertEqual(partner2.street, "street Y")
self.assertEqual(partner2.street2, "street2 Y")
self.assertEqual(changeset.state, "done")
self.assertEqual(changeset2.state, "done")
def test_new_changeset_source(self):
"""Source is the user who made the change"""
self.partner.write({"street": False})
self.partner._compute_changeset_ids()
changeset = self.partner.changeset_ids
self.assertEqual(changeset.source, self.demo_user)
def test_new_changeset_source_other_model(self):
"""Define source from another model"""
company = self.env.ref("base.main_company")
keys = {
"force_changeset_for_partners": True,
"__changeset_rules_source_model": "res.company",
"__changeset_rules_source_id": company.id,
}
self.partner.with_context(**keys).write({"street": False})
self.partner._compute_changeset_ids()
changeset = self.partner.changeset_ids
self.assertEqual(changeset.source, company)
def test_name_get(self):
"""Test the name_get of a changeset for a model without name field"""
self.env["changeset.field.rule"].create(
{
"field_id": self.env.ref("base.field_res_partner_bank__active").id,
"action": "validate",
}
)
bank = self.env.ref("base.bank_partner_demo").with_context(
test_record_changeset=True
)
bank.active = False
self.assertTrue(bank.changeset_ids)
self.assertIn(bank.acc_number, bank.changeset_ids.name_get()[0][1])
def test_new_changeset_expression(self):
"""Test that rules can be conditional"""
self.env["changeset.field.rule"].search(
[
("field_id", "=", self.field_street.id),
]
).expression = "object.street != 'street X'"
self.partner.street = "street Y"
self.partner.invalidate_recordset()
self.assertEqual(self.partner.street, "street Y")
self.assertFalse(self.partner.changeset_ids)
self.partner.street = "street Z"
self.partner.invalidate_recordset()
self.assertTrue(self.partner.changeset_ids)
self.assertEqual(self.partner.street, "street Y")

View file

@ -0,0 +1,139 @@
# Copyright 2015-2017 Camptocamp SA
# Copyright 2020 Onestein (<https://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import Form, TransactionCase
from ..models.base import disable_changeset
from .common import ChangesetTestCommon
class TestChangesetOrigin(ChangesetTestCommon, TransactionCase):
"""Check that origin - old fields are stored as expected.
'origin' fields dynamically read fields from the partner when the state
of the change is 'draft'. Once a change becomes 'done' or 'cancel', the
'old' field copies the value from the partner and then the 'origin' field
displays the 'old' value.
"""
@classmethod
def _setup_rules(cls):
ChangesetFieldRule = cls.env["changeset.field.rule"]
ChangesetFieldRule.search([]).unlink()
cls.field_name = cls.env.ref("base.field_res_partner__name")
ChangesetFieldRule.create({"field_id": cls.field_name.id, "action": "validate"})
@classmethod
def setUpClass(cls):
super().setUpClass()
cls._setup_rules()
cls.partner = cls.env["res.partner"].create({"name": "X"})
# Add context for this test for compatibility with other modules' tests
cls.partner = cls.partner.with_context(test_record_changeset=True)
def test_origin_value_of_change_with_apply(self):
"""Origin field is read from the parter or 'old' - with apply
According to the state of the change.
"""
with Form(self.partner) as partner_form:
partner_form.name = "Y"
self.assertEqual(self.partner.count_pending_changesets, 1)
changeset = self.partner.changeset_ids
change = changeset.change_ids
self.assertEqual(self.partner.name, "X")
self.assertEqual(change.origin_value_char, "X")
self.assertEqual(change.origin_value_display, "X")
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "A"
self.assertEqual(change.origin_value_char, "A")
self.assertEqual(change.origin_value_display, "A")
change.apply()
self.assertEqual(change.origin_value_char, "A")
self.assertEqual(change.origin_value_display, "A")
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "B"
self.assertEqual(change.origin_value_char, "A")
self.assertEqual(change.origin_value_display, "A")
self.assertEqual(self.partner.count_pending_changesets, 0)
def test_origin_value_of_change_with_cancel(self):
"""Origin field is read from the parter or 'old' - with cancel
According to the state of the change.
"""
with Form(self.partner) as partner_form:
partner_form.name = "Y"
self.assertEqual(self.partner.count_pending_changesets, 1)
changeset = self.partner.changeset_ids
change = changeset.change_ids
self.assertEqual(self.partner.name, "X")
self.assertEqual(change.origin_value_char, "X")
self.assertEqual(change.origin_value_display, "X")
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "A"
self.assertEqual(change.origin_value_char, "A")
self.assertEqual(change.origin_value_display, "A")
change.cancel()
self.assertEqual(change.origin_value_char, "A")
self.assertEqual(change.origin_value_display, "A")
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "B"
self.assertEqual(change.origin_value_char, "A")
self.assertEqual(change.origin_value_display, "A")
self.assertEqual(self.partner.count_pending_changesets, 0)
def test_old_field_of_change_with_apply(self):
"""Old field is stored when the change is applied"""
with Form(self.partner) as partner_form:
partner_form.name = "Y"
self.assertEqual(self.partner.count_pending_changesets, 1)
changeset = self.partner.changeset_ids
change = changeset.change_ids
self.assertEqual(self.partner.name, "X")
self.assertFalse(change.old_value_char)
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "A"
self.assertFalse(change.old_value_char)
change.apply()
self.assertEqual(change.old_value_char, "A")
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "B"
self.assertEqual(change.old_value_char, "A")
self.assertEqual(self.partner.count_pending_changesets, 0)
def test_old_field_of_change_with_cancel(self):
"""Old field is stored when the change is canceled"""
with Form(self.partner) as partner_form:
partner_form.name = "Y"
self.assertEqual(self.partner.count_pending_changesets, 1)
changeset = self.partner.changeset_ids
change = changeset.change_ids
self.assertEqual(self.partner.name, "X")
self.assertFalse(change.old_value_char)
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "A"
self.assertFalse(change.old_value_char)
change.cancel()
self.assertEqual(change.old_value_char, "A")
with Form(
self.partner.with_context(__no_changeset=disable_changeset)
) as partner_form:
partner_form.name = "B"
self.assertEqual(change.old_value_char, "A")
self.assertEqual(self.partner.count_pending_changesets, 0)

View file

@ -0,0 +1,38 @@
# Copyright 2021 Hunki Enterprises BV (<https://hunki-enterprises.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.tests.common import TransactionCase
from .common import ChangesetTestCommon
class TestChangesetFlow(ChangesetTestCommon, TransactionCase):
"""Check that changesets don't leak information"""
@classmethod
def setUp(cls):
super().setUpClass()
cls.env["changeset.field.rule"].search([]).unlink()
cls.rule = cls.env["changeset.field.rule"].create(
{
"model_id": cls.env.ref("base.model_ir_config_parameter").id,
"field_id": cls.env.ref("base.field_ir_config_parameter__key").id,
"action": "auto",
}
)
def test_change_unprivileged_user(self):
"""
Check that unprivileged users can't see changesets they didn't create
"""
user = self.env.ref("base.user_demo")
self.env["ir.config_parameter"].with_context(
test_record_changeset=True,
).set_param("hello", "world")
changeset = self.env["record.changeset.change"].search(
[
("rule_id", "=", self.rule.id),
]
)
self.assertTrue(changeset)
self.assertFalse(changeset.with_user(user).search([("id", "=", changeset.id)]))