Skip to content

Commit dac4717

Browse files
author
Nacho Ales Lopez
committed
[FIX] Fixed readonly behavior of fields.
1 parent 94c482e commit dac4717

File tree

4 files changed

+213
-1
lines changed

4 files changed

+213
-1
lines changed

base_tier_validation/models/tier_validation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,9 @@ def get_view(self, view_id=None, view_type="form", **options):
847847
if node.attrib.get("name") in excepted_fields:
848848
continue
849849
new_r_modifier = self._get_tier_validation_readonly_domain()
850-
old_r_modifier = node.attrib.get("readonly")
850+
old_r_modifier = node.attrib.get(
851+
"readonly", self._fields.get(node.attrib["name"]).readonly
852+
)
851853
if old_r_modifier:
852854
new_r_modifier = f"({old_r_modifier}) or ({new_r_modifier})"
853855
node.attrib["readonly"] = new_r_modifier

base_tier_validation/tests/common.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ def setUpClass(cls):
1919
TierDefinition,
2020
TierValidationTester,
2121
TierValidationTester2,
22+
TierValidationTester3,
2223
TierValidationTesterComputed,
2324
)
2425

2526
cls.loader.update_registry(
2627
(
2728
TierValidationTester,
2829
TierValidationTester2,
30+
TierValidationTester3,
2931
TierValidationTesterComputed,
3032
TierDefinition,
3133
)
3234
)
3335

3436
cls.test_model = cls.env[TierValidationTester._name]
3537
cls.test_model_2 = cls.env[TierValidationTester2._name]
38+
cls.test_model_3 = cls.env[TierValidationTester3._name]
3639
cls.test_model_computed = cls.env[TierValidationTesterComputed._name]
3740

3841
cls.tester_model = cls.env["ir.model"].search(
@@ -41,6 +44,9 @@ def setUpClass(cls):
4144
cls.tester_model_2 = cls.env["ir.model"].search(
4245
[("model", "=", "tier.validation.tester2")]
4346
)
47+
cls.tester_model_3 = cls.env["ir.model"].search(
48+
[("model", "=", "tier.validation.tester3")]
49+
)
4450
cls.tester_model_computed = cls.env["ir.model"].search(
4551
[("model", "=", "tier.validation.tester.computed")]
4652
)
@@ -80,6 +86,49 @@ def setUpClass(cls):
8086
}
8187
)
8288

89+
# Custom view for tester3
90+
# Access record:
91+
cls.env["ir.model.access"].create(
92+
{
93+
"name": f"access {model.name}",
94+
"model_id": model.id,
95+
"perm_read": 1,
96+
"perm_write": 1,
97+
"perm_create": 1,
98+
"perm_unlink": 1,
99+
}
100+
)
101+
cls.env["ir.ui.view"].create(
102+
{
103+
"model": cls.tester_model_3.model,
104+
"name": f"Demo view for {model}",
105+
"arch": """<form>
106+
<header>
107+
<button name="action_confirm" type="object" string="Confirm" />
108+
<field name="state" widget="statusbar" />
109+
</header>
110+
111+
<sheet>
112+
<div class="oe_button_box">
113+
<button class="oe_stat_button" name="action_confirm" type="object">
114+
<div class="o_field_widget o_stat_info">
115+
<span class="o_stat_value"><field name="readonly_store_field" /></span>
116+
<span class="o_stat_text">Readonly Field</span>
117+
</div>
118+
</button>
119+
</div>
120+
121+
<group>
122+
<group readonly="True">
123+
<field name="inverse_field" />
124+
<field name="readonly_non_store_field" readonly="True" />
125+
</group>
126+
</group>
127+
</sheet>
128+
</form>""",
129+
}
130+
)
131+
83132
# Create users:
84133
group_ids = cls.env.ref("base.group_system").ids
85134
cls.test_user_1 = cls.env["res.users"].create(
@@ -108,6 +157,7 @@ def setUpClass(cls):
108157

109158
cls.test_record = cls.test_model.create({"test_field": 1.0})
110159
cls.test_record_2 = cls.test_model_2.create({"test_field": 1.0})
160+
cls.test_record_3 = cls.test_model_3.create({})
111161
cls.test_record_computed = cls.test_model_computed.create({"test_field": 1.0})
112162

113163
cls.tier_def_obj.create(
@@ -161,6 +211,20 @@ def setUpClass(cls):
161211
}
162212
)
163213

214+
# Create a tier definition for the readonly check
215+
cls.tier_def_obj.create(
216+
{
217+
"model_id": cls.tester_model_3.id,
218+
"review_type": "individual",
219+
"reviewer_id": cls.test_user_1.id,
220+
"definition_domain": "[]",
221+
"approve_sequence": False,
222+
"notify_on_pending": False,
223+
"sequence": 10,
224+
"name": 'Definition for test "readonly"',
225+
}
226+
)
227+
164228
@classmethod
165229
def tearDownClass(cls):
166230
cls.loader.restore_registry()

base_tier_validation/tests/test_tier_validation.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from odoo.exceptions import ValidationError
1010
from odoo.tests import Form
1111
from odoo.tests.common import tagged
12+
from odoo.tools.safe_eval import safe_eval
1213

