changeset ef2eba99f59b in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset;node=ef2eba99f59b
description:
        Add MultiSelection entry to Dict field

        issue8903
        review284571003
diffstat:

 CHANGELOG                        |    1 +
 trytond/ir/message.xml           |    3 +
 trytond/model/dictschema.py      |   13 ++-
 trytond/model/fields/dict.py     |   44 +++++++++---
 trytond/tests/test_field_dict.py |  141 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 186 insertions(+), 16 deletions(-)

diffs (346 lines):

diff -r 9634941347ec -r ef2eba99f59b CHANGELOG
--- a/CHANGELOG Sat Dec 28 18:16:28 2019 +0100
+++ b/CHANGELOG Mon Dec 30 14:26:14 2019 +0100
@@ -1,3 +1,4 @@
+* Add MultiSelection entry to Dict field
 * Allow empty order clause
 * Change editable tree attribute into boolean
 * Send default order to clients
diff -r 9634941347ec -r ef2eba99f59b trytond/ir/message.xml
--- a/trytond/ir/message.xml    Sat Dec 28 18:16:28 2019 +0100
+++ b/trytond/ir/message.xml    Mon Dec 30 14:26:14 2019 +0100
@@ -90,6 +90,9 @@
         <record model="ir.message" id="msg_dict_schema_selection">
             <field name="text">Selection</field>
         </record>
+        <record model="ir.message" id="msg_dict_schema_multiselection">
+            <field name="text">MultiSelection</field>
+        </record>
         <record model="ir.message" id="msg_dict_schema_digits">
             <field name="text">Digits</field>
         </record>
diff -r 9634941347ec -r ef2eba99f59b trytond/model/dictschema.py
--- a/trytond/model/dictschema.py       Sat Dec 28 18:16:28 2019 +0100
+++ b/trytond/model/dictschema.py       Mon Dec 30 14:26:14 2019 +0100
@@ -42,6 +42,8 @@
             ('date', lazy_gettext('ir.msg_dict_schema_date')),
             ('datetime', lazy_gettext('ir.msg_dict_schema_datetime')),
             ('selection', lazy_gettext('ir.msg_dict_schema_selection')),
+            ('multiselection',
+                lazy_gettext('ir.msg_dict_schema_multiselection')),
             ], lazy_gettext('ir.msg_dict_schema_type'), required=True)
     digits = fields.Integer(
         lazy_gettext('ir.msg_dict_schema_digits'),
@@ -52,19 +54,20 @@
     selection = fields.Text(
         lazy_gettext('ir.msg_dict_schema_selection'),
         states={
-            'invisible': Eval('type_') != 'selection',
+            'invisible': ~Eval('type_').in_(['selection', 'multiselection']),
             }, translate=True, depends=['type_'],
         help=lazy_gettext('ir.msg_dict_schema_selection_help'))
     selection_sorted = fields.Boolean(
         lazy_gettext('ir.msg_dict_schema_selection_sorted'),
         states={
-            'invisible': Eval('type_') != 'selection',
+            'invisible': ~Eval('type_').in_(['selection', 'multiselection']),
             }, depends=['type_'],
         help=lazy_gettext('ir.msg_dict_schema_selection_sorted_help'))
     selection_json = fields.Function(fields.Char(
             lazy_gettext('ir.msg_dict_schema_selection_json'),
             states={
-                'invisible': Eval('type_') != 'selection',
+                'invisible': ~Eval('type_').in_(
+                    ['selection', 'multiselection']),
                 },
             depends=['type_']), 'get_selection_json')
 
@@ -117,7 +120,7 @@
     @classmethod
     def check_selection(cls, schemas):
         for schema in schemas:
-            if schema.type_ != 'selection':
+            if schema.type_ not in {'selection', 'multiselection'}:
                 continue
             try:
                 dict(json.loads(schema.get_selection_json()))
@@ -147,7 +150,7 @@
                 'domain': record.domain,
                 'sequence': getattr(record, 'sequence', record.name),
                 }
