lims_interface: add support for Selection fields

This commit is contained in:
Adrián Bernardi 2021-02-05 16:57:27 -03:00
parent fab66670c9
commit d4a0724f78
6 changed files with 189 additions and 71 deletions

View File

@ -70,6 +70,10 @@ class Adapter:
obj = fields.Many2One(field.related_model.model, field.string)
elif field.type in ('binary', 'icon'):
obj = fields.Binary(field.string)
elif field.type == 'selection':
selection = [tuple(v.split(':', 1))
for v in field.selection.splitlines() if v]
obj = fields.Selection(selection, field.string)
obj.name = field.name
res[field.name] = obj
groups = max(groups, field.group or 0)
@ -137,6 +141,10 @@ class GroupedAdapter:
obj = fields.Many2One(field.related_model.model, field.string)
elif field.type in ('binary', 'icon'):
obj = fields.Binary(field.string)
elif field.type == 'selection':
selection = [tuple(v.split(':', 1))
for v in field.selection.splitlines() if v]
obj = fields.Selection(selection, field.string)
obj.name = field.name
res[field.name] = obj
obj = fields.Integer('ID')
@ -365,13 +373,30 @@ class Data(ModelSQL, ModelView):
'name': field.name,
'string': field.string,
'type': FIELD_TYPE_TRYTON[field.type],
'relation': (field.related_model.model if
field.related_model else None),
'states': encoder.encode(states),
'help': field.help,
'domain': field.domain,
'states': encoder.encode(states),
'sortable': True,
}
if field.type == 'many2one':
res[field.name]['relation'] = (field.related_model.model if
field.related_model else None)
if field.type == 'selection':
selection = [tuple(v.split(':', 1))
for v in field.selection.splitlines() if v]
res[field.name]['selection'] = selection
res[field.name]['selection_change_with'] = []
res[field.name]['sort'] = False
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 ['date', 'time', '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))
if field.inputs:
inputs = []
for input_ in field.inputs.split():
@ -387,17 +412,6 @@ class Data(ModelSQL, ModelView):
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))
for i in range(0, groups):
field_description = None
for rep in interface.grouped_repetitions:
@ -882,13 +896,30 @@ class GroupedData(ModelView):
'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(readonly or field.formula or field.readonly),
'help': field.help,
'domain': field.domain,
'states': '{}',
}
if field.type == 'many2one':
res[field.name]['relation'] = (field.related_model.model if
field.related_model else None)
if field.type == 'selection':
selection = [tuple(v.split(':', 1))
for v in field.selection.splitlines() if v]
res[field.name]['selection'] = selection
res[field.name]['selection_change_with'] = []
res[field.name]['sort'] = False
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 ['date', 'time', '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))
if field.inputs:
res[field.name]['on_change_with'] = field.inputs.split() + [
'data']
@ -896,28 +927,17 @@ class GroupedData(ModelView):
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))
res['data'] = {
'name': 'data',
'string': 'Data',
'type': 'many2one',
'readonly': True,
'help': '',
'states': '{}',
'relation': 'lims.interface.data',
'relation_field': 'group_%s' % group,
'relation_fields': (Data.fields_get(level=level - 1)
if level > 0 else []),
'readonly': True,
'help': '',
'states': '{}',
}
return res

View File

