Skip to content

Commit b774f8e

Browse files
committed
fixup! [UPD] web_m2x_options_manager: overhaul
Lesser test refactoring
1 parent 22d4267 commit b774f8e

File tree

6 files changed

+182
-149
lines changed

6 files changed

+182
-149
lines changed

web_m2x_options_manager/models/m2x_create_edit_option.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright 2021 Camptocamp SA
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

4-
54
from odoo import _, api, fields, models
65
from odoo.exceptions import ValidationError
76
from odoo.tools.cache import ormcache
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
from . import test_ir_model
2+
from . import test_ir_model_fields
13
from . import test_m2x_create_edit_option
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2025 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from lxml import etree
5+
6+
from odoo.tests.common import SavepointCase
7+
from odoo.tools.safe_eval import safe_eval
8+
9+
10+
class Common(SavepointCase):
11+
@classmethod
12+
def _create_opt(cls, model_name, field_name, vals=None):
13+
field = cls._get_field(model_name, field_name)
14+
vals = dict(vals or [])
15+
return cls.env["m2x.create.edit.option"].create(dict(field_id=field.id, **vals))
16+
17+
@classmethod
18+
def _get_field(cls, model_name, field_name):
19+
return cls.env["ir.model.fields"]._get(model_name, field_name)
20+
21+
@classmethod
22+
def _get_model(cls, model_name):
23+
return cls.env["ir.model"]._get(model_name)
24+
25+
@staticmethod
26+
def _eval_node_options(node):
27+
opt = node.attrib.get("options")
28+
if opt:
29+
return safe_eval(opt, nocopy=True)
30+
return {}
31+
32+
@classmethod
33+
def _get_test_view(cls):
34+
return cls.env.ref("web_m2x_options_manager.res_partner_demo_form_view")
35+
36+
@classmethod
37+
def _get_test_view_fields_view_get(cls):
38+
return cls.env["res.partner"].fields_view_get(cls._get_test_view().id)
39+
40+
@classmethod
41+
def _get_test_view_parsed(cls):
42+
return etree.XML(cls._get_test_view_fields_view_get()["arch"])
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2025 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo.tools import mute_logger
5+
6+
from .common import Common
7+
8+
9+
class TestIrModel(Common):
10+
@mute_logger("odoo.models.unlink")
11+
def test_model_buttons(self):
12+
model = self._get_model("res.users")
13+
(model.m2x_option_ids + model.m2x_comodels_option_ids).unlink()
14+
15+
# Model's fields workflow
16+
# 1- fill: check options have been created
17+
model.button_fill_m2x_options()
18+
options = model.m2x_option_ids
19+
self.assertTrue(options)
20+
# 2- refill: check no option has been created (they all existed already)
21+
model.button_fill_m2x_options()
22+
self.assertFalse(model.m2x_option_ids - options)
23+
# 3- empty: check no option exists anymore
24+
model.button_empty_m2x_options()
25+
self.assertFalse(model.m2x_option_ids)
26+
27+
# Model's inverse fields workflow
28+
# 1- fill: check options have been created
29+
model.button_fill_m2x_comodels_options()
30+
comodels_options = model.m2x_comodels_option_ids
31+
self.assertTrue(comodels_options)
32+
# 2- refill: check no option has been created (they all existed already)
33+
model.button_fill_m2x_comodels_options()
34+
self.assertFalse(model.m2x_comodels_option_ids - comodels_options)
35+
# 3- empty: check no option exists anymore
36+
model.button_empty_m2x_comodels_options()
37+
self.assertFalse(model.m2x_comodels_option_ids)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright 2025 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo_test_helper import FakeModelLoader
5+
6+
from odoo import fields, models
7+
8+
from .common import Common
9+
10+
11+
class TestIrModelFields(Common):
12+
def test_field_can_have_options(self):
13+
# M2O field
14+
self.assertTrue(self._get_field("res.partner", "parent_id").can_have_options)
15+
# M2M field
16+
self.assertTrue(self._get_field("res.partner", "category_id").can_have_options)
17+
# O2M field
18+
self.assertFalse(self._get_field("res.partner", "user_ids").can_have_options)
19+
# non-relational field
20+
self.assertFalse(self._get_field("res.partner", "id").can_have_options)
21+
22+
def test_field_comodel_id(self):
23+
# M2O field
24+
self.assertEqual(
25+
self._get_field("res.partner", "parent_id").comodel_id,
26+
self._get_model("res.partner"),
27+
)
28+
# M2M field
29+
self.assertEqual(
30+
self._get_field("res.partner", "category_id").comodel_id,
31+
self._get_model("res.partner.category"),
32+
)
33+
# O2M field
34+
self.assertEqual(
35+
self._get_field("res.partner", "user_ids").comodel_id,
36+
self._get_model("res.users"),
37+
)
38+
# Non-relational field
39+
self.assertFalse(self._get_field("res.partner", "id").comodel_id)
40+
41+
def test_field_search(self):
42+
loader = FakeModelLoader(self.env, self.__module__)
43+
loader.backup_registry()
44+
45+
class ResUsers(models.Model):
46+
_inherit = "res.users"
47+
48+
test_field = fields.Many2one("res.groups", string="Abc")
49+
50+
class ResGroups(models.Model):
51+
_inherit = "res.groups"
52+
53+
test_field_abc = fields.Many2one("res.users", string="Abc")
54+
55+
loader.update_registry((ResUsers, ResGroups))
56+
57+
test_field = self._get_field("res.users", "test_field")
58+
test_field_abc = self._get_field("res.groups", "test_field_abc")
59+
60+
ir_model_fields = self.env["ir.model.fields"]
61+
name = "ABC"
62+
domain = [("model", "in", ("res.users", "res.groups"))]
63+
64+
# String "abc" is contained in both fields' description: basic ``name_search()``
65+
# will return them both sorted by ID
66+
ir_model_fields = ir_model_fields.with_context(search_by_technical_name=False)
67+
self.assertEqual(
68+
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
69+
[test_field.id, test_field_abc.id],
70+
)
71+
72+
# Use context key ``search_by_technical_name``: ``test_field_abc`` should now be
73+
# returned first
74+
ir_model_fields = ir_model_fields.with_context(search_by_technical_name=True)
75+
self.assertEqual(
76+
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
77+
[test_field_abc.id, test_field.id],
78+
)
79+
80+
ir_model_fields.with_context(search_by_technical_name=False)
81+
82+
# Search again by mimicking the ``ir.model`` Form view for m2x options
83+
users_model = self._get_model("res.users")
84+
ir_model_fields = ir_model_fields.with_context(
85+
o2m_list_view_m2x_domain=[("model_id", "=", users_model.id)]
86+
)
87+
self.assertEqual(
88+
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
89+
[test_field.id],
90+
)
91+
ir_model_fields = ir_model_fields.with_context(
92+
o2m_list_view_m2x_domain=[("comodel_id", "=", users_model.id)]
93+
)
94+
self.assertEqual(
95+
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
96+
[test_field_abc.id],
97+
)
98+
99+
loader.restore_registry()

