mirror of
https://github.com/Kalenis/kalenislims.git
synced 2023-12-14 07:13:04 +01:00
lims_interface: add support for Selection fields
This commit is contained in:
parent
fab66670c9
commit
d4a0724f78
6 changed files with 189 additions and 71 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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"/>
|
||||
|
|
Loading…
Reference in a new issue