updating user interface manager

This commit is contained in:
Adolfo Gómez García 2022-10-31 20:53:30 +01:00
parent 937240a9fc
commit c07c21b6a9
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
6 changed files with 100 additions and 58 deletions

View File

@ -85,7 +85,7 @@ class Images(ModelHandler):
'value': '', 'value': '',
'label': gettext('Image'), 'label': gettext('Image'),
'tooltip': gettext('Image object'), 'tooltip': gettext('Image object'),
'type': gui.InputField.Types.IMAGECHOICE, 'type': gui.InputField.Types.IMAGE_CHOICE,
'order': 100, # At end 'order': 100, # At end
}, },
) )

View File

@ -179,7 +179,7 @@ class MetaPools(ModelHandler):
), ),
'label': gettext('Associated Image'), 'label': gettext('Associated Image'),
'tooltip': gettext('Image assocciated with this service'), 'tooltip': gettext('Image assocciated with this service'),
'type': gui.InputField.Types.IMAGECHOICE, 'type': gui.InputField.Types.IMAGE_CHOICE,
'order': 120, 'order': 120,
'tab': gui.Tab.DISPLAY, 'tab': gui.Tab.DISPLAY,
}, },
@ -196,7 +196,7 @@ class MetaPools(ModelHandler):
'tooltip': gettext( 'tooltip': gettext(
'Pool group for this pool (for pool classify on display)' 'Pool group for this pool (for pool classify on display)'
), ),
'type': gui.InputField.Types.IMAGECHOICE, 'type': gui.InputField.Types.IMAGE_CHOICE,
'order': 121, 'order': 121,
'tab': gui.Tab.DISPLAY, 'tab': gui.Tab.DISPLAY,
}, },

View File

@ -99,7 +99,7 @@ class ServicesPoolGroups(ModelHandler):
), ),
'label': gettext('Associated Image'), 'label': gettext('Associated Image'),
'tooltip': gettext('Image assocciated with this service'), 'tooltip': gettext('Image assocciated with this service'),
'type': gui.InputField.Types.IMAGECHOICE, 'type': gui.InputField.Types.IMAGE_CHOICE,
'order': 102, 'order': 102,
} }
]: ]:

View File

@ -400,7 +400,7 @@ class ServicesPools(ModelHandler):
), ),
'label': gettext('Associated Image'), 'label': gettext('Associated Image'),
'tooltip': gettext('Image assocciated with this service'), 'tooltip': gettext('Image assocciated with this service'),
'type': gui.InputField.Types.IMAGECHOICE, 'type': gui.InputField.Types.IMAGE_CHOICE,
'order': 120, 'order': 120,
'tab': gettext('Display'), 'tab': gettext('Display'),
}, },
@ -417,7 +417,7 @@ class ServicesPools(ModelHandler):
'tooltip': gettext( 'tooltip': gettext(
'Pool group for this pool (for pool classify on display)' 'Pool group for this pool (for pool classify on display)'
), ),
'type': gui.InputField.Types.IMAGECHOICE, 'type': gui.InputField.Types.IMAGE_CHOICE,
'order': 121, 'order': 121,
'tab': gettext('Display'), 'tab': gettext('Display'),
}, },

View File

@ -145,7 +145,7 @@ class CryptoManager(metaclass=singleton.Singleton):
encoded = encryptor.update(toEncode) + encryptor.finalize() encoded = encryptor.update(toEncode) + encryptor.finalize()
if base64: if base64:
return codecs.encode(encoded, 'base64') # Return as binary encoded = codecs.encode(encoded, 'base64') # Return as bytes
return encoded return encoded

View File