1314
from ..models.tier_validation import BASE_EXCEPTION_FIELDS as BEF
1415
from ..models.tier_validation import TierValidation as TV
@@ -1055,3 +1056,91 @@ def test_get_view(self):
10551056
self.assertIn("need_validation", view["models"][model])
10561057
self.assertIn("next_review", view["models"][model])
10571058
self.assertIn("review_ids", view["models"][model])
1059+
1060+
def test_readonly_field_view(self):
1061+
"""
1062+
Tests that "readonly" fields are kept as read-only after installing the module.
1063+
"""
1064+
view = self.test_record_3.get_view()
1065+
1066+
form = etree.fromstring(view["arch"])
1067+
1068+
# Checking that the readonly fields do contain a "readonly" evaluation.
1069+
self.assertTrue(
1070+
safe_eval(
1071+
form.xpath("//field[@name='readonly_non_store_field']")[0].attrib.get(
1072+
"readonly"
1073+
),
1074+
{**self.test_record_3.read()[0]},
1075+
)
1076+
)
1077+
self.assertTrue(
1078+
safe_eval(
1079+
form.xpath("//field[@name='readonly_store_field']")[0].attrib.get(
1080+
"readonly"
1081+
),
1082+
{**self.test_record_3.read()[0]},
1083+
)
1084+
)
1085+
self.assertFalse(
1086+
safe_eval(
1087+
form.xpath("//field[@name='inverse_field']")[0].attrib.get("readonly"),
1088+
{**self.test_record_3.read()[0]},
1089+
)
1090+
)
1091+
1092+
# Once the validation is triggered, this should cause all fields to be readonly
1093+
self.test_record_3.request_validation()
1094+
self.test_record_3.invalidate_recordset()
1095+
1096+
self.assertTrue(self.test_record_3.review_ids)
1097+
1098+
self.assertTrue(
1099+
safe_eval(
1100+
form.xpath("//field[@name='readonly_non_store_field']")[0].attrib.get(
1101+
"readonly"
1102+
),
1103+
{**self.test_record_3.read()[0]},
1104+
)
1105+
)
1106+
self.assertTrue(
1107+
safe_eval(
1108+
form.xpath("//field[@name='readonly_store_field']")[0].attrib.get(
1109+
"readonly"
1110+
),
1111+
{**self.test_record_3.read()[0]},
1112+
)
1113+
)
1114+
self.assertTrue(
1115+
safe_eval(
1116+
form.xpath("//field[@name='inverse_field']")[0].attrib.get("readonly"),
1117+
{**self.test_record_3.read()[0]},
1118+
)
1119+
)
1120+
1121+
# And, after validating, the field return to normal (maintaining their readonly-ness)
1122+
self.test_record_3.validate_tier()
1123+
self.test_record_3.invalidate_recordset()
1124+
1125+
self.assertTrue(
1126+
safe_eval(
1127+
form.xpath("//field[@name='readonly_non_store_field']")[0].attrib.get(
1128+
"readonly"
1129+
),
1130+
{**self.test_record_3.read()[0]},
1131+
)
1132+
)
1133+
self.assertTrue(
1134+
safe_eval(
1135+
form.xpath("//field[@name='readonly_store_field']")[0].attrib.get(
1136+
"readonly"
1137+
),
1138+
{**self.test_record_3.read()[0]},
1139+
)
1140+
)
1141+
self.assertTrue(
1142+
safe_eval(
1143+
form.xpath("//field[@name='inverse_field']")[0].attrib.get("readonly"),
1144+
{**self.test_record_3.read()[0]},
1145+
)
1146+
)

base_tier_validation/tests/tier_validation_tester.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,62 @@ def action_confirm(self):
4848
self.write({"state": "confirmed"})
4949

5050

51+
class TierValidationTester3(models.Model):
52+
_name = "tier.validation.tester3"
53+
_description = "Tier Validation Tester 3"
54+
_inherit = "tier.validation"
55+
_tier_validation_manual_config = False
56+
57+
state = fields.Selection(
58+
selection=[
59+
("draft", "Draft"),
60+
("confirmed", "Confirmed"),
61+
("cancel", "Cancel"),
62+
],
63+
default="draft",
64+
)
65+
66+
readonly_non_store_field = fields.Char(compute="_compute_readonly_non_store_field")
67+
readonly_store_field = fields.Char(
68+
compute="_compute_readonly_store_field", store=True
69+
)
70+
inverse_field = fields.Char(
71+
compute="_compute_inverse_field", inverse="_inverse_inverse_field", store=True
72+
)
73+
inverse_counter = fields.Integer(string="Counter", default=0)
74+
75+
def _compute_readonly_non_store_field(self):
76+
"""
77+
Assigns a default value to the non-store field.
78+
"""
79+
for tester in self:
80+
tester.readonly_non_store_field = "Default Value"
81+
82+
def _compute_readonly_field(self):
83+
"""
84+
Assigns a default value, that the user won't be able to change in the views.
85+
"""
86+
for tester in self:
87+
tester.readonly_store_field = "Default Value"
88+
89+
def _compute_inverse_field(self):
90+
"""
91+
Returns the readonly field value if none is present in the inverse field.
92+
"""
93+
for tester in self:
94+
tester.inverse_field = tester.readonly_field
95+
96+
def _inverse_inverse_field(self):
97+
"""
98+
Any modification made to the field will increase by one the counter field
99+
"""
100+
for tester in self:
101+
tester.inverse_counter += 1
102+
103+
def action_confirm(self):
104+
self.write({"state": "confirmed"})
105+
106+
51107
class TierValidationTesterComputed(models.Model):
52108
_name = "tier.validation.tester.computed"
53109
_description = "Tier Validation Tester Computed"
@@ -109,5 +165,6 @@ def _get_tier_validation_model_names(self):
109165
res = super()._get_tier_validation_model_names()
110166
res.append("tier.validation.tester")
111167
res.append("tier.validation.tester2")
168+
res.append("tier.validation.tester3")
112169
res.append("tier.validation.tester.computed")
113170
return res

0 commit comments

Comments
 (0)