@ -11,6 +11,7 @@ import io
import csv
import hashlib
import tempfile
import json
from openpyxl import load_workbook
from decimal import Decimal
from datetime import datetime, date, time
@ -24,7 +25,7 @@ from trytond.model import (Workflow, ModelView, ModelSQL, fields,
from trytond.wizard import (Wizard, StateTransition, StateView, StateAction,
Button)
from trytond.pool import Pool
from trytond.pyson import PYSONEncoder, Eval, Bool, Not, And, Or
from trytond.pyson import PYSONDecoder, PYSONEncoder, Eval, Bool, Not, And, Or
from trytond.transaction import Transaction
from trytond.i18n import gettext
from trytond.exceptions import UserError
@ -59,6 +60,8 @@ FIELD_TYPES = [
('binary', 'binary', 'File', 'fields.Binary', 'BLOB', bytes, bytearray),
('reference', 'reference', 'Reference', 'fields.Reference', 'VARCHAR', str,
None),
('selection', 'selection', 'Selection', 'fields.Selection', 'VARCHAR', str,
None),
]
FIELD_TYPE_SELECTION = [(x[0], x[2]) for x in FIELD_TYPES]
@ -377,6 +380,7 @@ class Interface(Workflow, ModelSQL, ModelView):
related_line_field=
column.related_line_field,
related_model=column.related_model,
selection=column.selection,
formula=(column.expression if
column.expression and
column.expression.startswith('=') else
@ -409,6 +413,7 @@ class Interface(Workflow, ModelSQL, ModelView):
help=expression,
domain=column.domain,
related_model=column.related_model,
selection=column.selection,
formula=(expression if
expression and
expression.startswith('=') else
@ -436,6 +441,7 @@ class Interface(Workflow, ModelSQL, ModelView):
transfer_field=column.transfer_field,
related_line_field=column.related_line_field,
related_model=column.related_model,
selection=column.selection,
formula=(expression if expression and
expression.startswith('=') else None),
inputs=(get_inputs(expression)
@ -468,6 +474,7 @@ class Interface(Workflow, ModelSQL, ModelView):
transfer_field=column.transfer_field,
related_line_field=column.related_line_field,
related_model=column.related_model,
selection=column.selection,
formula=(column.expression if
column.expression and
column.expression.startswith('=') else
@ -818,6 +825,7 @@ class Column(sequence_ordered(), ModelSQL, ModelView):
('image', 'Image'),
('binary', 'File'),
('reference', 'Reference'),
('selection', 'Selection'),
], 'Field Type', states=_states, depends=_depends)
related_model = fields.Many2One('ir.model', 'Related Model',
states={
@ -826,6 +834,14 @@ class Column(sequence_ordered(), ModelSQL, ModelView):
'readonly': _states['readonly'],
},
depends=['type_', 'interface_state'])
selection = fields.Text('Selection',
states={
'required': Eval('type_') == 'selection',
'invisible': Eval('type_') != 'selection',
'readonly': _states['readonly'],
},
depends=['type_', 'interface_state'],
help='A couple of key and label separated by ":" per line.')
default_value = fields.Char('Default value',
states={'readonly': _states['readonly'] | Bool(Eval('expression'))},
depends=['expression', 'interface_state'])
@ -953,6 +969,8 @@ class Column(sequence_ordered(), ModelSQL, ModelView):
for column in columns:
column.check_alias()
column.check_default_value()
column.check_domain()
column.check_selection()
def check_alias(self):
for symbol in self.alias:
@ -961,39 +979,70 @@ class Column(sequence_ordered(), ModelSQL, ModelView):
symbol=symbol, name=self.name))
def check_default_value(self):
if self.default_value:
if self.type_ in [
'datetime', 'time', 'timestamp', 'timedelta',
'icon', 'image', 'binary', 'reference',
]:
if not self.default_value:
return
if self.type_ in [
'datetime', 'time', 'timestamp', 'timedelta',
'icon', 'image', 'binary', 'reference',
]:
raise UserError(gettext(
'lims_interface.invalid_default_value_type',
name=self.name))
if self.type_ == 'boolean':
try:
int(self.default_value)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_default_value_type',
'lims_interface.invalid_default_value_boolean',
name=self.name))
if self.type_ == 'boolean':
try:
int(self.default_value)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_default_value_boolean',
name=self.name))
elif self.type_ == 'date':
try:
str2date(self.default_value, self.interface.language)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_default_value_date',
name=self.name))
elif self.type_ == 'many2one':
get_model_resource(
self.related_model.model, self.default_value, self.name)
else:
ftype = FIELD_TYPE_PYTHON[self.type_]
try:
ftype(self.default_value)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_default_value',
value=self.default_value, name=self.name))
elif self.type_ == 'date':
try:
str2date(self.default_value, self.interface.language)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_default_value_date',
name=self.name))
elif self.type_ == 'many2one':
get_model_resource(
self.related_model.model, self.default_value, self.name)
else:
ftype = FIELD_TYPE_PYTHON[self.type_]
try:
ftype(self.default_value)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_default_value',
value=self.default_value, name=self.name))
def check_domain(self):
if not self.domain:
return
try:
value = PYSONDecoder().decode(self.domain)
except Exception:
raise UserError(gettext(
'lims_interface.invalid_domain',
name=self.name))
if not isinstance(value, list):
raise UserError(gettext(
'lims_interface.invalid_domain',
name=self.name))
def check_selection(self):
if self.type_ != 'selection':
return
try:
dict(json.loads(self.get_selection_json()))
except Exception:
raise UserError(gettext(
'lims_interface.invalid_selection',
name=self.name))
def get_selection_json(self, name=None):
db_selection = self.selection or ''
selection = [[w.strip() for w in v.split(':', 1)]
for v in db_selection.splitlines() if v]
return json.dumps(selection, separators=(',', ':'))
def formula_error(self):
if not self.expression:
@ -1115,15 +1164,16 @@ class ViewColumn(sequence_ordered(), ModelSQL, ModelView):
c.check_analysis_specific()
def check_analysis_specific(self):
if self.analysis_specific:
if self.search([
('view', '=', self.view.id),
('analysis_specific', '=', True),
('id', '!=', self.id),
]):
raise UserError(gettext(
'lims_interface.msg_analysis_specific',
view=self.view.name))
if not self.analysis_specific:
return
if self.search([
('view', '=', self.view.id),
('analysis_specific', '=', True),
('id', '!=', self.id),
]):
raise UserError(gettext(
'lims_interface.msg_analysis_specific',
view=self.view.name))
class CopyInterfaceColumnStart(ModelView):
@ -1186,6 +1236,7 @@ class CopyInterfaceColumn(Wizard):
'type_': origin.type_,
'related_model': (origin.related_model and
origin.related_model or None),
'selection': origin.selection,
'default_value': origin.default_value,
'readonly': origin.readonly,
'transfer_field': origin.transfer_field,