-            if record.type_ == 'selection':
+            if record.type_ in {'selection', 'multiselection'}:
                 with Transaction().set_context(language=Config.get_language()):
                     english_key = cls(record.id)
                     selection = OrderedDict(json.loads(
diff -r 9634941347ec -r ef2eba99f59b trytond/model/fields/dict.py
--- a/trytond/model/fields/dict.py      Sat Dec 28 18:16:28 2019 +0100
+++ b/trytond/model/fields/dict.py      Mon Dec 30 14:26:14 2019 +0100
@@ -47,7 +47,14 @@
     def sql_format(self, value):
         value = super().sql_format(value)
         if isinstance(value, dict):
-            value = dumps({k: v for k, v in value.items() if v is not None})
+            d = {}
+            for k, v in value.items():
+                if v is None:
+                    continue
+                if isinstance(v, list):
+                    v = list(sorted(set(v)))
+                d[k] = v
+            value = dumps(d)
         return value
 
     def translated(self, name=None, type_='values'):
@@ -74,6 +81,8 @@
             value = int(value)
         if isinstance(value, (Select, CombiningQuery)):
             return value
+        if isinstance(value, (list, tuple)):
+            value = sorted(set(value))
         if operator.endswith('in'):
             return [dumps(v) for v in value]
         else:
@@ -101,33 +110,46 @@
         table, _ = tables[None]
         name, key = name.split('.', 1)
         Operator = SQL_OPERATORS[operator]
-        column = self.sql_column(table)
-        column = self._domain_column(operator, column, key)
+        raw_column = self.sql_column(table)
+        column = self._domain_column(operator, raw_column, key)
         expression = Operator(column, self._domain_value(operator, value))
         if operator in {'=', '!='}:
             # Try to use custom operators in case there is indexes
-            raw_column = self.sql_column(table)
             try:
                 if value is None:
                     expression = database.json_key_exists(
                         raw_column, key)
                     if operator == '=':
                         expression = operators.Not(expression)
-                    return expression
-                else:
+                # we compare on multi-selection by doing an equality check and
+                # not a contain check
+                elif not isinstance(value, (list, tuple)):
                     expression = database.json_contains(
                         raw_column, dumps({key: value}))
                     if operator == '!=':
                         expression = operators.Not(expression)
                         expression &= database.json_key_exists(
                             raw_column, key)
-                    return expression
+                return expression
             except NotImplementedError:
                 pass
-        if isinstance(expression, operators.In) and not expression.right:
-            expression = Literal(False)
-        elif isinstance(expression, operators.NotIn) and not expression.right:
-            expression = Literal(True)
+        elif operator.endswith('in'):
+            # Try to use custom operators in case there is indexes
+            if not value:
+                expression = Literal(operator.startswith('not'))
+            else:
+                op = '!=' if operator.startswith('not') else '='
+                try:
+                    in_expr = Literal(False)
+                    for v in value:
+                        in_expr |= database.json_contains(
+                            self._domain_column(op, raw_column, key),
+                            dumps(v))
+                    if operator.startswith('not'):
+                        in_expr = ~in_expr
+                    expression = in_expr
+                except NotImplementedError:
+                    pass
         expression = self._domain_add_null(column, operator, value, expression)
         return expression
 
diff -r 9634941347ec -r ef2eba99f59b trytond/tests/test_field_dict.py
--- a/trytond/tests/test_field_dict.py  Sat Dec 28 18:16:28 2019 +0100
+++ b/trytond/tests/test_field_dict.py  Mon Dec 30 14:26:14 2019 +0100
@@ -36,6 +36,17 @@
                     'type_': 'selection',
                     'selection': ('arabic: Arabic\n'
                         'hexa: Hexadecimal'),
+                    }, {
+                    'name': 'countries',
+                    'string': 'Countries',
+                    'type_': 'multiselection',
+                    'selection': (
+                        'au: Australia\n'
+                        'be: Belgium\n'
+                        'ca: Canada\n'
+                        'de: Germany\n'
+                        'es: Spain\n'
+                        'fr: France'),
                     }])
 
     def set_jsonb(self, table):
@@ -131,6 +142,18 @@
         self.assertDictEqual(dict_.dico, {'type': 'arabic'})
 
     @with_transaction()
+    def test_create_multiselection(self):
+        "Test create dict with multi-selection"
+        Dict = Pool().get('test.dict')
+        self.create_schema()
+
+        dict_, = Dict.create([{
+                    'dico': {'countries': ['fr', 'be']},
+                    }])
+
+        self.assertDictEqual(dict_.dico, {'countries': ['be', 'fr']})
+
+    @with_transaction()
     def test_invalid_selection_schema(self):
         "Test invalid selection schema"
         pool = Pool()
@@ -235,6 +258,31 @@
         self.assertListEqual(dicts_foo_b, [])
 
     @with_transaction()
