Skip to content

Commit b6be5d7

Browse files
committed
[MIG] web_m2x_options_manager: Migration to 17.0
Field ``option_create_edit_wizard`` has been dropped in the migration: the pop-up wizard does not exist anymore, so the field has no use
1 parent dbf07e1 commit b6be5d7

File tree

11 files changed

+147
-195
lines changed

11 files changed

+147
-195
lines changed
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
# Copyright 2021 Camptocamp SA
2-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3-
41
from . import models

web_m2x_options_manager/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"summary": 'Adds an interface to manage the "Create" and'
77
' "Create and Edit" options for specific models and'
88
" fields.",
9-
"version": "14.0.1.3.0",
9+
"version": "17.0.1.0.0",
1010
"author": "Camptocamp, Odoo Community Association (OCA)",
1111
"license": "AGPL-3",
1212
"category": "Web",

web_m2x_options_manager/i18n/it.po

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,6 @@ msgstr "Aggiungi"
3333
msgid "Create & Edit Option"
3434
msgstr "Opzione crea & modifica"
3535

36-
#. module: web_m2x_options_manager
37-
#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard
38-
msgid "Create & Edit Wizard"
39-
msgstr "Procedura guidata crea & modifica"
40-
4136
#. module: web_m2x_options_manager
4237
#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create
4338
msgid "Create Option"
@@ -58,16 +53,6 @@ msgstr "Creato da"
5853
msgid "Created on"
5954
msgstr "Creato il"
6055

61-
#. module: web_m2x_options_manager
62-
#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard
63-
msgid ""
64-
"Defines behaviour for 'Create & Edit' Wizard\n"
65-
"Set to False to prevent 'Create & Edit' Wizard to pop up"
66-
msgstr ""
67-
"Definisce il comportamento per la procedura guidata 'crea & modifica'\n"
68-
"Imposta a Falso per impedire il pop up della procedura guidata 'crea & "
69-
"modifica'"
70-
7156
#. module: web_m2x_options_manager
7257
#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit
7358
msgid ""

web_m2x_options_manager/i18n/web_m2x_options_manager.pot

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ msgstr ""
3030
msgid "Create & Edit Option"
3131
msgstr ""
3232

33-
#. module: web_m2x_options_manager
34-
#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard
35-
msgid "Create & Edit Wizard"
36-
msgstr ""
37-
3833
#. module: web_m2x_options_manager
3934
#: model:ir.model.fields,field_description:web_m2x_options_manager.field_m2x_create_edit_option__option_create
4035
msgid "Create Option"
@@ -55,13 +50,6 @@ msgstr ""
5550
msgid "Created on"
5651
msgstr ""
5752

58-
#. module: web_m2x_options_manager
59-
#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit_wizard
60-
msgid ""
61-
"Defines behaviour for 'Create & Edit' Wizard\n"
62-
"Set to False to prevent 'Create & Edit' Wizard to pop up"
63-
msgstr ""
64-
6553
#. module: web_m2x_options_manager
6654
#: model:ir.model.fields,help:web_m2x_options_manager.field_m2x_create_edit_option__option_create_edit
6755
msgid ""
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
# Copyright 2021 Camptocamp SA
2-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3-
41
from . import ir_model
2+
from . import ir_model_fields
53
from . import ir_ui_view
64
from . import m2x_create_edit_option
Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,30 @@
11
# Copyright 2021 Camptocamp SA
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

4-
from odoo import api, fields, models
4+
from odoo import fields, models
55

66

77
class IrModel(models.Model):
88
_inherit = "ir.model"
99

10-
m2x_create_edit_option_ids = fields.One2many(
11-
"m2x.create.edit.option",
12-
"model_id",
13-
)
10+
m2x_create_edit_option_ids = fields.One2many("m2x.create.edit.option", "model_id")
1411

1512
def button_empty(self):
16-
for ir_model in self:
17-
ir_model._empty_m2x_create_edit_option()
13+
self._empty_m2x_create_edit_option()
1814

1915
def button_fill(self):
2016
for ir_model in self:
2117
ir_model._fill_m2x_create_edit_option()
2218

2319
def _empty_m2x_create_edit_option(self):
24-
"""Removes every option for model ``self``"""
25-
self.ensure_one()
20+
"""Removes every option for models ``self``"""
2621
self.m2x_create_edit_option_ids.unlink()
2722