web_m2x_options_manager/tests/test_m2x_create_edit_option.py

Lines changed: 2 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,13 @@
11
# Copyright 2021 Camptocamp SA
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

4-
from lxml import etree
5-
from odoo_test_helper import FakeModelLoader
64

7-
from odoo import fields, models
85
from odoo.exceptions import ValidationError
9-
from odoo.tests.common import SavepointCase
10-
from odoo.tools.safe_eval import safe_eval
116

7+
from .common import Common
128

13-
class TestM2xCreateEditOption(SavepointCase):
14-
@classmethod
15-
def _create_opt(cls, model_name, field_name, vals=None):
16-
field = cls._get_field(model_name, field_name)
17-
vals = dict(vals or [])
18-
return cls.env["m2x.create.edit.option"].create(dict(field_id=field.id, **vals))
19-
20-
@classmethod
21-
def _get_field(cls, model_name, field_name):
22-
return cls.env["ir.model.fields"]._get(model_name, field_name)
23-
24-
@classmethod
25-
def _get_model(cls, model_name):
26-
return cls.env["ir.model"]._get(model_name)
27-
28-
@staticmethod
29-
def _eval_node_options(node):
30-
opt = node.attrib.get("options")
31-
if opt:
32-
return safe_eval(opt, nocopy=True)
33-
return {}
34-
35-
@classmethod
36-
def _get_test_view(cls):
37-
return cls.env.ref("web_m2x_options_manager.res_partner_demo_form_view")
38-
39-
@classmethod
40-
def _get_test_view_fields_view_get(cls):
41-
return cls.env["res.partner"].fields_view_get(cls._get_test_view().id)
42-
43-
@classmethod
44-
def _get_test_view_parsed(cls):
45-
return etree.XML(cls._get_test_view_fields_view_get()["arch"])
469

10+
class TestM2xCreateEditOption(Common):
4711
def test_errors(self):
4812
with self.assertRaises(ValidationError):
4913
# Fails ``_check_field_type``: users_field is a One2many
@@ -121,116 +85,6 @@ def test_apply_options(self):
12185
{"create": True, "create_edit": True, "m2o_dialog": True},
12286
)
12387

