odoo-addon-web-m2x-options-manager 16.0.1.0.0.3__py3-none-any.whl → 17.0.1.0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. odoo/addons/web_m2x_options_manager/README.rst +29 -18
  2. odoo/addons/web_m2x_options_manager/__init__.py +1 -3
  3. odoo/addons/web_m2x_options_manager/__manifest__.py +9 -2
  4. odoo/addons/web_m2x_options_manager/demo/res_partner_demo_view.xml +11 -10
  5. odoo/addons/web_m2x_options_manager/hooks.py +11 -0
  6. odoo/addons/web_m2x_options_manager/i18n/it.po +1 -16
  7. odoo/addons/web_m2x_options_manager/i18n/web_m2x_options_manager.pot +81 -33
  8. odoo/addons/web_m2x_options_manager/models/__init__.py +1 -3
  9. odoo/addons/web_m2x_options_manager/models/ir_model.py +46 -21
  10. odoo/addons/web_m2x_options_manager/models/ir_model_fields.py +53 -0
  11. odoo/addons/web_m2x_options_manager/models/ir_ui_view.py +9 -7
  12. odoo/addons/web_m2x_options_manager/models/m2x_create_edit_option.py +133 -76
  13. odoo/addons/web_m2x_options_manager/readme/CONTRIBUTORS.md +2 -0
  14. odoo/addons/web_m2x_options_manager/readme/USAGE.md +12 -0
  15. odoo/addons/web_m2x_options_manager/static/description/index.html +49 -32
  16. odoo/addons/web_m2x_options_manager/tests/__init__.py +2 -3
  17. odoo/addons/web_m2x_options_manager/tests/common.py +42 -0
  18. odoo/addons/web_m2x_options_manager/tests/test_ir_model.py +37 -0
  19. odoo/addons/web_m2x_options_manager/tests/test_ir_model_fields.py +99 -0
  20. odoo/addons/web_m2x_options_manager/tests/test_m2x_create_edit_option.py +67 -92
  21. odoo/addons/web_m2x_options_manager/tools.py +34 -0
  22. odoo/addons/web_m2x_options_manager/views/ir_model.xml +38 -18
  23. odoo/addons/web_m2x_options_manager/views/m2x_create_edit_option.xml +157 -0
  24. {odoo_addon_web_m2x_options_manager-16.0.1.0.0.3.dist-info → odoo_addon_web_m2x_options_manager-17.0.1.0.0.2.dist-info}/METADATA +37 -24
  25. odoo_addon_web_m2x_options_manager-17.0.1.0.0.2.dist-info/RECORD +30 -0
  26. {odoo_addon_web_m2x_options_manager-16.0.1.0.0.3.dist-info → odoo_addon_web_m2x_options_manager-17.0.1.0.0.2.dist-info}/WHEEL +1 -1
  27. odoo_addon_web_m2x_options_manager-17.0.1.0.0.2.dist-info/top_level.txt +1 -0
  28. odoo/addons/web_m2x_options_manager/readme/CONTRIBUTORS.rst +0 -4
  29. odoo/addons/web_m2x_options_manager/readme/USAGE.rst +0 -7
  30. odoo_addon_web_m2x_options_manager-16.0.1.0.0.3.dist-info/RECORD +0 -23
  31. odoo_addon_web_m2x_options_manager-16.0.1.0.0.3.dist-info/top_level.txt +0 -1
  32. /odoo/addons/web_m2x_options_manager/readme/{DESCRIPTION.rst → DESCRIPTION.md} +0 -0
@@ -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()
@@ -1,120 +1,95 @@
1
1
  # Copyright 2021 Camptocamp SA
2
2
  # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
3
 
4
- from lxml import etree
5
-
6
4
  from odoo.exceptions import ValidationError
7
- from odoo.tests.common import TransactionCase
8
- from odoo.tools.safe_eval import safe_eval
9
5
 
