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()