1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
# repository contains the full copyright notices and license terms.
import json
from functools import partial
from sql import Literal, operators
from trytond.rpc import RPC
from trytond.transaction import Transaction
from .field import Field, domain_method
from .selection import SelectionMixin
# Use canonical form
dumps = partial(json.dumps, separators=(',', ':'), sort_keys=True)
class MultiSelection(SelectionMixin, Field):
"Define a multi-selection field."
_type = 'multiselection'
_sql_type = 'VARCHAR'
_py_type = tuple
def __init__(self, selection, string='', sort=True, translate=True,
help='', help_selection=None, required=False, readonly=False,
domain=None, states=None, on_change=None,
on_change_with=None, depends=None, context=None, loading='eager'):
"""
:param selection: A list or a function name that returns a list.
The list must be a list of tuples. First member is the value
to store and the second is the value to display.
:param sort: A boolean to sort or not the selections.
"""
super().__init__(string=string, help=help,
required=required, readonly=readonly, domain=domain, states=states,
on_change=on_change, on_change_with=on_change_with,
depends=depends, context=context, loading=loading)
if hasattr(selection, 'copy'):
self.selection = selection.copy()
else:
self.selection = selection
self.selection_change_with = set()
self.sort = sort
self.translate_selection = translate
self.help_selection = help_selection
__init__.__doc__ += Field.__init__.__doc__
def set_rpc(self, model):
super().set_rpc(model)
if not isinstance(self.selection, (list, tuple)):
assert hasattr(model, self.selection), \
'Missing %s on model %s' % (self.selection, model.__name__)
instantiate = 0 if self.selection_change_with else None
cache = dict(days=1) if instantiate is None else None
model.__rpc__.setdefault(
self.selection, RPC(instantiate=instantiate, cache=cache))
def get(self, ids, model, name, values=None):
lists = {id: () for id in ids}
for value in values or []:
data = value[name]
if data:
# If stored as JSON conversion is done on backend
if isinstance(data, str):
data = json.loads(data)
lists[value['id']] = tuple(data)
return lists
def sql_format(self, value):
value = super().sql_format(value)
if isinstance(value, (list, tuple)):
value = dumps(sorted(set(value)))
return value
def _domain_column(self, operator, column):
database = Transaction().database
return database.json_get(super()._domain_column(operator, column))
def _domain_value(self, operator, value):
database = Transaction().database
domain_value = super()._domain_value(operator, value)
if value is not None:
domain_value = database.json_get(domain_value)
return domain_value
@domain_method
def convert_domain(self, domain, tables, Model):
name, operator, value = domain[:3]
if operator not in {'in', 'not in'}:
return super().convert_domain(domain, tables, Model)
database = Transaction().database
table, _ = tables[None]
raw_column = self.sql_column(table)
if isinstance(value, str):
try:
expression = database.json_key_exists(raw_column, value)
except NotImplementedError:
expression = operators.Like(
raw_column, '%' + dumps(value) + '%')
else:
try:
expression = database.json_any_keys_exist(
raw_column, list(value))
except NotImplementedError:
expression = Literal(False)
for item in value:
expression |= operators.Like(
raw_column, '%' + dumps(item) + '%')
if operator == 'not in':
expression = operators.Not(expression)
return expression
|