124-
def test_model_buttons(self):
125-
res_partner_model = self._get_model("res.partner")
126-
127-
# Model's fields workflow
128-
res_partner_model.button_empty_m2x_options()
129-
self.assertFalse(res_partner_model.m2x_option_ids)
130-
res_partner_model.button_fill_m2x_options()
131-
options = res_partner_model.m2x_option_ids
132-
self.assertTrue(options)
133-
res_partner_model.button_fill_m2x_options()
134-
self.assertFalse(res_partner_model.m2x_option_ids - options)
135-
136-
# Model's FK fields workflow
137-
res_partner_model.button_empty_m2x_comodels_options()
138-
self.assertFalse(res_partner_model.m2x_comodels_option_ids)
139-
res_partner_model.button_fill_m2x_comodels_options()
140-
comodels_options = res_partner_model.m2x_comodels_option_ids
141-
self.assertTrue(comodels_options)
142-
res_partner_model.button_fill_m2x_comodels_options()
143-
self.assertFalse(res_partner_model.m2x_comodels_option_ids - comodels_options)
144-
145-
def test_field_can_have_options(self):
146-
# M2O field
147-
self.assertTrue(self._get_field("res.partner", "parent_id").can_have_options)
148-
# M2M field
149-
self.assertTrue(self._get_field("res.partner", "category_id").can_have_options)
150-
# O2M field
151-
self.assertFalse(self._get_field("res.partner", "user_ids").can_have_options)
152-
# non-relational field
153-
self.assertFalse(self._get_field("res.partner", "id").can_have_options)
154-
155-
def test_field_comodel_id(self):
156-
# M2O field
157-
self.assertEqual(
158-
self._get_field("res.partner", "parent_id").comodel_id,
159-
self._get_model("res.partner"),
160-
)
161-
# M2M field
162-
self.assertEqual(
163-
self._get_field("res.partner", "category_id").comodel_id,
164-
self._get_model("res.partner.category"),
165-
)
166-
# O2M field
167-
self.assertEqual(
168-
self._get_field("res.partner", "user_ids").comodel_id,
169-
self._get_model("res.users"),
170-
)
171-
# Non-relational field
172-
self.assertFalse(self._get_field("res.partner", "id").comodel_id)
173-
174-
def test_field_search(self):
175-
loader = FakeModelLoader(self.env, self.__module__)
176-
loader.backup_registry()
177-
178-
class ResUsers(models.Model):
179-
_inherit = "res.users"
180-
181-
test_field = fields.Many2one("res.groups", string="Abc")
182-
183-
class ResGroups(models.Model):
184-
_inherit = "res.groups"
185-
186-
test_field_abc = fields.Many2one("res.users", string="Abc")
187-
188-
loader.update_registry((ResUsers, ResGroups))
189-
190-
test_field = self._get_field("res.users", "test_field")
191-
test_field_abc = self._get_field("res.groups", "test_field_abc")
192-
193-
ir_model_fields = self.env["ir.model.fields"]
194-
name = "ABC"
195-
domain = [("model", "in", ("res.users", "res.groups"))]
196-
197-
# String "abc" is contained in both fields' description: basic ``name_search()``
198-
# will return them both sorted by ID
199-
ir_model_fields = ir_model_fields.with_context(search_by_technical_name=False)
200-
self.assertEqual(
201-
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
202-
[test_field.id, test_field_abc.id],
203-
)
204-
205-
# Use context key ``search_by_technical_name``: ``test_field_abc`` should now be
206-
# returned first
207-
ir_model_fields = ir_model_fields.with_context(search_by_technical_name=True)
208-
self.assertEqual(
209-
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
210-
[test_field_abc.id, test_field.id],
211-
)
212-
213-
ir_model_fields.with_context(search_by_technical_name=False)
214-
215-
# Search again by mimicking the ``ir.model`` Form view for m2x options
216-
users_model = self._get_model("res.users")
217-
ir_model_fields = ir_model_fields.with_context(
218-
o2m_list_view_m2x_domain=[("model_id", "=", users_model.id)]
219-
)
220-
self.assertEqual(
221-
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
222-
[test_field.id],
223-
)
224-
ir_model_fields = ir_model_fields.with_context(
225-
o2m_list_view_m2x_domain=[("comodel_id", "=", users_model.id)]
226-
)
227-
self.assertEqual(
228-
[r[0] for r in ir_model_fields.name_search(name, domain, limit=None)],
229-
[test_field_abc.id],
230-
)
231-
232-
loader.restore_registry()
233-
23488
def test_m2x_option_name(self):
23589
# Mostly to make Codecov happy...
23690
opt = self._create_opt(

0 commit comments

Comments
 (0)