From e58a569f23c4bcdd4cb2b844ffb6632c55f6e78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 14 Nov 2023 00:00:15 +0100 Subject: [PATCH] Do not force value for single list on multi-value field Fields based on multi-value must not be forced to the single value of a list. Closes #12737 --- sao/src/common.js | 4 ++-- sao/src/model.js | 9 +++++++-- sao/src/view/tree.js | 4 +++- sao/tests/sao.js | 1 + tryton/tryton/common/domain_inversion.py | 7 ++++--- tryton/tryton/gui/window/view_form/model/field.py | 10 ++++++++-- tryton/tryton/gui/window/view_form/view/list.py | 4 +++- trytond/trytond/tests/test_tools.py | 2 ++ trytond/trytond/tools/domain_inversion.py | 5 +++-- 9 files changed, 33 insertions(+), 13 deletions(-) diff --git a/sao/src/common.js b/sao/src/common.js index 1d7d7e38b3..6149cc1dc1 100644 --- a/sao/src/common.js +++ b/sao/src/common.js @@ -2755,12 +2755,12 @@ } return this.simplify(this.merge(result)); }, - unique_value: function(domain) { + unique_value: function(domain, single_value=true) { if ((domain instanceof Array) && (domain.length == 1)) { let [name, operator, value, ...model] = domain[0]; if (operator == '=' || - (operator == 'in' && value.length == 1)) { + (single_value && operator == 'in' && value.length == 1)) { value = operator == '=' ? value : value[0]; var count = 0; if (model.length && name.endsWith('.id')) { diff --git a/sao/src/model.js b/sao/src/model.js index 13915bd59f..e8d98b8377 100644 --- a/sao/src/model.js +++ b/sao/src/model.js @@ -1556,6 +1556,7 @@ Sao.field.Field = Sao.class_(Object, { _default: null, + _single_value: true, init: function(description) { this.description = description; this.name = description.name; @@ -1728,12 +1729,13 @@ } else { let [screen_domain, _] = this.get_domains( record, pre_validate); - var uniques = inversion.unique_value(domain); + var uniques = inversion.unique_value( + domain, this._single_value); var unique = uniques[0]; var leftpart = uniques[1]; var value = uniques[2]; let unique_from_screen = inversion.unique_value( - screen_domain)[0]; + screen_domain, this._single_value)[0]; if (this._is_empty(record) && !is_required && !is_invisible && @@ -1821,6 +1823,7 @@ Sao.field.MultiSelection = Sao.class_(Sao.field.Selection, { _default: null, + _single_value: false, get: function(record) { var value = Sao.field.MultiSelection._super.get.call(this, record); if (jQuery.isEmptyObject(value)) { @@ -2203,6 +2206,7 @@ Sao.field.One2Many._super.init.call(this, description); }, _default: null, + _single_value: false, _set_value: function(record, value, default_, modified) { this._set_default_value(record); var group = record._values[this.name]; @@ -2813,6 +2817,7 @@ Sao.field.Dict = Sao.class_(Sao.field.Field, { _default: {}, + _single_value: false, init: function(description) { Sao.field.Dict._super.init.call(this, description); this.schema_model = new Sao.Model(description.schema_model); diff --git a/sao/src/view/tree.js b/sao/src/view/tree.js index bc74cf1c4c..940d87223f 100644 --- a/sao/src/view/tree.js +++ b/sao/src/view/tree.js @@ -879,11 +879,13 @@ visible_columns -= 1; column.set_visible(false); } else { + const field = this.screen.model.fields[name]; var inv_domain = inversion.domain_inversion(domain, name); if (typeof inv_domain != 'boolean') { inv_domain = inversion.simplify(inv_domain); } - var unique = inversion.unique_value(inv_domain)[0]; + var unique = inversion.unique_value( + inv_domain, field._single_value)[0]; if (unique && jQuery.isEmptyObject(this.children_field)) { visible_columns -= 1; column.set_visible(false); diff --git a/sao/tests/sao.js b/sao/tests/sao.js index a09aca6fb5..9586275baf 100644 --- a/sao/tests/sao.js +++ b/sao/tests/sao.js @@ -2813,6 +2813,7 @@ QUnit.ok(compare(unique_value(domain), [false, null, null])); domain = [['a', 'in', [1]]]; QUnit.ok(compare(unique_value(domain), [true, 'a', 1])); + QUnit.ok(compare(unique_value(domain, false), [false, null, null])); domain = [['a', 'in', [1, 2]]]; QUnit.ok(compare(unique_value(domain), [false, null, null])); domain = [['a', 'in', []]]; diff --git a/tryton/tryton/common/domain_inversion.py b/tryton/tryton/common/domain_inversion.py index cf56f049b0..6e52d28037 100644 --- a/tryton/tryton/common/domain_inversion.py +++ b/tryton/tryton/common/domain_inversion.py @@ -424,13 +424,14 @@ def concat(*domains, **kwargs): return simplify(merge(result)) -def unique_value(domain): +def unique_value(domain, single_value=True): "Return if unique, the field and the value" if (isinstance(domain, list) and len(domain) == 1): name, operator, value, *model = domain[0] - if operator == '=' or (operator == 'in' and len(value) == 1): - value = value if operator == '=' else value[0] + if (operator == '=' + or (single_value and operator == 'in' and len(value) == 1)): + value = value if operator == '=' and single_value else value[0] count = 0 if model and name.endswith('.id'): count = 1 diff --git a/tryton/tryton/gui/window/view_form/model/field.py b/tryton/tryton/gui/window/view_form/model/field.py index 097c10facf..06b1d81417 100644 --- a/tryton/tryton/gui/window/view_form/model/field.py +++ b/tryton/tryton/gui/window/view_form/model/field.py @@ -30,6 +30,7 @@ class Field(object): set_client: save the value from the widget ''' _default = None + _single_value = True @staticmethod def get_field(ctype): @@ -111,8 +112,10 @@ class Field(object): invalid = 'domain' else: screen_domain, _ = self.domains_get(record, pre_validate) - unique, leftpart, value = unique_value(domain) - unique_from_screen, _, _ = unique_value(screen_domain) + unique, leftpart, value = unique_value( + domain, single_value=self._single_value) + unique_from_screen, _, _ = unique_value( + screen_domain, single_value=self._single_value) if (self._is_empty(record) and not is_required and not is_invisible @@ -248,6 +251,7 @@ class SelectionField(Field): class MultiSelectionField(SelectionField): _default = None + _single_value = False def get(self, record): value = super().get(record) @@ -601,6 +605,7 @@ class O2MField(Field): ''' _default = None + _single_value = False def __init__(self, attrs): super(O2MField, self).__init__(attrs) @@ -1112,6 +1117,7 @@ class BinaryField(Field): class DictField(Field): _default = {} + _single_value = False def __init__(self, attrs): super(DictField, self).__init__(attrs) diff --git a/tryton/tryton/gui/window/view_form/view/list.py b/tryton/tryton/gui/window/view_form/view/list.py index d18e2525df..4a3a1bfc2d 100644 --- a/tryton/tryton/gui/window/view_form/view/list.py +++ b/tryton/tryton/gui/window/view_form/view/list.py @@ -1132,10 +1132,12 @@ class ViewTree(View): elif name == self.screen.exclude_field: column.set_visible(False) else: + field = self.group.fields.get(name) inv_domain = domain_inversion(domain, name) if not isinstance(inv_domain, bool): inv_domain = simplify(inv_domain) - unique, _, _ = unique_value(inv_domain) + unique, _, _ = unique_value( + inv_domain, single_value=field._single_value) column.set_visible(not unique or bool(self.children_field)) if self.children_field: for i, column in enumerate(self.treeview.get_columns()): diff --git a/trytond/trytond/tests/test_tools.py b/trytond/trytond/tests/test_tools.py index e1772c579b..5b55839eda 100644 --- a/trytond/trytond/tests/test_tools.py +++ b/trytond/trytond/tests/test_tools.py @@ -830,6 +830,8 @@ class DomainInversionTestCase(unittest.TestCase): domain = [['a', 'in', [1]]] self.assertEqual(unique_value(domain), (True, 'a', 1)) + self.assertEqual( + unique_value(domain, single_value=False), (False, None, None)) domain = [['a', 'in', [1, 2]]] self.assertEqual(unique_value(domain), (False, None, None)) diff --git a/trytond/trytond/tools/domain_inversion.py b/trytond/trytond/tools/domain_inversion.py index cf56f049b0..60220ca20f 100644 --- a/trytond/trytond/tools/domain_inversion.py +++ b/trytond/trytond/tools/domain_inversion.py @@ -424,12 +424,13 @@ def concat(*domains, **kwargs): return simplify(merge(result)) -def unique_value(domain): +def unique_value(domain, single_value=True): "Return if unique, the field and the value" if (isinstance(domain, list) and len(domain) == 1): name, operator, value, *model = domain[0] - if operator == '=' or (operator == 'in' and len(value) == 1): + if (operator == '=' + or (single_value and operator == 'in' and len(value) == 1)): value = value if operator == '=' else value[0] count = 0 if model and name.endswith('.id'):