Add MultiSelection entry to Dict field

issue8903
review284571003
This commit is contained in:
Nicolas Évrard
2019-12-30 14:26:14 +01:00
parent 6b14518eff
commit e2d8b6b295
5 changed files with 186 additions and 16 deletions
+1
View File
@@ -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
+3
View File
@@ -90,6 +90,9 @@ this repository contains the full copyright notices and license terms. -->
<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>
+8 -5
View File
@@ -42,6 +42,8 @@ class DictSchemaMixin(object):
('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 @@ class DictSchemaMixin(object):
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 @@ class DictSchemaMixin(object):
@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 @@ class DictSchemaMixin(object):
'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(
+33 -11
View File
@@ -47,7 +47,14 @@ class Dict(Field):
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 @@ class Dict(Field):
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 @@ class Dict(Field):
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
+141
View File
@@ -36,6 +36,17 @@ class FieldDictTestCase(unittest.TestCase):
'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):
@@ -130,6 +141,18 @@ class FieldDictTestCase(unittest.TestCase):
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"
@@ -234,6 +257,31 @@ class FieldDictTestCase(unittest.TestCase):
self.assertListEqual(dicts_bar, [])
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"
@@ -290,6 +338,35 @@ class FieldDictTestCase(unittest.TestCase):
self.assertListEqual(dicts_bar, [dict_])
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"
@@ -402,6 +479,38 @@ class FieldDictTestCase(unittest.TestCase):
self.assertListEqual(dicts_empty, [])
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]"
@@ -446,6 +555,38 @@ class FieldDictTestCase(unittest.TestCase):
self.assertListEqual(dicts_empty, [dict_])
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]"