+    def test_search_multiselection_equals(self):
+        "Test search dict multi-selection equals"
+        pool = Pool()
+        Dict = pool.get('test.dict')
+        self.create_schema()
+
+        dict_, = Dict.create([{
+                    'dico': {'countries': ['fr', 'be']},
+                    }])
+
+        france_belgium = Dict.search([
+                ('dico.countries', '=', ['be', 'fr']),
+                ])
+        belgium = Dict.search([
+                ('dico.countries', '=', ['be']),
+                ])
+        germany = Dict.search([
+                ('dico.countries', '=', ['de']),
+                ])
+
+        self.assertEqual(france_belgium, [dict_])
+        self.assertEqual(belgium, [])
+        self.assertEqual(germany, [])
+
+    @with_transaction()
     def test_search_element_equals_none(self):
         "Test search dict element equals None"
         pool = Pool()
@@ -291,6 +339,35 @@
         self.assertListEqual(dicts_foo_b, [])
 
     @with_transaction()
+    def test_search_multiselection_not_equals(self):
+        "Test search dict multi-selection not equals"
+        pool = Pool()
+        Dict = pool.get('test.dict')
+        self.create_schema()
+
+        dict_, = Dict.create([{
+                    'dico': {'countries': ['fr', 'be']},
+                    }])
+
+        not_france_belgium = Dict.search([
+                ('dico.countries', '!=', ['be', 'fr']),
+                ])
+        not_belgium = Dict.search([
+                ('dico.countries', '!=', ['be']),
+                ])
+        not_germany = Dict.search([
+                ('dico.countries', '!=', ['de']),
+                ])
+        not_empty = Dict.search([
+                ('dico.countries', '!=', []),
+                ])
+
+        self.assertEqual(not_france_belgium, [])
+        self.assertEqual(not_belgium, [dict_])
+        self.assertEqual(not_germany, [dict_])
+        self.assertEqual(not_empty, [dict_])
+
+    @with_transaction()
     def test_search_element_non_equals_none(self):
         "Test search dict element non equals None"
         pool = Pool()
@@ -403,6 +480,38 @@
         self.assertListEqual(dicts_foo_b, [])
 
     @with_transaction()
+    @unittest.skipIf(
+        backend.name != 'postgresql',
+        'in use the contain check specific to postgresql')
+    def test_search_multiselection_in(self):
+        "Test search dict multi-selection with in"
+        pool = Pool()
+        Dict = pool.get('test.dict')
+        self.create_schema()
+
+        dict_, = Dict.create([{
+                    'dico': {'countries': ['fr', 'be']},
+                    }])
+
+        belgium = Dict.search([
+                ('dico.countries', 'in', ['be']),
+                ])
+        germany = Dict.search([
+                ('dico.countries', 'in', ['de']),
+                ])
+        belgium_germany = Dict.search([
+                ('dico.countries', 'in', ['be', 'de']),
+                ])
+        empty = Dict.search([
+                ('dico.countries', 'in', []),
+                ])
+
+        self.assertEqual(belgium, [dict_])
+        self.assertEqual(germany, [])
+        self.assertEqual(belgium_germany, [dict_])
+        self.assertEqual(empty, [])
+
+    @with_transaction()
     def test_search_element_in_none(self):
         "Test search dict element in [None]"
         pool = Pool()
@@ -447,6 +556,38 @@
         self.assertListEqual(dicts_foo_b, [])
 
     @with_transaction()
+    @unittest.skipIf(
+        backend.name != 'postgresql',
+        'in use the contain check specific to postgresql')
+    def test_search_multiselection_not_in(self):
+        "Test search dict multi-selection with not in"
+        pool = Pool()
+        Dict = pool.get('test.dict')
+        self.create_schema()
+
+        dict_, = Dict.create([{
+                    'dico': {'countries': ['fr', 'be']},
+                    }])
+
+        not_belgium = Dict.search([
+                ('dico.countries', 'not in', ['be']),
+                ])
+        not_germany = Dict.search([
+                ('dico.countries', 'not in', ['de']),
+                ])
+        not_belgium_germany = Dict.search([
+                ('dico.countries', 'not in', ['de', 'be']),
+                ])
+        not_empty = Dict.search([
+                ('dico.countries', 'not in', []),
+                ])
+
+        self.assertEqual(not_belgium, [])
+        self.assertEqual(not_germany, [dict_])
+        self.assertEqual(not_belgium_germany, [])
+        self.assertEqual(not_empty, [dict_])
+
+    @with_transaction()
     def test_search_element_not_in_none(self):
         "Test search dict element not in [None]"
         pool = Pool()

Reply via email to