2823
def _fill_m2x_create_edit_option(self):
2924
"""Adds every missing field option for model ``self``"""
3025
self.ensure_one()
31-
existing = self.m2x_create_edit_option_ids.mapped("field_id")
26+
existing = self.m2x_create_edit_option_ids.field_id
3227
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-
36-
37-
class IrModelFields(models.Model):
38-
_inherit = "ir.model.fields"
39-
40-
@api.model
41-
def name_search(self, name="", args=None, operator="ilike", limit=100):
42-
res = super().name_search(name, args, operator, limit)
43-
if not (name and self.env.context.get("search_by_technical_name")):
44-
return res
45-
domain = list(args or []) + [("name", operator, name)]
46-
new_fids = self.search(domain, limit=limit).ids
47-
for fid in [x[0] for x in res]:
48-
if fid not in new_fids:
49-
new_fids.append(fid)
50-
if limit and limit > 0:
51-
new_fids = new_fids[:limit]
52-
return self.browse(new_fids).sudo().name_get()
28+
if missing := (valid - existing):
29+
vals = [fields.Command.create({"field_id": f.id}) for f in missing]
30+
self.write({"m2x_create_edit_option_ids": vals})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2021 Camptocamp SA
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from odoo import api, models
5+
from odoo.osv.expression import AND
6+
7+
8+
class IrModelFields(models.Model):
9+
_inherit = "ir.model.fields"
10+
11+
@api.model
12+
def name_search(self, name="", args=None, operator="ilike", limit=100):
13+
# OVERRIDE: allow searching by field tech name if the correct context key is
14+
# used; in this case, fields fetched by tech name are prepended to other fields
15+
res = super().name_search(name, args, operator, limit)
16+
if not (name and self.env.context.get("search_by_technical_name")):
17+
return res
18+
domain = AND([args or [], [("name", operator, name)]])
19+
new_fields = self.search_read(domain, fields=["display_name"], limit=limit)
20+
new_res = {f["id"]: f["display_name"] for f in new_fields}
21+
while res and (not limit or limit <= 0 or len(new_res) <= limit):
22+
fid, fname = res.pop(0)
23+
if fid not in new_res:
24+
new_res[fid] = fname
25+
return list(new_res.items())

web_m2x_options_manager/models/ir_ui_view.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
class IrUiView(models.Model):
88
_inherit = "ir.ui.view"
99

10-
def postprocess(self, node, current_node_path, editable, name_manager):
11-
res = super().postprocess(node, current_node_path, editable, name_manager)
12-
if node.tag == "field":
13-
mname = name_manager.Model._name
14-
fname = node.attrib["name"]
15-
field = self.env[mname]._fields.get(fname)
16-
if field and field.type in ("many2many", "many2one"):
17-
rec = self.env["m2x.create.edit.option"].get(mname, field.name)
18-
if rec:
19-
rec._apply_options(node)
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
13+
res = super()._postprocess_tag_field(node, name_manager, node_info=node_info)
14+
if (field_name := node.get("name")) and (
15+
option := self.env["m2x.create.edit.option"].get(
16+
name_manager.model._name, field_name
17+
)
18+
):
19+
option._apply_options(node)
2020
return res

web_m2x_options_manager/models/m2x_create_edit_option.py