@ -39,10 +39,13 @@ import typing
import logging import logging
import enum import enum
from collections import abc from collections import abc
import yaml
from django.utils.translation import get_language, gettext as _, gettext_noop from django.utils.translation import get_language, gettext as _, gettext_noop
from django.conf import settings from django.conf import settings
from numpy import isin from numpy import isin
from regex import B
from yaml import safe_dump
from uds.core.managers import cryptoManager from uds.core.managers import cryptoManager
from uds.core.util.decorators import deprecatedClassValue from uds.core.util.decorators import deprecatedClassValue
@ -62,6 +65,9 @@ PASSWORD_FIELD = b'\005'
FIELD_SEPARATOR = b'\002' FIELD_SEPARATOR = b'\002'
NAME_VALUE_SEPARATOR = b'\003' NAME_VALUE_SEPARATOR = b'\003'
SERIALIZATION_HEADER = b'GUIZ'
SERIALIZATION_VERSION = b'\001'
class gui: class gui:
""" """
@ -316,7 +322,6 @@ class gui:
class Types(enum.Enum): class Types(enum.Enum):
TEXT = 'text' TEXT = 'text'
TEXT_AUTOCOMPLETE = 'text-autocomplete' TEXT_AUTOCOMPLETE = 'text-autocomplete'
# TEXTBOX = 'textbox'
NUMERIC = 'numeric' NUMERIC = 'numeric'
PASSWORD = 'password' # nosec: this is not a password PASSWORD = 'password' # nosec: this is not a password
HIDDEN = 'hidden' HIDDEN = 'hidden'
@ -324,9 +329,10 @@ class gui:
MULTI_CHOICE = 'multichoice' MULTI_CHOICE = 'multichoice'
EDITABLE_LIST = 'editlist' EDITABLE_LIST = 'editlist'
CHECKBOX = 'checkbox' CHECKBOX = 'checkbox'
IMAGECHOICE = 'imgchoice' IMAGE_CHOICE = 'imgchoice'
DATE = 'date' DATE = 'date'
INFO = 'dummy' INFO = 'dummy'
IMAGE = 'image'
def __str__(self): def __str__(self):
return self.value return self.value
@ -338,30 +344,42 @@ class gui:
def __init__(self, **options) -> None: def __init__(self, **options) -> None:
# Added defaultValue as alias for defvalue # Added defaultValue as alias for defvalue
self._data = {}
if 'type' in options:
self.type = options['type'] # set type first
defvalue = options.get( defvalue = options.get(
'defvalue', options.get('defaultValue', options.get('defValue', '')) 'defvalue', options.get('defaultValue', options.get('defValue', ''))
) )
if callable(defvalue): if callable(defvalue):
defvalue = defvalue() defvalue = defvalue()
self._data = { self._data.update(
'length': options.get( {
'length', gui.InputField.DEFAULT_LENTGH 'length': options.get(
), # Length is not used on some kinds of fields, but present in all anyway 'length', gui.InputField.DEFAULT_LENTGH
'required': options.get('required', False), ), # Length is not used on some kinds of fields, but present in all anyway
'label': options.get('label', ''), 'required': options.get('required', False),
'defvalue': str(defvalue), 'label': options.get('label', ''),
'rdonly': options.get( 'defvalue': str(defvalue),
'rdonly', options.get('readOnly', options.get('readonly', False)) 'rdonly': options.get(
), # This property only affects in "modify" operations 'rdonly',
'order': options.get('order', 0), options.get('readOnly', options.get('readonly', False)),
'tooltip': options.get('tooltip', ''), ), # This property only affects in "modify" operations
'type': str(gui.InputField.Types.TEXT), 'order': options.get('order', 0),
'value': options.get('value', ''), 'tooltip': options.get('tooltip', ''),
} 'type': str(gui.InputField.Types.TEXT),
'value': options.get('value', ''),
}
)
if 'tab' in options: if 'tab' in options:
self._data['tab'] = str(options.get('tab')) # Ensure it's a string self._data['tab'] = str(options.get('tab')) # Ensure it's a string
def _type(self, type_: typing.Union[Types, str]) -> None: @property
def type(self) -> 'Types':
return gui.InputField.Types(self._data['type'])
@type.setter
def type(self, type_: Types) -> None:
""" """
Sets the type of this field. Sets the type of this field.
@ -483,8 +501,7 @@ class gui:
""" """
def __init__(self, **options) -> None: def __init__(self, **options) -> None:
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.TEXT)
self._type(gui.InputField.Types.TEXT)
multiline = int(options.get('multiline', 0)) multiline = int(options.get('multiline', 0))
if multiline > 8: if multiline > 8:
multiline = 8 multiline = 8
@ -501,8 +518,8 @@ class gui:
def __init__(self, **options) -> None: def __init__(self, **options) -> None:
super().__init__(**options) super().__init__(**options)
# Change the type # Update parent type
self._type(gui.InputField.Types.TEXT_AUTOCOMPLETE) self.type = gui.InputField.Types.TEXT_AUTOCOMPLETE
# And store values in a list # And store values in a list
self._data['values'] = gui.convertToChoices(options.get('values', [])) self._data['values'] = gui.convertToChoices(options.get('values', []))
@ -534,7 +551,7 @@ class gui:
""" """
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.NUMERIC)
self._data['minValue'] = int( self._data['minValue'] = int(
options.get('minValue', options.get('minvalue', '987654321')) options.get('minValue', options.get('minvalue', '987654321'))
) )
@ -542,8 +559,6 @@ class gui:
options.get('maxValue', options.get('maxvalue', '987654321')) options.get('maxValue', options.get('maxvalue', '987654321'))
) )
self._type(gui.InputField.Types.NUMERIC)
def _setValue(self, value: typing.Any): def _setValue(self, value: typing.Any):
# Internally stores an string # Internally stores an string
super()._setValue(str(value)) super()._setValue(str(value))
@ -601,8 +616,7 @@ class gui:
for v in 'value', 'defvalue': for v in 'value', 'defvalue':
self.processValue(v, options) self.processValue(v, options)
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.DATE)
self._type(gui.InputField.Types.DATE)
def date(self, min: bool = True) -> datetime.date: def date(self, min: bool = True) -> datetime.date:
""" """
@ -666,8 +680,7 @@ class gui:
""" """
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.PASSWORD)
self._type(gui.InputField.Types.PASSWORD)
def cleanStr(self): def cleanStr(self):
return str(self.value).strip() return str(self.value).strip()
@ -705,9 +718,8 @@ class gui:
""" """
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.HIDDEN)
self._isSerializable: bool = options.get('serializable', '') != '' self._isSerializable: bool = options.get('serializable', '') != ''
self._type(gui.InputField.Types.HIDDEN)
def isSerializable(self) -> bool: def isSerializable(self) -> bool:
return self._isSerializable return self._isSerializable
@ -732,8 +744,7 @@ class gui:
""" """
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.CHECKBOX)
self._type(gui.InputField.Types.CHECKBOX)
@staticmethod @staticmethod
def _checkTrue(val: typing.Union[str, bytes, bool]) -> bool: def _checkTrue(val: typing.Union[str, bytes, bool]) -> bool:
@ -852,7 +863,7 @@ class gui:
""" """
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.CHOICE)
self._data['values'] = gui.convertToChoices(options.get('values')) self._data['values'] = gui.convertToChoices(options.get('values'))
if 'fills' in options: if 'fills' in options:
# Save fnc to register as callback # Save fnc to register as callback
@ -861,7 +872,6 @@ class gui:
fills.pop('function') fills.pop('function')
self._data['fills'] = fills self._data['fills'] = fills
gui.callbacks[fills['callbackName']] = fnc gui.callbacks[fills['callbackName']] = fnc
self._type(gui.InputField.Types.CHOICE)
def setValues(self, values: typing.List['gui.ChoiceType']): def setValues(self, values: typing.List['gui.ChoiceType']):
""" """
@ -871,11 +881,9 @@ class gui:
class ImageChoiceField(InputField): class ImageChoiceField(InputField):
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.IMAGE_CHOICE)
self._data['values'] = options.get('values', []) self._data['values'] = options.get('values', [])
self._type(gui.InputField.Types.IMAGECHOICE)
def setValues(self, values: typing.List[typing.Any]): def setValues(self, values: typing.List[typing.Any]):
""" """
Set the values for this choice field Set the values for this choice field
@ -917,12 +925,11 @@ class gui:
""" """
def __init__(self, **options): def __init__(self, **options):
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.MULTI_CHOICE)
if options.get('values') and isinstance(options.get('values'), dict): if options.get('values') and isinstance(options.get('values'), dict):
options['values'] = gui.convertToChoices(options['values']) options['values'] = gui.convertToChoices(options['values'])
self._data['values'] = options.get('values', []) self._data['values'] = options.get('values', [])
self._data['rows'] = options.get('rows', -1) self._data['rows'] = options.get('rows', -1)
self._type(gui.InputField.Types.MULTI_CHOICE)
def setValues(self, values: typing.List[typing.Any]) -> None: def setValues(self, values: typing.List[typing.Any]) -> None:
""" """
@ -961,9 +968,8 @@ class gui:
SEPARATOR = '\001' SEPARATOR = '\001'
def __init__(self, **options) -> None: def __init__(self, **options) -> None:
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.EDITABLE_LIST)
self._data['values'] = gui.convertToList(options.get('values', [])) self._data['values'] = gui.convertToList(options.get('values', []))
self._type(gui.InputField.Types.EDITABLE_LIST)
def _setValue(self, value): def _setValue(self, value):
""" """
@ -978,8 +984,7 @@ class gui:
""" """
def __init__(self, **options) -> None: def __init__(self, **options) -> None:
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.IMAGE)
self._type(gui.InputField.Types.TEXT)
class InfoField(InputField): class InfoField(InputField):
""" """
@ -987,8 +992,7 @@ class gui:
""" """
def __init__(self, **options) -> None: def __init__(self, **options) -> None:
super().__init__(**options) super().__init__(**options, type=gui.InputField.Types.INFO)
self._type(gui.InputField.Types.INFO)
class UserInterfaceType(type): class UserInterfaceType(type):
@ -1030,6 +1034,7 @@ class UserInterface(metaclass=UserInterfaceType):
By default, the values passed to this class constructor are used to fill By default, the values passed to this class constructor are used to fill
the gui form fields values. the gui form fields values.
""" """
# Class variable that will hold the gui fields description # Class variable that will hold the gui fields description
_base_gui: typing.ClassVar[typing.Dict[str, gui.InputField]] _base_gui: typing.ClassVar[typing.Dict[str, gui.InputField]]
@ -1051,7 +1056,9 @@ class UserInterface(metaclass=UserInterfaceType):
self._gui = copy.deepcopy(self._base_gui) self._gui = copy.deepcopy(self._base_gui)
for key, val in self._gui.items(): # And refresh self references to them for key, val in self._gui.items(): # And refresh self references to them
setattr(self, key, val) # val is an InputField instance, so it is a reference to self._gui[key] setattr(
self, key, val
) # val is an InputField instance, so it is a reference to self._gui[key]
if values is not None: if values is not None:
for k, v in self._gui.items(): for k, v in self._gui.items():
@ -1168,6 +1175,43 @@ class UserInterface(metaclass=UserInterfaceType):
return codecs.encode(FIELD_SEPARATOR.join(arr), 'zip') return codecs.encode(FIELD_SEPARATOR.join(arr), 'zip')
# TODO: This method is being created, not to be used yet
def serializeFormTo(
self, serializer: typing.Optional[typing.Callable[[typing.Any], str]] = None
) -> bytes:
"""New form serialization
Returns:
bytes -- serialized form (zipped)
"""
def serialize(value: typing.Any) -> str:
if serializer:
return serializer(value)
return yaml.safe_dump(value)
converters: typing.Mapping[
gui.InfoField.Types, typing.Callable[[gui.InputField], typing.Optional[str]]
] = {
gui.InputField.Types.HIDDEN: (
lambda x: None if not x.isSerializable() else x.value
),
gui.InputField.Types.INFO: lambda x: None,
gui.InputField.Types.EDITABLE_LIST: lambda x: serialize(x.value),
gui.InputField.Types.MULTI_CHOICE: lambda x: serialize(x.value),
gui.InputField.Types.PASSWORD: lambda x: (
cryptoManager().AESCrypt(x.value.encode('utf8'), UDSK, True).decode()
),
gui.InputField.Types.NUMERIC: lambda x: str(int(x.num())),
gui.InputField.Types.CHECKBOX: lambda x: str(x.isTrue()),
}
arr = [(k, v.type, converters[v.type](v)) for k, v in self._gui.items()]
return codecs.encode(
SERIALIZATION_HEADER + SERIALIZATION_VERSION + serialize(arr).encode(),
'zip',
)
def unserializeForm(self, values: bytes) -> None: def unserializeForm(self, values: bytes) -> None:
""" """
This method unserializes the values previously obtained using This method unserializes the values previously obtained using
@ -1220,9 +1264,7 @@ class UserInterface(metaclass=UserInterfaceType):
# Values can contain invalid characters, so we log every single char # Values can contain invalid characters, so we log every single char
# logger.info('Invalid serialization data on {0} {1}'.format(self, values.encode('hex'))) # logger.info('Invalid serialization data on {0} {1}'.format(self, values.encode('hex')))
def guiDescription( def guiDescription(self) -> typing.List[typing.MutableMapping[str, typing.Any]]:
self
) -> typing.List[typing.MutableMapping[str, typing.Any]]:
""" """
This simple method generates the theGui description needed by the This simple method generates the theGui description needed by the
administration client, so it can administration client, so it can