View File

@ -158,6 +158,10 @@ msgctxt "field:lims.interface.column,related_model:"
msgid "Related Model"
msgstr "Modelo relacionado"
msgctxt "field:lims.interface.column,selection:"
msgid "Selection"
msgstr "Selección"
msgctxt "field:lims.interface.column,singleton:"
msgid "Is a singleton value"
msgstr "Es un valor único"
@ -450,6 +454,10 @@ msgctxt "field:lims.interface.table.field,related_model:"
msgid "Related Model"
msgstr "Modelo relacionado"
msgctxt "field:lims.interface.table.field,selection:"
msgid "Selection"
msgstr "Selección"
msgctxt "field:lims.interface.table.field,string:"
msgid "String"
msgstr "Etiqueta"
@ -506,6 +514,10 @@ msgctxt "field:lims.interface.table.grouped_field,related_model:"
msgid "Related Model"
msgstr "Modelo relacionado"
msgctxt "field:lims.interface.table.grouped_field,selection:"
msgid "Selection"
msgstr "Selección"
msgctxt "field:lims.interface.table.grouped_field,string:"
msgid "String"
msgstr "Etiqueta"
@ -642,6 +654,10 @@ msgstr ""
"En columnas agrupadas el sufijo _XX será reemplazado por la repetición "
"correspondiente"
msgctxt "help:lims.interface.column,selection:"
msgid "A couple of key and label separated by \":\" per line."
msgstr "Una pareja de claves y valores separados por \":\" en cada línea."
msgctxt "help:lims.interface.column,singleton:"
msgid "Is a fixed value (column:row) in source file"
msgstr "Es un campo fijo (columna:fila) en el archivo de origen"
@ -755,6 +771,10 @@ msgctxt "model:ir.message,text:invalid_default_value_type"
msgid "The field type in \"%(name)s\" is not valid for default values."
msgstr "El Tipo de campo en \"%(name)s\" no permite valores por defecto."
msgctxt "model:ir.message,text:invalid_domain"
msgid "Invalid domain in column \"%(name)s\"."
msgstr "El dominio en la columna \"%(name)s\" es inválido."
msgctxt "model:ir.message,text:invalid_interface_charset"
msgid ""
"The charset of the file does not match the one defined in the interface."
@ -762,6 +782,10 @@ msgstr ""
"El conjunto de caracteres del archivo no coincide con el definido en la "
"interfaz."
msgctxt "model:ir.message,text:invalid_selection"
msgid "Invalid selection in column \"%(name)s\"."
msgstr "Las opciones de selección en la columna \"%(name)s\" son inválidas."
msgctxt "model:ir.message,text:msg_analysis_specific"
msgid "There cannot be more than one column per analysis in view \"%(view)s\""
msgstr "No puede haber más de una columna por análisis en la vista \"%(view)s\""
@ -1047,6 +1071,10 @@ msgctxt "selection:lims.interface.column,type_:"
msgid "Reference"
msgstr "Referencia"
msgctxt "selection:lims.interface.column,type_:"
msgid "Selection"
msgstr "Selección"
msgctxt "selection:lims.interface.column,type_:"
msgid "Text (multi-line)"
msgstr "Texto (multi-línea)"
@ -1131,6 +1159,10 @@ msgctxt "selection:lims.interface.table.field,type:"
msgid "Reference"
msgstr "Referencia"
msgctxt "selection:lims.interface.table.field,type:"
msgid "Selection"
msgstr "Selección"
msgctxt "selection:lims.interface.table.field,type:"
msgid "Text (multi-line)"
msgstr "Texto (multi-línea)"
@ -1195,6 +1227,10 @@ msgctxt "selection:lims.interface.table.grouped_field,type:"
msgid "Reference"
msgstr "Referencia"
msgctxt "selection:lims.interface.table.grouped_field,type:"
msgid "Selection"
msgstr "Selección"
msgctxt "selection:lims.interface.table.grouped_field,type:"
msgid "Text (multi-line)"
msgstr "Texto (multi-línea)"