Lines changed: 57 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,18 @@ class M2xCreateEditOption(models.Model):
1616
domain=[("ttype", "in", ("many2many", "many2one"))],
1717
ondelete="cascade",
1818
required=True,
19-
string="Field",
2019
)
21-
22-
field_name = fields.Char(
23-
related="field_id.name",
24-
store=True,
25-
string="Field Name",
26-
)
27-
20+
field_name = fields.Char(related="field_id.name", store=True)
2821
model_id = fields.Many2one(
2922
"ir.model",
3023
ondelete="cascade",
3124
required=True,
32-
string="Model",
3325
)
34-
3526
model_name = fields.Char(
3627
compute="_compute_model_name",
3728
inverse="_inverse_model_name",
3829
store=True,
39-
string="Model Name",
4030
)
41-
4231
option_create = fields.Selection(
4332
[
4433
("none", "Do nothing"),
@@ -57,7 +46,6 @@ class M2xCreateEditOption(models.Model):
5746
required=True,
5847
string="Create Option",
5948
)
60-
6149
option_create_edit = fields.Selection(
6250
[
6351
("none", "Do nothing"),
@@ -77,13 +65,6 @@ class M2xCreateEditOption(models.Model):
7765
string="Create & Edit Option",
7866
)
7967

80-
option_create_edit_wizard = fields.Boolean(
81-
default=True,
82-
help="Defines behaviour for 'Create & Edit' Wizard\n"
83-
"Set to False to prevent 'Create & Edit' Wizard to pop up",
84-
string="Create & Edit Wizard",
85-
)
86-
8768
_sql_constraints = [
8869
(
8970
"model_field_uniqueness",
@@ -94,87 +75,107 @@ class M2xCreateEditOption(models.Model):
9475

9576
@api.model_create_multi
9677
def create(self, vals_list):
97-
# Clear cache to avoid misbehavior from cached :meth:`_get()`
98-
type(self)._get.clear_cache(self.browse())
78+
self._clear_caches()
9979
return super().create(vals_list)
10080

10181
def write(self, vals):
102-
# Clear cache to avoid misbehavior from cached :meth:`_get()`
103-
type(self)._get.clear_cache(self.browse())
82+
self._clear_caches()
10483
return super().write(vals)
10584

10685
def unlink(self):
107-
# Clear cache to avoid misbehavior from cached :meth:`_get()`
108-
type(self)._get.clear_cache(self.browse())
86+
self._clear_caches()
10987
return super().unlink()
11088

89+
def _clear_caches(self, *cache_names):
90+
"""Clear registry caches
91+
92+
By default, clears caches to avoid misbehavior from cached methods:
93+
- ``m2x.create.edit.option._get()``
94+
- ``ir.ui.view._get_view_cache()``
95+
"""
96+
self.env.registry.clear_cache(*self._clear_caches_get_names(*cache_names))
97+
98+
def _clear_caches_get_names(self, *cache_names) -> list[str]:
99+
"""Retrieves registry caches names for clearance
100+
101+
By default, we want to clear caches:
102+
- "default": where ``m2x.create.edit.option._get()`` results get stored
103+
- "templates": where ``ir.ui.view._get_view_cache()`` results get stored
104+
"""
105+
return list(cache_names) + ["default", "templates"]
106+
111107
@api.depends("model_id")
112108
def _compute_model_name(self):
113109
for opt in self:
114110
opt.model_name = opt.model_id.model
115111

116112
def _inverse_model_name(self):
117113
getter = self.env["ir.model"]._get
118-
for opt in self:
119-
# This also works as a constrain: if ``model_name`` is not a
120-
# valid model name, then ``model_id`` will be emptied, but it's
121-
# a required field!
122-
opt.model_id = getter(opt.model_name)
114+
for model_name, opts in self.grouped("model_name").items():
115+
if model := getter(model_name):
116+
opts.model_id = model
117+
else:
118+
raise ValidationError(
119+
_("Invalid model name: '%(model_name)s'", model_name=model_name)
120+
)
123121

124122
@api.constrains("model_id", "field_id")
125123
def _check_field_in_model(self):
126124
for opt in self:
127125
if opt.field_id.model_id != opt.model_id:
128-
msg = _("'%s' is not a valid field for model '%s'!")
129-
raise ValidationError(msg % (opt.field_name, opt.model_name))
126+
raise ValidationError(
127+
_(
128+
"'%(fname)s' is not a valid field for model '%(mname)s'!",
129+
fname=opt.field_name,
130+
mname=opt.model_name,
131+
)
132+
)
130133

131134
@api.constrains("field_id")
132135
def _check_field_type(self):
133-
ttypes = ("many2many", "many2one")
134-
if any(o.field_id.ttype not in ttypes for o in self):
135-
msg = _("Only Many2many and Many2one fields can be chosen!")
136-
raise ValidationError(msg)
136+
if any(f.ttype not in ("many2many", "many2one") for f in self.field_id):
137+
raise ValidationError(
138+
_("Only Many2many and Many2one fields can be chosen!")
139+
)
137140

138141
def _apply_options(self, node):
139-
"""Applies options ``self`` to ``node``"""
142+
"""Applies option ``self`` to ``node``"""
140143
self.ensure_one()
141144
options = node.attrib.get("options") or {}
142145
if isinstance(options, str):
143146
options = safe_eval(options, dict(self.env.context or [])) or {}
144-
for k in ("create", "create_edit"):
145-
opt = self["option_%s" % k]
146-
if opt == "none":
147-
continue
148-
mode, val = opt.split("_")
149-
if mode == "force" or k not in options:
150-
options[k] = val == "true"
147+
for key in ("create", "create_edit"):
148+
if (opt := self[f"option_{key}"]) != "none":
149+
mode, val = opt.split("_")
150+
if mode == "force" or key not in options:
151+
options[key] = val == "true"
151152
node.set("options", str(options))
152-
if not self.option_create_edit_wizard:
153-
node.set("can_create", "false")
154-
node.set("can_write", "true")
155153

156154
@api.model
157155
def get(self, model_name, field_name):
158156
"""Returns specific record for ``field_name`` in ``model_name``
159157
160158
:param str model_name: technical model name (i.e. "sale.order")
161159
:param str field_name: technical field name (i.e. "partner_id")
160+
:returns: the ``m2x.create.edit.option`` record for the given model/field couple
161+
:rtype: M2xCreateEditOption
162162
"""
163163
return self.browse(self._get(model_name, field_name))
164164

165165
@api.model
166-
@ormcache("model_name", "field_name")
166+
@ormcache("model_name", "field_name", cache="default")
167167
def _get(self, model_name, field_name):
168168
"""Inner implementation of ``get``.
169-
An ID is returned to allow caching (see :class:`ormcache`); :meth:`get`
169+
170+
An ID is returned to allow caching (see :class:``ormcache``); :meth:``get``
170171
will then convert it to a proper record.
171172
172173
:param str model_name: technical model name (i.e. "sale.order")
173174
:param str field_name: technical field name (i.e. "partner_id")
175+
:returns: the ``m2x.create.edit.option`` ID for the given model/field couple,
176+
or None if there's no match
177+
:rtype: int|None
174178
"""
175-
dom = [
176-
("model_name", "=", model_name),
177-
("field_name", "=", field_name),
178-
]
179-
# `_check_field_model_uniqueness()` grants uniqueness if existing
180-
return self.search(dom, limit=1).id
179+
dom = [("model_name", "=", model_name), ("field_name", "=", field_name)]
180+
# ``_check_field_model_uniqueness()`` grants uniqueness if existing
181+
return self.search(dom, limit=1).id or None

0 commit comments

Comments
 (0)