Add MultiSelection entry to Dict field
issue8903 review284571003
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]"
|
||||
|
||||
Reference in New Issue
Block a user