6
+ from .common import Common
7
+
8
+
9
+ class TestM2xCreateEditOption(Common):
10
+ def test_errors(self):
11
+ with self.assertRaises(ValidationError):
12
+ # Fails ``_check_field_type``: users_field is a One2many
13
+ self._create_opt("res.partner", "user_ids")
14
+
15
+ def test_apply_options(self):
16
+ # Check fields on res.partner form view before applying options
17
+ form_doc = self._get_test_view_parsed()
18
+ self.assertEqual(
19
+ self._eval_node_options(form_doc.xpath("//field[@name='title']")[0]), {}
20
+ )
21
+ self.assertEqual(
22
+ self._eval_node_options(form_doc.xpath("//field[@name='parent_id']")[0]),
23
+ {"create": False, "create_edit": False},
24
+ )
25
+ self.assertEqual(
26
+ self._eval_node_options(form_doc.xpath("//field[@name='category_id']")[0]),
27
+ {"create": False, "create_edit": False},
28
+ )
10
29
 
11
- class TestM2xCreateEditOption(TransactionCase):
12
- def setUp(self):
13
- super(TestM2xCreateEditOption, self).setUp()
14
- ref = self.env.ref
15
- # View to be used
16
- self.view = ref("web_m2x_options_manager.res_partner_demo_form_view")
17
- # res.partner model and fields
18
- self.res_partner_model = ref("base.model_res_partner")
19
- self.categ_field = ref("base.field_res_partner__category_id")
20
- self.title_field = ref("base.field_res_partner__title")
21
- self.users_field = ref("base.field_res_partner__user_ids")
22
- # res.users model and fields
23
- self.res_users_model = ref("base.model_res_users")
24
- self.company_field = ref("base.field_res_users__company_id")
25
- # Options setup
26
- self.title_opt = self.env["m2x.create.edit.option"].create(
30
+ # Create options, check view has been updated
31
+ self._create_opt(
32
+ "res.partner",
33
+ "title",
27
34
  {
28
- "field_id": self.title_field.id,
29
- "model_id": self.res_partner_model.id,
30
35
  "option_create": "set_true",
31
36
  "option_create_edit": "set_true",
32
- "option_create_edit_wizard": True,
33
- }
37
+ },
34
38
  )
35
- self.categories_opt = self.env["m2x.create.edit.option"].create(
39
+ self._create_opt(
40
+ "res.partner",
41
+ "parent_id",
36
42
  {
37
- "field_id": self.categ_field.id,
38
- "model_id": self.res_partner_model.id,
39
43
  "option_create": "set_true",
40
44
  "option_create_edit": "set_true",
41
- "option_create_edit_wizard": True,
42
- }
45
+ },
43
46
  )
44
- self.company_opt = self.env["m2x.create.edit.option"].create(
47
+ self._create_opt(
48
+ "res.partner",
49
+ "category_id",
45
50
  {
46
- "field_id": self.company_field.id,
47
- "model_id": self.res_users_model.id,
48
51
  "option_create": "force_true",
49
- "option_create_edit": "set_true",
50
- "option_create_edit_wizard": False,
51
- }
52
+ "option_create_edit": "force_true",
53
+ },
52
54
  )
53
-
54
- def test_errors(self):
55
- with self.assertRaises(ValidationError):
56
- # Fails ``_check_field_in_model``: model is res.partner, field is
57
- # res.users's company_id
58
- self.env["m2x.create.edit.option"].create(
59
- {
60
- "field_id": self.company_field.id,
61
- "model_id": self.res_partner_model.id,
62
- "option_create": "set_true",
63
- "option_create_edit": "set_true",
64
- }
65
- )
66
- with self.assertRaises(ValidationError):
67
- # Fails ``_check_field_type``: users_field is a One2many
68
- self.env["m2x.create.edit.option"].create(
69
- {
70
- "field_id": self.users_field.id,
71
- "model_id": self.res_partner_model.id,
72
- "option_create": "set_true",
73
- "option_create_edit": "set_true",
74
- }
75
- )
76
-
77
- def test_apply_options(self):
78
- res = self.env["res.partner"].get_view(self.view.id)
79
-
80
- # Check fields on res.partner form view
81
- form_arch = res["arch"]
82
- form_doc = etree.XML(form_arch)
83
- title_node = form_doc.xpath("//field[@name='title']")[0]
55
+ form_doc = self._get_test_view_parsed()
84
56
  self.assertEqual(
85
- safe_eval(title_node.attrib.get("options"), nocopy=True),
57
+ self._eval_node_options(form_doc.xpath("//field[@name='title']")[0]),
86
58
  {"create": True, "create_edit": True},
87
59
  )
