Skip to content

Commit 1836511

Browse files
committed
add many2many support for draft
1 parent 1934653 commit 1836511

File tree

6 files changed

+50
-19
lines changed

6 files changed

+50
-19
lines changed

web_form_banner/README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Evaluation context variables available in Message Value Code:
7373
form's unsaved changes) + the current unsaved changes on trigger fields.
7474
Should be used instead of `record` when your rule is triggered dynamically by an
7575
update to a trigger field. It doesn't include any values from complex fields
76-
(x2many/reference, etc).
76+
(one2many/reference, etc).
7777
* `record_id`: Integer id of the record being edited, or `False` if the form
7878
is creating a new record.
7979
* `model`: Shortcut to the current model (`env[record._name]`).
@@ -221,8 +221,8 @@ Limitations of `draft` eval context variable
221221
persisted record; all other fields come from `Model.new` defaults rather than the
222222
database.
223223
* Only simple field types are included: `char`, `text`, `html`, `selection`, `boolean`,
224-
`integer`, `float`, `monetary`, `date`, `datetime`, and `many2one` (normalized to an
225-
integer ID). **x2many/reference/other types are omitted.**
224+
`integer`, `float`, `monetary`, `date`, `datetime`, `many2one`, and `many2many`.
225+
**one2many/reference/other types are omitted.**
226226

227227
Bug Tracker
228228
===========

web_form_banner/models/web_form_banner_rule.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,60 @@
3030

3131

3232
def _extract_m2o_id(v):
33-
"""Normalize many2one values to an integer id or False.
34-
Accepts: int, (id, ...) tuple/list, or dict with id-ish keys.
35-
"""
33+
"""Normalize many2one values to an integer id or False."""
3634
if isinstance(v, int):
3735
return v
3836
if isinstance(v, (list, tuple)) and v and isinstance(v[0], int):
3937
return v[0]
4038
if isinstance(v, dict):
41-
data = v.get("data") or {}
42-
return v.get("res_id") or data.get("id") or v.get("id") or v.get("ref") or False
39+
m2o_id = v.get("res_id") or v.get("id")
40+
if isinstance(m2o_id, int):
41+
return m2o_id
4342
return False
4443

4544

45+
def _m2m_items(value):
46+
if isinstance(value, (list, tuple)):
47+
return value
48+
if isinstance(value, dict):
49+
if isinstance(value.get("res_ids"), (list, tuple)):
50+
return value["res_ids"]
51+
if isinstance(value.get("data"), (list, tuple)):
52+
return value["data"]
53+
return None
54+
55+
56+
def _to_int_id(e):
57+
if isinstance(e, int):
58+
return e
59+
if isinstance(e, str) and e.isdigit():
60+
return int(e)
61+
if isinstance(e, dict):
62+
rid = e.get("res_id")
63+
if isinstance(rid, int):
64+
return rid
65+
iid = e.get("id")
66+
if isinstance(iid, int):
67+
return iid
68+
return None
69+
70+
4671
def _sanitize_field(field, value):
4772
"""Return sanitized value for a single field, or None to skip."""
4873
if not field:
4974
return None
5075
if field.type == "many2one":
5176
return _extract_m2o_id(value)
77+
if field.type == "many2many":
78+
items = _m2m_items(value)
79+
if items is None:
80+
return None
81+
ids = [i for i in (_to_int_id(e) for e in items) if i is not None]
82+
# Always return a command, even when empty, to reflect clearing the relation
83+
return [(6, 0, ids)]
5284
if field.type in _SIMPLE_FIELD_TYPES:
5385
return value
54-
return None # skip x2many/reference/others
86+
return None # skip one2many/reference/others
5587

5688

5789
class WebFormBannerRule(models.Model):

web_form_banner/readme/ROADMAP.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ Limitations of `draft` eval context variable
1313
persisted record; all other fields come from `Model.new` defaults rather than the
1414
database.
1515
* Only simple field types are included: `char`, `text`, `html`, `selection`, `boolean`,
16-
`integer`, `float`, `monetary`, `date`, `datetime`, and `many2one` (normalized to an
17-
integer ID). **x2many/reference/other types are omitted.**
16+
`integer`, `float`, `monetary`, `date`, `datetime`, `many2one`, and `many2many`.
17+
**one2many/reference/other types are omitted.**

