odoo-addon-web-m2x-options-manager 16.0.1.0.0.1__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 +201 -0
  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.1.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.1.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.1.dist-info/RECORD +0 -22
  31. odoo_addon_web_m2x_options_manager-16.0.1.0.0.1.dist-info/top_level.txt +0 -1
  32. /odoo/addons/web_m2x_options_manager/readme/{DESCRIPTION.rst → DESCRIPTION.md} +0 -0
@@ -7,33 +7,58 @@ from odoo import fields, models
7
7
  class IrModel(models.Model):
8
8
  _inherit = "ir.model"
9
9
 
10
- m2x_create_edit_option_ids = fields.One2many(
10
+ m2x_option_ids = fields.One2many(
11
11
  "m2x.create.edit.option",
12
12
  "model_id",
13
13
  )
14
+ m2x_comodels_option_ids = fields.One2many(
15
+ "m2x.create.edit.option",
16
+ "comodel_id",
17
+ )
18
+ comodel_field_ids = fields.One2many("ir.model.fields", "comodel_id")
19
+
20
+ def button_empty_m2x_options(self):
21
+ self._empty_m2x_options(own=True)
22
+
23
+ def button_fill_m2x_options(self):
24
+ self._fill_m2x_options(own=True)
14
25
 
15
- def button_empty(self):
16
- for ir_model in self:
17
- ir_model._empty_m2x_create_edit_option()
26
+ def button_empty_m2x_comodels_options(self):
27
+ self._empty_m2x_options(comodels=True)
18
28
 
19
- def button_fill(self):
20
- for ir_model in self:
21
- ir_model._fill_m2x_create_edit_option()
29
+ def button_fill_m2x_comodels_options(self):
30
+ self._fill_m2x_options(comodels=True)
22
31
 
23
- def _empty_m2x_create_edit_option(self):
24
- """Removes every option for model ``self``"""
25
- self.ensure_one()
26
- self.m2x_create_edit_option_ids.unlink()
32
+ def _empty_m2x_options(self, own=False, comodels=False):
33
+ """Removes every option for model ``self``'s fields
27
34
 
28
- def _fill_m2x_create_edit_option(self):
29
- """Adds every missing field option for model ``self``"""
30
- self.ensure_one()
31
- existing = self.m2x_create_edit_option_ids.mapped("field_id")
32
- valid = self.field_id.filtered(lambda f: f.ttype in ("many2many", "many2one"))
33
- vals = [(0, 0, {"field_id": f.id}) for f in valid - existing]
34
- self.write({"m2x_create_edit_option_ids": vals})
35
+ :param bool own: if True, deletes options for model's fields
36
+ :param bool comodels: if True, deletes options for fields where ``self`` is
37
+ the field's comodel
38
+ """
39
+ to_delete = self.env["m2x.create.edit.option"]
40
+ if own:
41
+ to_delete += self.m2x_option_ids
42
+ if comodels:
43
+ to_delete += self.m2x_comodels_option_ids
44
+ if to_delete:
45
+ to_delete.unlink()
35
46
 
47
+ def _fill_m2x_options(self, own=False, comodels=False):
48
+ """Adds every missing field option for model ``self`` (with default values)
36
49
 
37
- class IrModelFields(models.Model):
38
- _inherit = "ir.model.fields"
39
- _rec_names_search = ["name", "field_description"]
50
+ :param bool own: if True, creates options for model's fields
51
+ :param bool comodels: if True, creates options for fields where ``self`` is
52
+ the field's comodel
53
+ """
54
+ todo = set()
55
+ if own:
56
+ exist = self.m2x_option_ids.field_id
57
+ valid = self.field_id.filtered("can_have_options")
58
+ todo.update((valid - exist).ids)
59
+ if comodels:
60
+ exist = self.m2x_comodels_option_ids.field_id
61
+ valid = self.comodel_field_ids.filtered("can_have_options")
62
+ todo.update((valid - exist).ids)
63
+ if todo:
64
+ self.env["m2x.create.edit.option"].create([{"field_id": i} for i in todo])
@@ -0,0 +1,53 @@
1
+ # Copyright 2025 Camptocamp SA
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import api, fields, models
5
+ from odoo.osv.expression import AND
6
+
7
+
8
+ class IrModelFields(models.Model):
9
+ _inherit = "ir.model.fields"
10
+
11
+ can_have_options = fields.Boolean(compute="_compute_can_have_options", store=True)
12
+ comodel_id = fields.Many2one(
13
+ "ir.model", compute="_compute_comodel_id", store=True, index=True
14
+ )
15
+
16
+ @api.depends("ttype")
17
+ def _compute_can_have_options(self):
18
+ for field in self:
19
+ field.can_have_options = field.ttype in ("many2many", "many2one")
20
+
21
+ @api.depends("relation")
22
+ def _compute_comodel_id(self):
23
+ empty = self.env["ir.model"]
24
+ getter = self.env["ir.model"]._get
25
+ for field in self:
26
+ if field.relation:
27
+ field.comodel_id = getter(field.relation)
28
+ else:
29
+ field.comodel_id = empty
30
+
31
+ @api.model
32
+ def name_search(self, name="", args=None, operator="ilike", limit=100):
33
+ # OVERRIDE: allow searching by field tech name if the correct context key is
34
+ # used; in this case, fields fetched by tech name are prepended to other fields
35
+ result = super().name_search(name, args, operator, limit)
36
+ if not (name and self.env.context.get("search_by_technical_name")):
37
+ return result
38
+ domain = AND([args or [], [("name", operator, name)]])
39
+ new_fields = self.search_read(domain, fields=["display_name"], limit=limit)
40
+ new_result = {f["id"]: f["display_name"] for f in new_fields}
41
+ while result and not (limit and 0 < limit <= len(new_result)):
42
+ field_id, field_display_name = result.pop(0)
43
+ if field_id not in new_result:
44
+ new_result[field_id] = field_display_name
45
+ return list(new_result.items())
46
+
47
+ @api.model
48
+ def _search(self, args, **kwargs):
49
+ # OVERRIDE: allow defining filtering custom domain on model/comodel when
50
+ # searching fields for O2M list views on ``m2x.create.edit.option``
51
+ if self.env.context.get("o2m_list_view_m2x_domain"):
52
+ args = AND([list(args or []), self.env.context["o2m_list_view_m2x_domain"]])
53
+ return super()._search(args, **kwargs)
@@ -8,12 +8,14 @@ class IrUiView(models.Model):
8
8
  _inherit = "ir.ui.view"
9
9
 
10
10
  def _postprocess_tag_field(self, node, name_manager, node_info):
11
+ # OVERRIDE: check ``m2x.create.edit.option`` config when processing a ``field``
12
+ # node in views
11
13
  res = super()._postprocess_tag_field(node, name_manager, node_info)
12
- if node.tag == "field":
13
- mname = name_manager.model._name
14
- field = name_manager.model._fields.get(node.get("name"))
15
- if field and field.type in ("many2many", "many2one"):
16
- rec = self.env["m2x.create.edit.option"].get(mname, field.name)
17
- if rec:
18
- rec._apply_options(node)
14
+ m2x_option = self.env["m2x.create.edit.option"].get(
15
+ name_manager.model._name,
16
+ # ``name`` is required in ``<field/>`` items
17
+ node.attrib["name"],
18
+ )
19
+ if m2x_option:
20
+ m2x_option._apply_options(node)
19
21
  return res
@@ -8,35 +8,48 @@ from odoo.tools.safe_eval import safe_eval
8
8
 
9
9
 
10
10
  class M2xCreateEditOption(models.Model):
11
+ """Technical model to define M2X option at single field level.
12
+
13
+ Each record is uniquely defined by its ``field_id``.
14
+ """
15
+
11
16
  _name = "m2x.create.edit.option"
12
- _description = "Manage Options 'Create/Edit' For Fields"
17
+ _description = "Field 'Create & Edit' Options"
13
18
 
19
+ name = fields.Char(compute="_compute_name", store=True)
14
20
  field_id = fields.Many2one(
15
21
  "ir.model.fields",
16
- domain=[("ttype", "in", ("many2many", "many2one"))],
22
+ domain=[("can_have_options", "=", True)],
17
23
  ondelete="cascade",
18
24
  required=True,
25
+ index=True,
19
26
  string="Field",
20
27
  )
21
-
22
28
  field_name = fields.Char(
23
29
  related="field_id.name",
24
30
  store=True,
25
31
  )
26
-
27
32
  model_id = fields.Many2one(
28
33
  "ir.model",
29
- ondelete="cascade",
30
- required=True,
34
+ related="field_id.model_id",
35
+ store=True,
31
36
  string="Model",
32
37
  )
33
-
34
38
  model_name = fields.Char(
35
- compute="_compute_model_name",
36
- inverse="_inverse_model_name",
39
+ related="field_id.model",
37
40
  store=True,
38
41
  )
39
-
42
+ comodel_id = fields.Many2one(
43
+ "ir.model",
44
+ related="field_id.comodel_id",
45
+ store=True,
46
+ string="Comodel",
47
+ )
48
+ comodel_name = fields.Char(
49
+ related="field_id.relation",
50
+ store=True,
51
+ string="Comodel Name",
52
+ )
40
53
  option_create = fields.Selection(
41
54
  [
42
55
  ("none", "Do nothing"),
@@ -55,7 +68,6 @@ class M2xCreateEditOption(models.Model):
55
68
  required=True,
56
69
  string="Create Option",
57
70
  )
58
-
59
71
  option_create_edit = fields.Selection(
60
72
  [
61
73
  ("none", "Do nothing"),
@@ -75,87 +87,125 @@ class M2xCreateEditOption(models.Model):
75
87
  string="Create & Edit Option",
76
88
  )
77
89
 
78
- option_create_edit_wizard = fields.Boolean(
79
- default=True,
80
- help="Defines behaviour for 'Create & Edit' Wizard\n"
81
- "Set to False to prevent 'Create & Edit' Wizard to pop up",
82
- string="Create & Edit Wizard",
83
- )
84
-
85
90
  _sql_constraints = [
86
91
  (
87
- "model_field_uniqueness",
88
- "unique(field_id,model_id)",
89
- "Options must be unique for each model/field couple!",
92
+ "field_uniqueness",
93
+ "unique(field_id)",
94
+ "Options must be unique for each field!",
90
95
  ),
91
96
  ]
92
97
 
93
98
  @api.model_create_multi
94
99
  def create(self, vals_list):
95
- # Clear cache to avoid misbehavior from cached :meth:`_get()`
96
- type(self)._get.clear_cache(self.browse())
100
+ # Clear cache to avoid misbehavior from cached methods
101
+ self._clear_caches()
97
102
  return super().create(vals_list)
98
103
 
99
104
  def write(self, vals):
100
- # Clear cache to avoid misbehavior from cached :meth:`_get()`
101
- type(self)._get.clear_cache(self.browse())
105
+ # Clear cache to avoid misbehavior from cached methods
106
+ if set(vals).intersection(["field_id"] + self._get_option_fields()):
107
+ self._clear_caches()
102
108
  return super().write(vals)
103
109
 
104
110
  def unlink(self):
105
- # Clear cache to avoid misbehavior from cached :meth:`_get()`
106
- type(self)._get.clear_cache(self.browse())
111
+ # Clear cache to avoid misbehavior from cached methods
112
+ self._clear_caches()
107
113
  return super().unlink()
108
114
 
109
- @api.depends("model_id")
110
- def _compute_model_name(self):
111
- for opt in self:
112
- opt.model_name = opt.model_id.model
115
+ def _clear_caches(self, *cache_names):
116
+ """Clear registry caches
113
117
 
114
- def _inverse_model_name(self):
115
- getter = self.env["ir.model"]._get
116
- for opt in self:
117
- # This also works as a constrain: if ``model_name`` is not a
118
- # valid model name, then ``model_id`` will be emptied, but it's
119
- # a required field!
120
- opt.model_id = getter(opt.model_name)
118
+ By default, clears caches to avoid misbehavior from cached methods:
119
+ - ``m2x.create.edit.option._get_id()``
120
+ - ``ir.ui.view._get_view_cache()``
121
+ """
122
+ self.env.registry.clear_cache(*self._clear_caches_get_names(*cache_names))
123
+
124
+ def _clear_caches_get_names(self, *cache_names) -> list[str]:
125
+ """Retrieves registry caches names for clearance
121
126
 
122
- @api.constrains("model_id", "field_id")
123
- def _check_field_in_model(self):
127
+ By default, we want to clear caches:
128
+ - "default": where ``m2x.create.edit.option._get_id()`` results get stored
129
+ - "templates": where ``ir.ui.view._get_view_cache()`` results get stored
130
+ """
131
+ return list(cache_names) + ["default", "templates"]
132
+
133
+ @api.depends("field_id")
134
+ def _compute_name(self):
124
135
  for opt in self:
125
- if opt.field_id.model_id != opt.model_id:
126
- msg = _(
127
- "'%(field_name)s' is not a valid field for model '%(model_name)s'!",
128
- field_name=opt.field_name,
129
- model_name=opt.model_name,
130
- )
131
- raise ValidationError(msg)
136
+ try:
137
+ opt.name = str(self.env[opt.field_id.model]._fields[opt.field_id.name])
138
+ except KeyError:
139
+ opt.name = "Invalid field"
132
140
 
133
141
  @api.constrains("field_id")
134
- def _check_field_type(self):
135
- ttypes = ("many2many", "many2one")
136
- if any(o.field_id.ttype not in ttypes for o in self):
137
- msg = _("Only Many2many and Many2one fields can be chosen!")
138
- raise ValidationError(msg)
142
+ def _check_field_can_have_options(self):
143
+ for opt in self:
144
+ if opt.field_id and not opt.field_id.can_have_options:
145
+ raise ValidationError(
146
+ _(
147
+ "Field %(field)s cannot have M2X options",
148
+ field=opt.field_id.display_name,
149
+ )
150
+ )
139
151
 
140
152
  def _apply_options(self, node):
141
- """Applies options ``self`` to ``node``"""
153
+ """Applies options ``self`` to ``node``
154
+
155
+ :param etree._Element node: view ``<field/>`` node to update
156
+ :rtype: None
157
+ """
158
+ self.ensure_one()
159
+ node_options = self._read_node_options(node)
160
+ for key, (mode, value) in self._read_own_options().items():
161
+ if mode == "force" or key not in node_options:
162
+ node_options[key] = value
163
+ node.set("options", str(node_options))
164
+
165
+ def _read_node_options(self, node):
166
+ """Helper method to read "options" attribute on ``node``
167
+
168
+ :param etree._Element node: view ``<field/>`` node to parse
169
+ :rtype: dict[str, Any]
170
+ """
142
171
  self.ensure_one()
143
172
  options = node.attrib.get("options") or {}
144
173
  if isinstance(options, str):
145
- options = safe_eval(options, dict(self.env.context or [])) or {}
146
-
147
- for k in ("create", "create_edit"):
148
- opt = self["option_%s" % k]
149
- if opt == "none":
150
- continue
151
- mode, val = opt.split("_")
152
- if k not in options:
153
- options[k] = val == "true"
154
- if mode == "force":
155
- options["no_%s" % k] = val == "false"
156
- if not self.option_create_edit_wizard:
157
- options["no_quick_create"] = True
158
- node.set("options", str(options))
174
+ options = safe_eval(options, self._get_node_options_eval_context()) or {}
175
+ return dict(options)
176
+
177
+ def _get_node_options_eval_context(self):
178
+ """Helper method to get eval context to read "options" attribute from a node
179
+
180
+ :rtype: dict
181
+ """
182
+ self.ensure_one()
183
+ eval_ctx = dict(self.env.context or [])
184
+ eval_ctx.update({"context": dict(eval_ctx)})
185
+ return eval_ctx
186
+
187
+ def _read_own_options(self):
188
+ """Helper method to retrieve M2X options from ``self``
189
+
190
+ :return: a dictionary mapping each M2X option to its mode and value, eg:
191
+ {'create': ('force', 'true'), 'create_edit': ('set', 'false')}
192
+ :rtype: dict[str, tuple[str, Any]]
193
+ """
194
+ self.ensure_one()
195
+ res = {}
196
+ for fname, fvalue in self.read(self._get_option_fields())[0].items():
197
+ if fname != "id" and fvalue != "none":
198
+ mode, value = tuple(fvalue.split("_"))
199
+ res[fname.replace("option_", "")] = (mode, value == "true")
200
+ return res
201
+
202
+ def _get_option_fields(self):
203
+ """Helper method to retrieve field names to parse as M2X options
204
+
205
+ :return: list of field names to parse as M2X options
206
+ :rtype: list[str]
207
+ """
208
+ return ["option_create", "option_create_edit"]
159
209
 
160
210
  @api.model
161
211
  def get(self, model_name, field_name):
@@ -163,22 +213,29 @@ class M2xCreateEditOption(models.Model):
163
213
 
164
214
  :param str model_name: technical model name (i.e. "sale.order")
165
215
  :param str field_name: technical field name (i.e. "partner_id")
216
+ :return: a ``m2x.create.edit.option`` record
217
+ :rtype: M2xCreateEditOption
166
218
  """
167
- return self.browse(self._get(model_name, field_name))
219
+ return self.browse(self._get_id(model_name, field_name))
168
220
 
169
221
  @api.model
170
- @ormcache("model_name", "field_name")
171
- def _get(self, model_name, field_name):
222
+ @ormcache("model_name", "field_name", cache="default")
223
+ def _get_id(self, model_name, field_name):
172
224
  """Inner implementation of ``get``.
225
+
173
226
  An ID is returned to allow caching (see :class:`ormcache`); :meth:`get`
174
227
  will then convert it to a proper record.
175
228
 
176
229
  :param str model_name: technical model name (i.e. "sale.order")
177
230
  :param str field_name: technical field name (i.e. "partner_id")
231
+ :return: a ``m2x.create.edit.option`` record ID
232
+ :rtype: int
178
233
  """
179
- dom = [
180
- ("model_name", "=", model_name),
181
- ("field_name", "=", field_name),
182
- ]
183
- # `_check_field_model_uniqueness()` grants uniqueness if existing
184
- return self.search(dom, limit=1).id
234
+ opt_id = 0
235
+ field = self.env["ir.model.fields"]._get(model_name, field_name)
236
+ if field:
237
+ # SQL constraint grants record uniqueness (if existing)
238
+ opt = self.search([("field_id", "=", field.id)], limit=1)
239
+ if opt:
240
+ opt_id = opt.id
241
+ return opt_id
@@ -0,0 +1,2 @@
1
+ - [Camptocamp](https://www.camptocamp.com):
2
+ - Silvio Gregorini
@@ -0,0 +1,12 @@
1
+ Go to Settings > Technical > Models.
2
+
3
+ Choose the model you wish to edit, and open its form view. Go to the "Create/Edit Options" tab,
4
+ and add the fields you want to manage in 2 different sections:
5
+
6
+ * the first list view allows you to handle fields for the selected model
7
+ * the second list view allows you to handle fields where the selected model is the comodel
8
+
9
+ For both sections:
10
+
11
+ * button "Fill" will add every missing field to the options
12
+ * button "Empty" will remove every option
@@ -1,20 +1,20 @@
1
- <?xml version="1.0" encoding="utf-8" ?>
2
1
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
2
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
3
  <head>
5
4
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
- <meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
7
- <title>Web M2X Options Manager</title>
5
+ <meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
6
+ <title>README.rst</title>
8
7
  <style type="text/css">
9
8
 
10
9
  /*
11
10
  :Author: David Goodger (goodger@python.org)
12
- :Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
11
+ :Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
13
12
  :Copyright: This stylesheet has been placed in the public domain.
14
13
 
15
14
  Default cascading style sheet for the HTML output of Docutils.
15
+ Despite the name, some widely supported CSS2 features are used.
16
16
 
17
- See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
17
+ See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
18
18
  customize this style sheet.
19
19
  */
20
20
 
@@ -275,7 +275,7 @@ pre.literal-block, pre.doctest-block, pre.math, pre.code {
275
275
  margin-left: 2em ;
276
276
  margin-right: 2em }
277
277
 
278
- pre.code .ln { color: grey; } /* line numbers */
278
+ pre.code .ln { color: gray; } /* line numbers */
279
279
  pre.code, code { background-color: #eeeeee }
280
280
  pre.code .comment, code .comment { color: #5C6576 }
281
281
  pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
@@ -301,7 +301,7 @@ span.option {
301
301
  span.pre {
302
302
  white-space: pre }
303
303
 
304
- span.problematic {
304
+ span.problematic, pre.problematic {
305
305
  color: red }
306
306
 
307
307
  span.section-subtitle {
@@ -360,76 +360,93 @@ ul.auto-toc {
360
360
  </style>
361
361
  </head>
362
362
  <body>
363
- <div class="document" id="web-m2x-options-manager">
364
- <h1 class="title">Web M2X Options Manager</h1>
363
+ <div class="document">
365
364
 
365
+
366
+ <a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
367
+ <img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
368
+ </a>
369
+ <div class="section" id="web-m2x-options-manager">
370
+ <h1>Web M2X Options Manager</h1>
366
371
  <!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
367
372
  !! This file is generated by oca-gen-addon-readme !!
368
373
  !! changes will be overwritten. !!
369
374
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
370
- !! source digest: sha256:e2c7c70fbcb74be8ffaed3747c322112463936bb6fbb5a48c42d659a5f8ddce7
375
+ !! source digest: sha256:496ff9c028368302f839913a2c4c825d59aa913df9048705ab11ce524d388144
371
376
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
372
- <p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/16.0/web_m2x_options_manager"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_m2x_options_manager"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
373
- <p>Allows managing the “Create…” and “Create and Edit…” options for <cite>Many2one</cite>
374
- and <cite>Many2many</cite> fields directly from the <cite>ir.model</cite> form view.</p>
377
+ <p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/license-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/17.0/web_m2x_options_manager"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-17-0/web-17-0-web_m2x_options_manager"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=17.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
378
+ <p>Allows managing the “Create…” and “Create and Edit…” options for
379
+ <tt class="docutils literal">Many2one</tt> and <tt class="docutils literal">Many2many</tt> fields directly from the <tt class="docutils literal">ir.model</tt>
380
+ form view.</p>
375
381
  <p><strong>Table of contents</strong></p>
376
382
  <div class="contents local topic" id="contents">
377
383
  <ul class="simple">
378
- <li><a class="reference internal" href="#usage" id="id1">Usage</a></li>
379
- <li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
380
- <li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
381
- <li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
382
- <li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
383
- <li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
384
+ <li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
385
+ <li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
386
+ <li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
387
+ <li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
388
+ <li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
389
+ <li><a class="reference internal" href="#maintainers" id="toc-entry-6">Maintainers</a></li>
384
390
  </ul>
385
391
  </li>
386
392
  </ul>
387
393
  </div>
388
394
  <div class="section" id="usage">
389
- <h1><a class="toc-backref" href="#id1">Usage</a></h1>
395
+ <h2><a class="toc-backref" href="#toc-entry-1">Usage</a></h2>
390
396
  <p>Go to Settings &gt; Technical &gt; Models.</p>
391
397
  <p>Choose the model you wish to edit, and open its form view. Go to the
392
- “Create/Edit Options” tab, and add the fields you want to manage.</p>
393
- <p>Button “Fill” will add every missing field to the options.
394
- Button “Empty” will remove every option.</p>
398
+ “Create/Edit Options” tab, and add the fields you want to manage in 2
399
+ different sections:</p>
400
+ <ul class="simple">
401
+ <li>the first list view allows you to handle fields for the selected model</li>
402
+ <li>the second list view allows you to handle fields where the selected
403
+ model is the comodel</li>
404
+ </ul>
405
+ <p>For both sections:</p>
406
+ <ul class="simple">
407
+ <li>button “Fill” will add every missing field to the options</li>
408
+ <li>button “Empty” will remove every option</li>
409
+ </ul>
395
410
  </div>
396
411
  <div class="section" id="bug-tracker">
397
- <h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
412
+ <h2><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h2>
398
413
  <p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
399
414
  In case of trouble, please check there if your issue has already been reported.
400
415
  If you spotted it first, help us to smash it by providing a detailed and welcomed
401
- <a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_m2x_options_manager%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
416
+ <a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_m2x_options_manager%0Aversion:%2017.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
402
417
  <p>Do not contact contributors directly about support or help with technical issues.</p>
403
418
  </div>
404
419
  <div class="section" id="credits">
405
- <h1><a class="toc-backref" href="#id3">Credits</a></h1>
420
+ <h2><a class="toc-backref" href="#toc-entry-3">Credits</a></h2>
406
421
  <div class="section" id="authors">
407
- <h2><a class="toc-backref" href="#id4">Authors</a></h2>
422
+ <h3><a class="toc-backref" href="#toc-entry-4">Authors</a></h3>
408
423
  <ul class="simple">
409
424
  <li>Camptocamp</li>
410
425
  </ul>
411
426
  </div>
412
427
  <div class="section" id="contributors">
413
- <h2><a class="toc-backref" href="#id5">Contributors</a></h2>
428
+ <h3><a class="toc-backref" href="#toc-entry-5">Contributors</a></h3>
414
429
  <ul class="simple">
415
430
  <li><a class="reference external" href="https://www.camptocamp.com">Camptocamp</a>:<ul>
416
431
  <li>Silvio Gregorini</li>
417
432
  </ul>
418
433
  </li>
419
- <li>Duong (Tran Quoc) &lt;<a class="reference external" href="mailto:duongtq&#64;trobz.com">duongtq&#64;trobz.com</a>&gt;</li>
420
434
  </ul>
421
435
  </div>
422
436
  <div class="section" id="maintainers">
423
- <h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
437
+ <h3><a class="toc-backref" href="#toc-entry-6">Maintainers</a></h3>
424
438
  <p>This module is maintained by the OCA.</p>
425
- <a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
439
+ <a class="reference external image-reference" href="https://odoo-community.org">
440
+ <img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
441
+ </a>
426
442
  <p>OCA, or the Odoo Community Association, is a nonprofit organization whose
427
443
  mission is to support the collaborative development of Odoo features and
428
444
  promote its widespread use.</p>
429
- <p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/16.0/web_m2x_options_manager">OCA/web</a> project on GitHub.</p>
445
+ <p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/17.0/web_m2x_options_manager">OCA/web</a> project on GitHub.</p>
430
446
  <p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
431
447
  </div>
432
448
  </div>
433
449
  </div>
450
+ </div>
434
451
  </body>
435
452
  </html>
@@ -1,4 +1,3 @@
1
- # Copyright 2021 Camptocamp SA
2
- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
-
1
+ from . import test_ir_model
2
+ from . import test_ir_model_fields
4
3
  from . import test_m2x_create_edit_option