88
60
  self.assertEqual(
89
- (
90
- title_node.attrib.get("can_create"),
91
- title_node.attrib.get("can_write"),
92
- ),
93
- ("true", "true"),
61
+ self._eval_node_options(form_doc.xpath("//field[@name='parent_id']")[0]),
62
+ # These remain the same because the options are defined w/ 'set_true':
63
+ # but the node already contains them, so no override is applied
64
+ {"create": False, "create_edit": False},
94
65
  )
95
- categ_node = form_doc.xpath("//field[@name='category_id']")[0]
96
66
  self.assertEqual(
97
- safe_eval(categ_node.attrib.get("options"), nocopy=True),
98
- {"create": False, "create_edit": True},
67
+ self._eval_node_options(form_doc.xpath("//field[@name='category_id']")[0]),
68
+ # These change values because the options are defined w/ 'force_true':
69
+ # options' values are overridden even if the node already contains them
70
+ {"create": True, "create_edit": True},
99
71
  )
100
- # Check fields on res.users tree view (contained in ``user_ids`` field)
101
- company_node = form_doc.xpath("//field[@name='company_id']")[0]
72
+
73
+ # Update options on ``res.partner.parent_id``, check its node has been updated
74
+ opt = self.env["m2x.create.edit.option"].get("res.partner", "parent_id")
75
+ opt.option_create = "force_true"
76
+ opt.option_create_edit = "force_true"
77
+ form_doc = self._get_test_view_parsed()
102
78
  self.assertEqual(
103
- safe_eval(company_node.attrib.get("options"), nocopy=True),
79
+ self._eval_node_options(form_doc.xpath("//field[@name='parent_id']")[0]),
80
+ {"create": True, "create_edit": True},
81
+ )
82
+
83
+ def test_m2x_option_name(self):
84
+ # Mostly to make Codecov happy...
85
+ opt = self._create_opt(
86
+ "res.partner",
87
+ "title",
104
88
  {
105
- "create": False,
106
- "no_create": False,
107
- "create_edit": True,
108
- "no_quick_create": True,
89
+ "option_create": "set_true",
90
+ "option_create_edit": "set_true",
109
91
  },
110
92
  )
111
- # Update options, check that node has been updated too
112
- self.title_opt.option_create_edit = "force_false"
113
- res = self.env["res.partner"].get_view(self.view.id)
114
- form_arch = res["arch"]
115
- form_doc = etree.XML(form_arch)
116
- title_node = form_doc.xpath("//field[@name='title']")[0]
117
- self.assertEqual(
118
- safe_eval(title_node.attrib.get("options"), nocopy=True),
119
- {"create": True, "create_edit": False, "no_create_edit": True},
120
- )
93
+ self.assertEqual(opt.name, "res.partner.title")
94
+ opt = opt.new({"field_id": self._get_field("res.partner", "parent_id").id})
95
+ self.assertEqual(opt.name, "res.partner.parent_id")
@@ -0,0 +1,34 @@
1
+ # Copyright 2025 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo.tools.sql import column_exists, create_column
5
+
6
+
7
+ def prepare_column_can_have_options(cr):
8
+ """Creates column ``ir_model_fields.can_have_options`` and fills its values"""
9
+ if not column_exists(cr, "ir_model_fields", "can_have_options"):
10
+ create_column(cr, "ir_model_fields", "can_have_options", "boolean")
11
+ cr.execute(
12
+ """
13
+ UPDATE ir_model_fields
14
+ SET can_have_options =
15
+ CASE
16
+ WHEN ttype in ('many2many', 'many2one') THEN true
17
+ ELSE false
18
+ END
19
+ """
20
+ )
21
+
22
+
23
+ def prepare_column_comodel_id(cr):
24
+ """Creates column ``ir_model_fields.comodel_id`` and fills its values"""
25
+ if not column_exists(cr, "ir_model_fields", "comodel_id"):
26
+ create_column(cr, "ir_model_fields", "comodel_id", "int4")
27
+ cr.execute(
28
+ """
29
+ UPDATE ir_model_fields
30
+ SET comodel_id = m.id
31
+ FROM ir_model m
32
+ WHERE relation = m.model
33
+ """
34
+ )
@@ -8,28 +8,48 @@
8
8
  <field name="arch" type="xml">
9
9
  <xpath expr="//notebook/page[@name='fields']" position="after">