web_form_banner/readme/USAGE.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Evaluation context variables available in Message Value Code:
2929
form's unsaved changes) + the current unsaved changes on trigger fields.
3030
Should be used instead of `record` when your rule is triggered dynamically by an
3131
update to a trigger field. It doesn't include any values from complex fields
32-
(x2many/reference, etc).
32+
(one2many/reference, etc).
3333
* `record_id`: Integer id of the record being edited, or `False` if the form
3434
is creating a new record.
3535
* `model`: Shortcut to the current model (`env[record._name]`).

web_form_banner/static/description/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ <h2><a class="toc-backref" href="#id3">Evaluation context variables available in
433433
form’s unsaved changes) + the current unsaved changes on trigger fields.
434434
Should be used instead of <cite>record</cite> when your rule is triggered dynamically by an
435435
update to a trigger field. It doesn’t include any values from complex fields
436-
(x2many/reference, etc).</li>
436+
(one2many/reference, etc).</li>
437437
<li><cite>record_id</cite>: Integer id of the record being edited, or <cite>False</cite> if the form
438438
is creating a new record.</li>
439439
<li><cite>model</cite>: Shortcut to the current model (<cite>env[record._name]</cite>).</li>
@@ -568,8 +568,8 @@ <h2><a class="toc-backref" href="#id8">Limitations of <cite>draft</cite> eval co
568568
persisted record; all other fields come from <cite>Model.new</cite> defaults rather than the
569569
database.</li>
570570
<li>Only simple field types are included: <cite>char</cite>, <cite>text</cite>, <cite>html</cite>, <cite>selection</cite>, <cite>boolean</cite>,
571-
<cite>integer</cite>, <cite>float</cite>, <cite>monetary</cite>, <cite>date</cite>, <cite>datetime</cite>, and <cite>many2one</cite> (normalized to an
572-
integer ID). <strong>x2many/reference/other types are omitted.</strong></li>
571+
<cite>integer</cite>, <cite>float</cite>, <cite>monetary</cite>, <cite>date</cite>, <cite>datetime</cite>, <cite>many2one</cite>, and <cite>many2many</cite>.
572+
<strong>one2many/reference/other types are omitted.</strong></li>
573573
</ul>
574574
</div>
575575
</div>

web_form_banner/static/src/js/web_form_banner.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ odoo.define("web_form_banner.save_plus_load", function (require) {
3535
if (p && typeof p.always === "function") { p.always(fn); return p; }
3636
return Promise.resolve(p).finally(fn);
3737
};
38-
// keep only primitives + many2one record ids
3938
var shrinkDraft = function (d) {
4039
return Object.entries(d || {}).reduce(function (o, kv) {
4140
var k = kv[0], v = kv[1], t = typeof v;
4241
if (v == null || t === "string" || t === "number" || t === "boolean") {
4342
o[k] = v;
4443
} else if (v && v.type === "record" && typeof v.res_id === "number") {
4544
o[k] = v.res_id;
45+
} else if (Array.isArray(v) || (v && (Array.isArray(v.data) || Array.isArray(v.res_ids)))) {
46+
// many2many (and possibly other x2many) values; let Python decide
47+
o[k] = v;
4648
}
4749
return o;
4850
}, {});
@@ -149,7 +151,6 @@ odoo.define("web_form_banner.save_plus_load", function (require) {
149151
update: function () {
150152
return withRefresh(this, this._super, arguments);
151153
},
152-
153154
// onchange: refresh only when a declared trigger actually changed
154155
_onFieldChanged: function (ev) {
155156
var res = this._super.apply(this, arguments);
@@ -162,13 +163,11 @@ odoo.define("web_form_banner.save_plus_load", function (require) {
162163
after(res, () => refreshBanners(this));
163164
return res;
164165
},
165-
166166
activate: function () {
167167
var res = this._super.apply(this, arguments);
168168
if (hasBanners(this)) after(res, () => refreshBanners(this));
169169
return res;
170170
},
171-
172171
on_attach_callback: function () {
173172
this._super.apply(this, arguments);
174173
setTimeout(() => refreshBanners(this));

0 commit comments

Comments
 (0)