lims_interface: remove ViewData

This commit is contained in:
Sebastián Marró 2020-10-04 22:57:09 -03:00
parent 4608f14bac
commit ea0b6fa971
4 changed files with 125 additions and 374 deletions

View File

@ -34,7 +34,6 @@ def register():
table.TableGroupedView,
data.ModelAccess,
data.Data,
data.ViewData,
data.GroupedData,
notebook.NotebookLine,
module='lims_interface', type_='model')

View File

@ -9,6 +9,7 @@ import formulas
import schedula
from decimal import Decimal
from itertools import chain
from collections import defaultdict
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
@ -84,8 +85,7 @@ class Adapter:
res['notebook_line'] = obj
for i in range(0, groups):
obj = fields.One2Many(
'lims.interface.grouped_data', None, 'Group %s' % (i + 1, ),
context={'lims_interface_table': table.id})
'lims.interface.grouped_data', 'data', 'Group %s' % (i + 1, ))
obj.name = 'group_%s' % (i + 1, )
res[obj.name] = obj
return res
@ -146,6 +146,10 @@ class GroupedAdapter:
obj.name = 'notebook_line'
obj.readonly = True
res['notebook_line'] = obj
obj = fields.Many2One('lims.interface.data', 'Data')
obj.name = 'data'
obj.readonly = True
res['data'] = obj
obj = fields.Integer('Iteration')
obj.name = 'iteration'
obj.readonly = True
@ -153,76 +157,6 @@ class GroupedAdapter:
return res
class ViewAdapter:
def __getattr__(self, name):
fields = self.get_fields()
return getattr(fields, name)
def __contains__(self, key):
fields = self.get_fields()
return fields.__contains__(key)
def __iter__(self):
fields = self.get_fields()
return fields.__iter__()
def __getitem__(self, name):
fields = self.get_fields()
return fields.__getitem__(name)
def get_fields(self):
# TODO: Cache
Data = Pool().get('lims.interface.view_data')
table = Data.get_table()
if not table:
return Data._previous_fields
res = {}
groups = 0
for field in table.fields_:
if field.type == 'char':
obj = fields.Char(field.string)
elif field.type == 'multiline':
obj = fields.Text(field.string)
elif field.type == 'integer':
obj = fields.Integer(field.string)
elif field.type == 'float':
obj = fields.Float(field.string)
elif field.type == 'boolean':
obj = fields.Boolean(field.string)
elif field.type == 'numeric':
obj = fields.Numeric(field.string)
elif field.type == 'date':
obj = fields.Date(field.string)
elif field.type == 'datetime':
obj = fields.DateTime(field.string)
elif field.type == 'timestamp':
obj = fields.Timestamp(field.string)
elif field.type == 'many2one':
obj = fields.Many2One(field.related_model.model, field.string)
elif field.type in ('binary', 'icon'):
obj = fields.Binary(field.string)
obj.name = field.name
res[field.name] = obj
groups = max(groups, field.group or 0)
obj = fields.Integer('ID')
obj.name = 'id'
res['id'] = obj
obj = fields.Many2One('lims.interface.compilation', 'Compilation')
obj.name = 'compilation'
res['compilation'] = obj
obj = fields.Many2One('lims.notebook.line', 'Notebook Line')
obj.name = 'notebook_line'
obj.readonly = True
res['notebook_line'] = obj
for i in range(0, groups):
obj = fields.One2Many(
'lims.interface.grouped_data', None, 'Group %s' % (i + 1, ),
context={'lims_interface_table': table.id})
obj.name = 'group_%s' % (i + 1, )
res[obj.name] = obj
return res
class ModelAccess(metaclass=PoolMeta):
__name__ = 'ir.model.access'
@ -238,8 +172,7 @@ class ModelAccess(metaclass=PoolMeta):
Model._fields[fieldname] we would not be forced to override the method.
'''
if model_name in ('lims.interface.data',
'lims.interface.grouped_data',
'lims.interface.view_data'):
'lims.interface.grouped_data'):
return True
return super().check_relation(model_name, field_name, mode)
@ -263,7 +196,7 @@ class Data(ModelSQL, ModelView):
def __post_setup__(cls):
super().__post_setup__()
cls._previous_fields = cls._fields
cls._fields = Adapter().get_fields()
cls._fields = Adapter()
@classmethod
def __table__(cls):
@ -289,12 +222,33 @@ class Data(ModelSQL, ModelView):
def on_change_with(self, fieldnames):
table = self.get_table()
res = {}
grouped_fields = defaultdict(list)
for field in table.fields_:
if field.group:
grouped_fields[field.group].append(field.name)
for field in table.fields_:
if field.name not in fieldnames:
continue
ast = field.get_ast()
inputs = field.inputs.split()
inputs = [getattr(self, x) for x in inputs]
inputs = []
for input_ in field.inputs.split():
found = False
for group, repetition_fields in grouped_fields.items():
if input_ in repetition_fields:
group_values = getattr(self, 'group_%s' % group)
if not group_values:
continue
for line in group_values:
if line['iteration'] == int(
input_.split('_')[-1:][0]):
inputs.append(line[
'_'.join(input_.split('_')[:-1])])
found = True
if not found:
inputs.append(getattr(self, input_))
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
@ -321,9 +275,30 @@ class Data(ModelSQL, ModelView):
fn_name = 'on_change_with_' + field.name
def fn(self):
table = self.get_table()
grouped_fields = defaultdict(list)
for table_field in table.fields_:
if table_field.group:
grouped_fields[table_field.group].append(table_field.name)
ast = field.get_ast()
inputs = field.inputs.split()
inputs = [getattr(self, x) for x in inputs]
inputs = []
for input_ in field.inputs.split():
found = False
for group, repetition_fields in grouped_fields.items():
if input_ in repetition_fields:
group_values = getattr(self, 'group_%s' % group)
if not group_values:
continue
for line in group_values:
if line['iteration'] == int(
input_.split('_')[-1:][0]):
inputs.append(line[
'_'.join(input_.split('_')[:-1])])
found = True
if not found:
inputs.append(getattr(self, input_))
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
@ -351,7 +326,17 @@ class Data(ModelSQL, ModelView):
table = cls.get_table()
readonly = Transaction().context.get('lims_interface_readonly', False)
encoder = PYSONEncoder()
groups = 0
grouped_fields = defaultdict(list)
for field in table.fields_:
if field.group:
grouped_fields[field.group].append(field.name)
for field in table.fields_:
groups = max(groups, field.group or 0)
if field.group:
continue
res[field.name] = {
'name': field.name,
'string': field.string,
@ -364,7 +349,16 @@ class Data(ModelSQL, ModelView):
'sortable': True,
}
if field.inputs:
res[field.name]['on_change_with'] = field.inputs.split()
inputs = []
for input_ in field.inputs.split():
found = False
for group, repetition_fields in grouped_fields.items():
if input_ in repetition_fields:
inputs.append('group_%s' % group)
found = True
if not found:
inputs.append(input_)
res[field.name]['on_change_with'] = list(set(inputs))
cls.add_on_change_with_method(field)
func_name = '%s_%s' % ('on_change_with', field.name)
cls.__rpc__.setdefault(func_name, RPC(instantiate=0))
@ -379,6 +373,24 @@ class Data(ModelSQL, ModelView):
'%H:%M:%S.%f')
if field.type in ['float', 'numeric']:
res[field.name]['digits'] = encoder.encode((16, field.digits))
for i in range(0, groups):
field_description = None
for rep in cls.get_compilation().interface.grouped_repetitions:
if rep.group == i + 1:
field_description = rep.description
field_name = 'group_%s' % (i + 1)
res[field_name] = {
'name': field_name,
'string': field_description or field_name,
'type': 'one2many',
'help': '',
'relation': 'lims.interface.grouped_data',
}
res[field_name]['views'] = {
'tree': GroupedData.fields_view_get(
view_type='tree', group=i + 1)}
cls.__rpc__.setdefault(func_name, RPC(instantiate=0))
return res
@classmethod
@ -397,8 +409,10 @@ class Data(ModelSQL, ModelView):
]
groups = 0
for field in table.fields_:
fields_names.append(field.name)
groups = max(groups, field.group or 0)
if field.group and view.type == 'form':
continue
fields_names.append(field.name)
for i in range(0, groups):
fields_names.append('group_%s' % (i + 1))
res = {
@ -632,8 +646,9 @@ class Data(ModelSQL, ModelView):
def get_formula_value(self, field, vals={}):
ast = field.get_ast()
inputs = []
for x in field.inputs.split():
inputs.append(vals.get(x, getattr(self, x)))
if field.inputs:
for x in field.inputs.split():
inputs.append(vals.get(x, getattr(self, x)))
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
@ -705,6 +720,8 @@ class GroupedData(ModelView):
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line',
readonly=True)
data = fields.Many2One('lims.inteface.data', 'Data',
readonly=True)
iteration = fields.Integer('Iteration', readonly=True)
@classmethod
@ -813,7 +830,8 @@ class GroupedData(ModelView):
'domain': field.domain,
}
if field.inputs:
res[field.name]['on_change_with'] = field.inputs.split()
res[field.name]['on_change_with'] = field.inputs.split() + [
'data']
cls.add_on_change_with_method(field)
func_name = '%s_%s' % ('on_change_with', field.name)
cls.__rpc__.setdefault(func_name, RPC(instantiate=0))
@ -842,6 +860,7 @@ class GroupedData(ModelView):
fields_names = [
'notebook_line',
'data',
'iteration',
]
for field in table.grouped_fields_:
@ -880,283 +899,3 @@ class GroupedData(ModelView):
table = compilation.table
if table:
return Table(table)
class ViewData(ModelView):
'Lims Interface View Data'
__name__ = 'lims.interface.view_data'
compilation = fields.Many2One('lims.interface.compilation', 'Compilation',
required=True, ondelete='CASCADE')
notebook_line = fields.Many2One('lims.notebook.line', 'Notebook Line',
readonly=True)
@classmethod
def __setup__(cls):
super().__setup__()
cls.__rpc__['fields_view_get'].cache = None
cls.__rpc__['default_get'].cache = None
@classmethod
def __post_setup__(cls):
super().__post_setup__()
cls._previous_fields = cls._fields
cls._fields = ViewAdapter().get_fields()
def __init__(self, id=None, **kwargs):
kwargs_copy = kwargs.copy()
for kw in kwargs_copy:
kwargs.pop(kw, None)
super().__init__(id, **kwargs)
self._values = {}
for kw in kwargs_copy:
self._values[kw] = kwargs_copy[kw]
def __getattr__(self, name):
try:
return super().__getattr__(name)
except AttributeError:
pass
def on_change_with(self, fieldnames):
table = self.get_table()
res = {}
for field in table.fields_:
if field.name not in fieldnames:
continue
ast = field.get_ast()
inputs = field.inputs.split()
inputs = [getattr(self, x) for x in inputs]
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
raise UserError(e.args[0] % e.args[1:])
if isinstance(value, list):
value = str(value)
elif not isinstance(value, (str, int, float, Decimal, type(None))):
value = value.tolist()
if isinstance(value, formulas.tokens.operand.XlError):
value = None
elif isinstance(value, list):
for x in chain(*value):
if isinstance(x, formulas.tokens.operand.XlError):
value = None
res[field.name] = value
return res
@classmethod
def add_on_change_with_method(cls, field):
"""
Dynamically add 'on_change_with_<field>' methods.
"""
fn_name = 'on_change_with_' + field.name
def fn(self):
ast = field.get_ast()
inputs = field.inputs.split()
inputs = [getattr(self, x) for x in inputs]
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
raise UserError(e.args[0] % e.args[1:])
if isinstance(value, list):
value = str(value)
elif not isinstance(value, (str, int, float, Decimal, type(None))):
value = value.tolist()
if isinstance(value, formulas.tokens.operand.XlError):
value = None
elif isinstance(value, list):
for x in chain(*value):
if isinstance(x, formulas.tokens.operand.XlError):
value = None
return value
setattr(cls, fn_name, fn)
def on_change(self, fieldnames):
table = self.get_table()
res = {}
for field in table.fields_:
if not field.formula:
continue
ast = field.get_ast()
inputs = field.inputs.split()
inputs = [getattr(self, x) for x in inputs]
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
raise UserError(e.args[0] % e.args[1:])
if isinstance(value, list):
value = str(value)
elif not isinstance(value, (str, int, float, Decimal, type(None))):
value = value.tolist()
if isinstance(value, formulas.tokens.operand.XlError):
value = None
elif isinstance(value, list):
for x in chain(*value):
if isinstance(x, formulas.tokens.operand.XlError):
value = None
res[field.name] = value
return res
@classmethod
def add_on_change_method(cls, field_name):
"""
Dynamically add 'on_change_<field>' methods.
"""
fn_name = 'on_change_' + field_name
def fn(self):
table = self.get_table()
for field in table.fields_:
if not field.formula:
continue
ast = field.get_ast()
inputs = field.inputs.split()
if not inputs:
continue
inputs = [getattr(self, x) for x in inputs]
try:
value = ast(*inputs)
except schedula.utils.exc.DispatcherError as e:
raise UserError(e.args[0] % e.args[1:])
if isinstance(value, list):
value = str(value)
elif not isinstance(
value, (str, int, float, Decimal, type(None))):
value = value.tolist()
if isinstance(value, formulas.tokens.operand.XlError):
value = None
elif isinstance(value, list):
for x in chain(*value):
if isinstance(x, formulas.tokens.operand.XlError):
value = None
if getattr(self, field.name):
setattr(self, field.name, value)
setattr(cls, fn_name, fn)
@classmethod
def fields_get(cls, fields_names=None, level=0):
Model = Pool().get('ir.model')
res = super().fields_get(fields_names)
table = cls.get_table()
readonly = Transaction().context.get('lims_interface_readonly', False)
encoder = PYSONEncoder()
groups = 0
for field in table.fields_:
res[field.name] = {
'name': field.name,
'string': field.string,
'type': FIELD_TYPE_TRYTON[field.type],
'relation': (field.related_model.model if
field.related_model else None),
'readonly': bool(field.formula or field.readonly or readonly),
'help': field.help,
'domain': field.domain,
'sortable': True,
}
if field.inputs:
res[field.name]['on_change_with'] = field.inputs.split()
cls.add_on_change_with_method(field)
func_name = '%s_%s' % ('on_change_with', field.name)
cls.__rpc__.setdefault(func_name, RPC(instantiate=0))
if field.type == 'reference':
selection = []
for model in Model.search([]):
selection.append((model.model, model.name))
res[field.name]['selection'] = selection
if field.type in ['datetime', 'timestamp']:
res[field.name]['format'] = PYSONEncoder().encode(
'%H:%M:%S.%f')
if field.type in ['float', 'numeric']:
res[field.name]['digits'] = encoder.encode((16, field.digits))
groups = max(groups, field.group or 0)
for i in range(0, groups):
field_description = None
for rep in cls.get_compilation().interface.grouped_repetitions:
if rep.group == i + 1:
field_description = rep.description
field_name = 'group_%s' % (i + 1)
res[field_name] = {
'name': field_name,
'string': field_description or field_name,
'type': 'one2many',
'help': '',
'relation': 'lims.interface.grouped_data',
}
res[field_name]['views'] = {
'tree': GroupedData.fields_view_get(
view_type='tree', group=i + 1)}
res['group_%s' % (i + 1)]['on_change'] = [field_name]
cls.add_on_change_method(field_name)
func_name = '%s_%s' % ('on_change', field_name)
cls.__rpc__.setdefault(func_name, RPC(instantiate=0))
return res
@classmethod
def fields_view_get(cls, view_id=None, view_type='form'):
if Pool().test:
return
table = cls.get_table()
for view in table.views:
if view.type == view_type:
break
assert(view.id)
fields_names = [
'compilation',
'notebook_line',
]
groups = 0
for field in table.fields_:
fields_names.append(field.name)
groups = max(groups, field.group or 0)
for i in range(0, groups):
fields_names.append('group_%s' % (i + 1))
res = {
'type': view.type,
'view_id': view_id,
'field_childs': None,
'arch': view.arch,
'fields': cls.fields_get(fields_names),
'model': cls.__name__,
}
return res
@classmethod
def get_compilation(cls):
Compilation = Pool().get('lims.interface.compilation')
compilation_id = Transaction().context.get(
'lims_interface_compilation')
if compilation_id:
return Compilation(compilation_id)
@classmethod
def get_table(cls):
Table = Pool().get('lims.interface.table')
table = Transaction().context.get('lims_interface_table')
if Pool().test:
# Tryton default tests try to get data using '1' as active_id
# We prevent the tests from failing by returning no table
return
if not table:
compilation = cls.get_compilation()
if compilation:
table = compilation.table
if table:
return Table(table)

View File

@ -325,6 +325,13 @@ class Interface(Workflow, ModelSQL, ModelView):
Field = pool.get('lims.interface.table.field')
GroupedField = pool.get('lims.interface.table.grouped_field')
def get_inputs(formula):
if not formula:
return
parser = formulas.Parser()
ast = parser.ast(formula)[1].compile()
return (' '.join([x for x in ast.inputs])).lower()
for interface in interfaces:
# interface.check_formulas()
# interface.check_icons()
@ -364,6 +371,10 @@ class Interface(Workflow, ModelSQL, ModelView):
column.expression and
column.expression.startswith('=') else
None),
inputs=(get_inputs(column.expression) if
column.expression and
column.expression.startswith('=') else
None),
readonly=column.readonly,
digits=column.digits,
default_width=column.default_width,
@ -392,6 +403,9 @@ class Interface(Workflow, ModelSQL, ModelView):
expression and
expression.startswith('=') else
None),
inputs=(get_inputs(expression) if
expression and
expression.startswith('=') else None),
readonly=column.readonly,
digits=column.digits,
group=column.group,
@ -414,6 +428,9 @@ class Interface(Workflow, ModelSQL, ModelView):
related_model=column.related_model,
formula=(expression if expression and
expression.startswith('=') else None),
inputs=(get_inputs(expression)
if expression and
expression.startswith('=') else None),
readonly=column.readonly,
digits=column.digits,
group=column.group,
@ -445,6 +462,10 @@ class Interface(Workflow, ModelSQL, ModelView):
column.expression and
column.expression.startswith('=') else
None),
inputs=(get_inputs(column.expression) if
column.expression and
column.expression.startswith('=') else
None),
readonly=column.readonly,
digits=column.digits,
related_group=column.related_group,

View File

@ -88,8 +88,7 @@ class TableField(ModelSQL, ModelView):
related_model = fields.Many2One('ir.model', 'Related Model')
domain = fields.Char('Domain Value')
formula = fields.Char('On Change With Formula')
inputs = fields.Function(fields.Char('On Change With Inputs'),
'get_inputs')
inputs = fields.Char('On Change With Inputs')
readonly = fields.Boolean('Read only')
digits = fields.Integer('Digits')
group = fields.Integer('Group')
@ -100,13 +99,6 @@ class TableField(ModelSQL, ModelView):
group_colspan = fields.Integer('Group Colspan')
group_col = fields.Integer('Group Col')
def get_inputs(self, name=None):
if not self.formula:
return
parser = formulas.Parser()
ast = parser.ast(self.formula)[1].compile()
return (' '.join([x for x in ast.inputs])).lower()
def get_ast(self):
parser = formulas.Parser()
ast = parser.ast(self.formula)[1].compile()