10
10
  <page string="Create/Edit Options" name="create_edit_options">
11
- <div>
12
- <button name="button_fill" type="object" string="Fill" />
13
- <button name="button_empty" type="object" string="Empty" />
11
+ <field name="id" invisible="1" />
12
+ <div class="alert alert-info" role="alert">
13
+ Here are displayed options for this model's fields<hr />
14
+ <div>
15
+ <button
16
+ name="button_fill_m2x_options"
17
+ type="object"
18
+ string="Fill"
19
+ />
20
+ <button
21
+ name="button_empty_m2x_options"
22
+ type="object"
23
+ string="Empty"
24
+ />
25
+ </div>
14
26
  </div>
15
27
  <field
16
- name="m2x_create_edit_option_ids"
28
+ name="m2x_option_ids"
29
+ context="{'tree_view_ref': 'web_m2x_options_manager.m2x_create_edit_option_tree_o2m_filter_by_model'}"
17
30
  nolabel="1"
18
- context="{'default_model_name': model}"
19
- >
20
- <tree editable="bottom">
21
- <field name="model_name" invisible="1" />
22
- <field
23
- name="field_id"
24
- context="{'display_technical_name': True}"
25
- domain="[('ttype', 'in', ('many2many', 'many2one')), ('model_id.model', '=', model_name)]"
26
- options="{'create': False, 'create_edit': False}"
31
+ />
32
+ <div class="alert alert-info" role="alert">
33
+ Here are displayed options for other models' fields related to this model<hr
34
+ />
35
+ <div>
36
+ <button
37
+ name="button_fill_m2x_comodels_options"
38
+ type="object"
39
+ string="Fill"
40
+ />
41
+ <button
42
+ name="button_empty_m2x_comodels_options"
43
+ type="object"
44
+ string="Empty"
27
45
  />
28
- <field name="option_create" />
29
- <field name="option_create_edit" />
30
- <field name="option_create_edit_wizard" />
31
- </tree>
32
- </field>
46
+ </div>
47
+ </div>
48
+ <field
49
+ name="m2x_comodels_option_ids"
50
+ context="{'tree_view_ref': 'web_m2x_options_manager.m2x_create_edit_option_tree_o2m_filter_by_comodel'}"
51
+ nolabel="1"
52
+ />
33
53
  </page>
34
54
  </xpath>
35
55
  </field>