View File

@ -22,6 +22,12 @@
<record model="ir.message" id="invalid_default_value_many2one">
<field name="text">The resource set as default value in "%(name)s" is not valid or it does not exist.</field>
</record>
<record model="ir.message" id="invalid_domain">
<field name="text">Invalid domain in column "%(name)s".</field>
</record>
<record model="ir.message" id="invalid_selection">
<field name="text">Invalid selection in column "%(name)s".</field>
</record>
<record model="ir.message" id="duplicated_origin_file">
<field name="text">File "%(file_name)s" already exists as origin.</field>
</record>

View File

@ -86,6 +86,7 @@ class TableField(ModelSQL, ModelView):
transfer_field = fields.Boolean('Is a transfer field')
related_line_field = fields.Many2One('ir.model.field', 'Related Field')
related_model = fields.Many2One('ir.model', 'Related Model')
selection = fields.Text('Selection')
domain = fields.Char('Domain Value')
formula = fields.Char('On Change With Formula')
inputs = fields.Char('On Change With Inputs')
@ -117,6 +118,7 @@ class TableGroupedField(ModelSQL, ModelView):
'Field Type', required=False)
help = fields.Text('Help')
related_model = fields.Many2One('ir.model', 'Related Model')
selection = fields.Text('Selection')
domain = fields.Char('Domain Value')
formula = fields.Char('On Change With Formula')
inputs = fields.Function(fields.Char('On Change With Inputs'),

View File

@ -16,6 +16,9 @@
<label name="digits"/>
<field name="digits" xexpand="0"/>
</group>
<group name="selection" colspan="4" col="1">
<field name="selection"/>
</group>
<group id="expression" colspan="4" col="3">
<label name="expression"/>
<field name="expression" xexpand="1"/>