Files
tryton/trytond/model/fields/dict.py
T

207 lines
7.7 KiB
Python
Raw Normal View History

2013-01-26 15:18:41 +01:00
# This file is part of Tryton. The COPYRIGHT file at the toplevel of this
# repository contains the full copyright notices and license terms.
2019-01-18 11:32:47 +01:00
from functools import partial
2016-11-05 19:54:12 +01:00
import json
2013-01-26 15:18:41 +01:00
2019-01-18 11:32:47 +01:00
from sql import operators, Literal, Select, CombiningQuery, Cast, Null
from trytond import backend
2020-07-21 14:38:36 +02:00
from trytond.pool import Pool
from trytond.protocols.jsonrpc import JSONDecoder, JSONEncoder
from trytond.tools import grouped_slice
2019-01-18 11:32:47 +01:00
from trytond.transaction import Transaction
from .field import Field, SQL_OPERATORS
2013-01-26 15:18:41 +01:00
2019-01-18 11:32:47 +01:00
# Use canonical form
dumps = partial(
json.dumps, cls=JSONEncoder, separators=(',', ':'), sort_keys=True)
2013-01-26 15:18:41 +01:00
class Dict(Field):
'Define dict field.'
_type = 'dict'
_sql_type = 'TEXT'
2019-06-26 21:56:16 +02:00
_py_type = dict
2013-01-26 15:18:41 +01:00
def __init__(self, schema_model, string='', help='', required=False,
readonly=False, domain=None, states=None, select=False,
on_change=None, on_change_with=None, depends=None,
2013-09-06 22:32:37 +02:00
context=None, loading='lazy'):
2013-01-26 15:18:41 +01:00
super(Dict, self).__init__(string, help, required, readonly, domain,
2013-09-06 22:32:37 +02:00
states, select, on_change, on_change_with, depends, context,
loading)
2013-01-26 15:18:41 +01:00
self.schema_model = schema_model
2019-01-18 11:32:47 +01:00
self.search_unaccented = True
2013-01-26 15:18:41 +01:00
def get(self, ids, model, name, values=None):
dicts = dict((id, None) for id in ids)
for value in values or []:
data = value[name]
if data:
# If stored as JSON conversion is done on backend
2018-06-26 19:11:19 +02:00
if isinstance(data, str):
data = json.loads(data, object_hook=JSONDecoder())
dicts[value['id']] = data
2013-01-26 15:18:41 +01:00
return dicts
2013-09-06 22:32:37 +02:00
def sql_format(self, value):
2019-06-26 21:56:16 +02:00
value = super().sql_format(value)
if isinstance(value, dict):
2019-12-30 14:26:14 +01:00
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)
2019-06-26 21:56:16 +02:00
return value
2013-09-06 22:32:37 +02:00
2014-11-27 22:20:43 +01:00
def translated(self, name=None, type_='values'):
"Return a descriptor for the translated value of the field"
if name is None:
name = self.name
if name is None:
raise ValueError('Missing name argument')
return TranslatedDict(name, type_)
2019-01-18 11:32:47 +01:00
def _domain_column(self, operator, column, key=None):
database = Transaction().database
column = database.json_get(
super()._domain_column(operator, column), key)
if operator.endswith('like'):
column = Cast(column, database.sql_type('VARCHAR').base)
if self.search_unaccented and operator.endswith('ilike'):
column = database.unaccent(column)
return column
def _domain_value(self, operator, value):
2019-12-06 17:33:47 +01:00
if backend.name == 'sqlite' and isinstance(value, bool):
2019-01-18 11:32:47 +01:00
# json_extract returns 0 for JSON false and 1 for JSON true
value = int(value)
if isinstance(value, (Select, CombiningQuery)):
return value
2019-12-30 14:26:14 +01:00
if isinstance(value, (list, tuple)):
value = sorted(set(value))
2019-01-18 11:32:47 +01:00
if operator.endswith('in'):
return [dumps(v) for v in value]
else:
value = dumps(value)
if self.search_unaccented and operator.endswith('ilike'):
database = Transaction().database
value = database.unaccent(value)
return value
def _domain_add_null(self, column, operator, value, expression):
expression = super()._domain_add_null(
column, operator, value, expression)
if value is None and operator.endswith('='):
if operator == '=':
expression |= (column == Null)
else:
expression &= (column != Null)
return expression
def convert_domain(self, domain, tables, Model):
name, operator, value = domain[:3]
if '.' not in name:
return super().convert_domain(domain, tables, Model)
database = Transaction().database
table, _ = tables[None]
name, key = name.split('.', 1)
Operator = SQL_OPERATORS[operator]
2019-12-30 14:26:14 +01:00
raw_column = self.sql_column(table)
column = self._domain_column(operator, raw_column, key)
2019-01-18 11:32:47 +01:00
expression = Operator(column, self._domain_value(operator, value))
if operator in {'=', '!='}:
# Try to use custom operators in case there is indexes
try:
if value is None:
expression = database.json_key_exists(
raw_column, key)
if operator == '=':
expression = operators.Not(expression)
2019-12-30 14:26:14 +01:00
# we compare on multi-selection by doing an equality check and
# not a contain check
elif not isinstance(value, (list, tuple)):
2019-01-18 11:32:47 +01:00
expression = database.json_contains(
raw_column, dumps({key: value}))
if operator == '!=':
expression = operators.Not(expression)
expression &= database.json_key_exists(
raw_column, key)
2019-12-30 14:26:14 +01:00
return expression
2019-01-18 11:32:47 +01:00
except NotImplementedError:
pass
2019-12-30 14:26:14 +01:00
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
2019-01-18 11:32:47 +01:00
expression = self._domain_add_null(column, operator, value, expression)
return expression
def convert_order(self, name, tables, Model):
fname, _, key = name.partition('.')
if not key:
return super().convert_order(fname, tables, Model)
database = Transaction().database
table, _ = tables[None]
column = self.sql_column(table)
return [database.json_get(column, key)]
2019-02-13 10:37:40 +01:00
def definition(self, model, language):
definition = super().definition(model, language)
definition['schema_model'] = self.schema_model
return definition
2014-11-27 22:20:43 +01:00
class TranslatedDict(object):
'A descriptor for translated values of Dict field'
def __init__(self, name, type_):
assert type_ in ['keys', 'values']
self.name = name
self.type_ = type_
def __get__(self, inst, cls):
if inst is None:
return self
pool = Pool()
schema_model = getattr(cls, self.name).schema_model
SchemaModel = pool.get(schema_model)
value = getattr(inst, self.name)
if not value:
return value
domain = []
if self.type_ == 'values':
domain = [('type_', '=', 'selection')]
records = []
for key_names in grouped_slice(value.keys()):
records += SchemaModel.search([
('name', 'in', key_names),
] + domain)
keys = SchemaModel.get_keys(records)
if self.type_ == 'keys':
return {k['name']: k['string'] for k in keys}
elif self.type_ == 'values':
trans = {k['name']: dict(k['selection']) for k in keys}
return {k: v if k not in trans else trans[k].get(v, v)
2018-06-26 19:11:19 +02:00
for k, v in value.items()}