@@ -0,0 +1,157 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <odoo>
3
+
4
+ <record id="m2x_create_edit_option_search" model="ir.ui.view">
5
+ <field name="name">m2x.create.edit.option search view</field>
6
+ <field name="model">m2x.create.edit.option</field>
7
+ <field name="arch" type="xml">
8
+ <search>
9
+ <field name="field_id" />
10
+ <field name="model_id" />
11
+ <field name="comodel_id" />
12
+ <group expand="0" string="Group By">
13
+ <filter
14
+ name="groupby_model_id"
15
+ string="Model"
16
+ domain="[]"
17
+ context="{'group_by': 'model_id'}"
18
+ />
19
+ <filter
20
+ name="groupby_comodel_id"
21
+ string="Comodel"
22
+ domain="[]"
23
+ context="{'group_by': 'comodel_id'}"
24
+ />
25
+ <filter
26
+ name="groupby_option_create"
27
+ string="Create Option"
28
+ domain="[]"
29
+ context="{'group_by': 'option_create'}"
30
+ />
31
+ <filter
32
+ name="groupby_option_create_edit"
33
+ string="Create &amp; Edit Option"
34
+ domain="[]"
35
+ context="{'group_by': 'option_create_edit'}"
36
+ />
37
+ </group>
38
+ </search>
39
+ </field>
40
+ </record>
41
+
42
+ <record id="m2x_create_edit_option_form" model="ir.ui.view">
43
+ <field name="name">m2x.create.edit.option form view</field>
44
+ <field name="model">m2x.create.edit.option</field>
45
+ <field name="arch" type="xml">
46
+ <form>
47
+ <sheet>
48
+ <group>
49
+ <group>
50
+ <field
51
+ name="field_id"
52
+ context="{'search_by_technical_name': True, 'display_technical_name': True}"
53
+ options="{'create': False, 'create_edit': False}"
54
+ />
55
+ <field name="model_id" />
56
+ <field name="comodel_id" />
57
+ </group>
58
+ <group>
59
+ <field name="option_create" />
60
+ <field name="option_create_edit" />
61
+ </group>
62
+ </group>
63
+ </sheet>
64
+ </form>
65
+ </field>
66
+ </record>
67
+
68
+ <record id="m2x_create_edit_option_tree_base" model="ir.ui.view">
69
+ <field name="name">m2x.create.edit.option tree view - base</field>
70
+ <field name="model">m2x.create.edit.option</field>
71
+ <field name="arch" type="xml">
72
+ <tree default_order="model_name asc, field_name asc">
73
+ <field name="model_id" optional="show" />
74
+ <field name="model_name" optional="hide" />
75
+ <field name="field_id" optional="show" />
76
+ <field name="field_name" optional="hide" />
77
+ <field name="comodel_id" optional="show" />
78
+ <field name="comodel_name" optional="hide" />
79
+ <field name="option_create" />
80
+ <field name="option_create_edit" />
81
+ </tree>
82
+ </field>
83
+ </record>
84
+
85
+ <record id="m2x_create_edit_option_tree_editable" model="ir.ui.view">
86
+ <field name="name">m2x.create.edit.option tree view - editable</field>
87
+ <field name="model">m2x.create.edit.option</field>
88
+ <field name="mode">primary</field>
89
+ <field
90
+ name="inherit_id"
91
+ ref="web_m2x_options_manager.m2x_create_edit_option_tree_base"
92
+ />
93
+ <field name="arch" type="xml">
94
+ <tree position="attributes">
95
+ <attribute name="editable">bottom</attribute>
96
+ </tree>
97
+ <field name="field_id" position="attributes">
98
+ <attribute
99
+ name="context"
100
+ >{'search_by_technical_name': True, 'display_technical_name': True}</attribute>
101
+ <attribute
102
+ name="options"
103
+ >{'create': False, 'create_edit': False}</attribute>
104
+ </field>
105
+ </field>
106
+ </record>
107
+
108
+ <record id="m2x_create_edit_option_tree_o2m_filter_by_model" model="ir.ui.view">
109
+ <field name="name">m2x.create.edit.option tree view - filter by model</field>
110
+ <field name="model">m2x.create.edit.option</field>
111
+ <field name="mode">primary</field>
112
+ <field
113
+ name="inherit_id"
114
+ ref="web_m2x_options_manager.m2x_create_edit_option_tree_editable"
115
+ />
116
+ <field name="arch" type="xml">
117
+ <field name="field_id" position="attributes">
118
+ <attribute name="context" operation="update">{
119
+ 'o2m_list_view_m2x_domain': [('model_id', '=', parent.id)],
120
+ }</attribute>
121
+ </field>
122
+ </field>
123
+ </record>
124
+
125
+ <record id="m2x_create_edit_option_tree_o2m_filter_by_comodel" model="ir.ui.view">
126
+ <field name="name">m2x.create.edit.option tree view - filter by comodel</field>
127
+ <field name="model">m2x.create.edit.option</field>
128
+ <field name="mode">primary</field>
129
+ <field
130
+ name="inherit_id"
131
+ ref="web_m2x_options_manager.m2x_create_edit_option_tree_editable"
132
+ />
133
+ <field name="arch" type="xml">
134
+ <field name="field_id" position="attributes">
135
+ <attribute name="context" operation="update">{
136
+ 'o2m_list_view_m2x_domain': [('comodel_id', '=', parent.id)],
137
+ }</attribute>
138
+ </field>
139
+ </field>
140
+ </record>
141
+
142
+ <record id="m2x_create_edit_option_action" model="ir.actions.act_window">
143
+ <field name="name">Fields' Create &amp; Edit Options</field>
144
+ <field name="res_model">m2x.create.edit.option</field>
145
+ <field name="view_mode">tree,form</field>
146
+ </record>
147
+
148
+ <record id="m2x_create_edit_option_menu" model="ir.ui.menu">
149
+ <field name="name">Fields' Create &amp; Edit Options</field>
150
+ <field name="parent_id" ref="base.next_id_9" />
151
+ <field
152
+ name="action"
153
+ ref="web_m2x_options_manager.m2x_create_edit_option_action"
154
+ />
155
+ </record>
156
+
157
+ </odoo>