tiramisu-api-python/tiramisu_json_api/api.py

937 lines
35 KiB
Python
Raw Normal View History

2018-12-24 09:28:44 +01:00
from typing import Optional, Dict, List, Any
2019-02-28 22:12:46 +01:00
from copy import copy
2018-12-24 09:28:44 +01:00
import warnings
import re
from .error import APIError, ValueWarning, ValueOptionError, ValueErrorWarning, PropertiesOptionError
from .setting import undefined
from .i18n import _
2019-01-10 09:54:22 +01:00
2018-12-24 09:28:44 +01:00
2019-04-03 19:48:45 +02:00
TIRAMISU_JSON_VERSION = '1.0'
2019-01-16 16:38:32 +01:00
TYPE = {'boolean': bool,
2019-03-11 15:59:29 +01:00
'integer': int,
2019-01-19 10:54:18 +01:00
'string': str,
'password': str,
'domain': str}
2019-01-16 16:38:32 +01:00
2018-12-24 09:28:44 +01:00
class Option:
2019-01-10 09:54:22 +01:00
# fake Option (IntOption, StrOption, ...)
2019-01-19 10:54:18 +01:00
# only usefull for warnings
2018-12-24 09:28:44 +01:00
def __init__(self,
path):
self.path = path
def __call__(self):
2019-02-05 07:01:22 +01:00
# suppose to be a weakref
2018-12-24 09:28:44 +01:00
return self
def impl_getpath(self):
return self.path
class TiramisuOptionOption:
2019-01-10 09:54:22 +01:00
# config.option(path).option
2018-12-24 09:28:44 +01:00
def __init__(self,
2019-02-08 11:11:06 +01:00
config: 'Config',
2018-12-24 09:28:44 +01:00
path: str,
2019-01-10 09:54:22 +01:00
schema: Dict,
2019-01-18 14:15:05 +01:00
model: Dict,
form: Dict) -> None:
2019-02-08 11:11:06 +01:00
self.config = config
2018-12-24 09:28:44 +01:00
self._path = path
self.schema = schema
2019-01-10 09:54:22 +01:00
self.model = model
2019-01-18 14:15:05 +01:00
self.form = form
2018-12-24 09:28:44 +01:00
def doc(self):
return self.schema['title']
def path(self):
return self._path
2019-01-18 14:15:05 +01:00
def name(self,
follow_symlink: bool=False) -> str:
if not follow_symlink or \
self.isoptiondescription() or \
not self.issymlinkoption():
path = self._path
else:
path = self.schema['opt_path']
return path.rsplit('.', 1)[-1]
2019-01-10 09:54:22 +01:00
2018-12-24 09:28:44 +01:00
def isoptiondescription(self):
return self.schema['type'] in ['object', 'array']
def isleadership(self):
2018-12-24 09:28:44 +01:00
return self.schema['type'] == 'array'
def isleader(self):
if '.' in self._path:
parent_schema = self.config.get_schema(self._path.rsplit('.', 1)[0])
2019-03-16 22:51:39 +01:00
if parent_schema['type'] == 'array':
leader = next(iter(parent_schema['properties'].keys()))
return leader == self._path
return False
def isfollower(self):
return self.config.isfollower(self._path)
2018-12-24 09:28:44 +01:00
def issymlinkoption(self) -> bool:
2019-01-18 14:15:05 +01:00
return self.schema['type'] == 'symlink'
2018-12-24 09:28:44 +01:00
def ismulti(self) -> bool:
return self.schema.get('isMulti', False)
def type(self) -> str:
if self.isleadership():
return 'leadership'
2018-12-24 09:28:44 +01:00
if self.isoptiondescription():
return 'optiondescription'
return self.schema['type']
2018-12-24 09:28:44 +01:00
2019-01-10 09:54:22 +01:00
def properties(self) -> List[str]:
2019-01-16 17:45:44 +01:00
model = self.model.get(self._path, {})
return self.config.get_properties(self.model, self._path, None)
2019-01-10 09:54:22 +01:00
def requires(self) -> None:
# FIXME
return None
2018-12-24 09:28:44 +01:00
class TiramisuOptionProperty:
2019-01-10 09:54:22 +01:00
# config.option(path).property
2018-12-24 09:28:44 +01:00
def __init__(self,
2019-02-08 11:30:29 +01:00
config: 'Config',
2019-02-05 07:01:22 +01:00
path: str,
index: Optional[int],
2018-12-24 09:28:44 +01:00
model: Dict) -> None:
2019-02-08 11:26:59 +01:00
self.config = config
2019-02-05 07:01:22 +01:00
self.path = path
self.index = index
2018-12-24 09:28:44 +01:00
self.model = model
2019-01-10 09:54:22 +01:00
def get(self, only_raises=False):
if not only_raises:
props = self.config.get_properties(self.model, self.path, self.index, only_raises)
2019-01-10 09:54:22 +01:00
else:
2019-02-16 20:23:50 +01:00
props = []
2019-04-03 19:42:59 +02:00
if self.config.is_hidden(self.path,
self.index):
2019-02-16 20:23:50 +01:00
props.append('hidden')
return props
2018-12-24 09:28:44 +01:00
2019-01-10 09:54:22 +01:00
class _Value:
def _dict_walk(self,
ret: Dict,
schema: Dict,
root: str,
fullpath: bool,
withwarning: bool) -> None:
leadership_len = None
2019-01-10 09:54:22 +01:00
for key, option in schema['properties'].items():
2019-04-03 19:42:59 +02:00
if self.config.is_hidden(key, None) is False:
2019-02-28 22:12:46 +01:00
if option['type'] in ['object', 'array']:
# optiondescription or leadership
2019-01-10 09:54:22 +01:00
self._dict_walk(ret,
option,
root,
fullpath,
withwarning)
elif schema.get('type') == 'array' and leadership_len is not None:
# followers
values = []
for index in range(leadership_len):
value = self.config.get_value(key,
index)
self._display_warnings(key, value, option['type'], option['name'], withwarning)
values.append(value)
ret[key] = values
2019-01-10 09:54:22 +01:00
else:
value = self.config.get_value(key)
2019-01-10 09:54:22 +01:00
self._display_warnings(key, value, option['type'], option['name'], withwarning)
ret[key] = value
if schema.get('type') == 'array':
leadership_len = len(value)
elif schema.get('type') == 'array' and leadership_len is None:
# if leader is hidden, followers are hidden too
2019-02-28 22:12:46 +01:00
break
2019-01-10 09:54:22 +01:00
def dict(self,
fullpath: bool=False,
withwarning: bool=False):
ret = {}
self._dict_walk(ret,
self.schema,
self.path,
fullpath,
withwarning)
return ret
2018-12-24 09:28:44 +01:00
2019-01-10 09:54:22 +01:00
def _display_warnings(self, path, value, type, name, withwarning=True):
2019-02-05 07:01:22 +01:00
for err in self.model.get(path, {}).get('error', []):
warnings.warn_explicit(ValueErrorWarning(value,
type,
Option(path),
'{0}'.format(err)),
2019-02-05 07:01:22 +01:00
ValueErrorWarning,
self.__class__.__name__, 0)
2018-12-24 09:28:44 +01:00
2019-01-10 09:54:22 +01:00
if withwarning and self.model.get(path, {}).get('warnings'):
2018-12-24 09:28:44 +01:00
for warn in self.model.get(path, {}).get('warnings'):
warnings.warn_explicit(ValueErrorWarning(value,
type,
Option(path),
'{0}'.format(err)),
ValueErrorWarning,
2018-12-24 09:28:44 +01:00
self.__class__.__name__, 0)
2019-01-10 09:54:22 +01:00
2019-01-16 09:29:01 +01:00
class TiramisuOptionOwner:
# config.option(path).owner
def __init__(self,
config: 'Config',
schema: Dict,
model: List[Dict],
form: List[Dict],
temp: List[Dict],
path: str,
index: int) -> None:
self.config = config
self.schema = schema
self.model = model
self.form = form
self.temp = temp
self.path = path
self.index = index
def isdefault(self) -> Any:
return self.config.get_owner(self.path, self.index) == 'default'
2019-01-16 09:29:01 +01:00
2019-02-16 20:23:50 +01:00
def get(self) -> str:
return self.config.get_owner(self.path, self.index)
2019-02-16 20:23:50 +01:00
2019-01-16 09:29:01 +01:00
2019-01-10 09:54:22 +01:00
class TiramisuOptionValue(_Value):
# config.option(path).value
def __init__(self,
config: 'Config',
schema: Dict,
model: List[Dict],
form: List[Dict],
temp: List[Dict],
path: str,
index: int) -> None:
self.config = config
self.schema = schema
self.model = model
self.form = form
self.temp = temp
self.path = path
self.index = index
2018-12-24 09:28:44 +01:00
def get(self) -> Any:
if self.config.isfollower(self.path):
if self.index is None:
raise APIError(_('index must be set with the follower option "{}"').format(self.path))
value = self.config.get_value(self.path, self.index)
self._display_warnings(self.path, value, self.schema['type'], self.schema['name'])
return value
if self.index is not None:
2019-03-28 07:53:29 +01:00
raise APIError(_('index must only be set with a follower option, not for "{}"').format(self.path))
2019-02-14 21:25:00 +01:00
value = self.config.get_value(self.path)
2018-12-24 09:28:44 +01:00
self._display_warnings(self.path, value, self.schema['type'], self.schema['name'])
return value
def list(self):
return self.schema['enum']
2019-01-19 10:54:18 +01:00
def _validate(self, type_, value):
if value in [None, undefined]:
return
if type_ == 'choice':
2019-01-16 16:38:32 +01:00
if value not in self.schema['enum']:
raise Exception('value {} is not in {}'.format(value, self.schema['enum']))
2019-01-19 10:54:18 +01:00
elif not isinstance(value, TYPE[type_]):
raise Exception('value {} is not a valid {} '.format(value, type_))
def set(self, value):
type_ = self.schema['type']
2019-03-16 22:51:39 +01:00
remote = self.form.get(self.path, {}).get('remote', False)
if self.index is None and self.schema.get('isMulti', False):
2019-01-19 10:54:18 +01:00
if not isinstance(value, list):
raise Exception('value must be a list')
for val in value:
self._validate(type_, val)
else:
self._validate(type_, value)
2018-12-24 09:28:44 +01:00
self.config.modify_value(self.path,
self.index,
value,
2019-02-05 07:01:22 +01:00
remote)
2019-01-19 10:54:18 +01:00
self._display_warnings(self.path, value, type_, self.schema['name'])
2018-12-24 09:28:44 +01:00
def reset(self):
2019-02-05 07:01:22 +01:00
remote = self.form.get(self.path, {}).get('remote', False)
2018-12-24 09:28:44 +01:00
self.config.delete_value(self.path,
self.index,
2019-02-05 07:01:22 +01:00
remote)
2018-12-24 09:28:44 +01:00
2019-03-11 15:57:32 +01:00
def default(self):
return self.schema.get('value')
2018-12-24 09:28:44 +01:00
2019-01-10 09:54:22 +01:00
class _Option:
2019-02-07 16:23:41 +01:00
def list(self,
2019-01-10 09:54:22 +01:00
type='option'):
if type not in ['all', 'option']:
raise NotImplementedError()
for path, schema in self.schema['properties'].items():
2019-04-03 19:42:59 +02:00
if type == 'all' or schema['type'] not in ['object', 'array'] and self.config.is_hidden(path, None):
if schema['type'] in ['object', 'array']:
yield TiramisuOptionDescription(self.config,
schema,
self.model,
self.form,
self.temp,
path)
else:
yield TiramisuOption(self.config,
schema,
self.model,
self.form,
self.temp,
path,
self.index)
2019-01-10 09:54:22 +01:00
class TiramisuOptionDescription(_Option):
# config.option(path) (with path == OptionDescription)
def __init__(self,
config: 'Config',
schema: Dict,
model: List[Dict],
form: List[Dict],
temp: List[Dict],
path: str) -> None:
self.config = config
self.schema = schema
self.model = model
self.form = form
self.temp = temp
self.path = path
self.index = None
def __getattr__(self,
subfunc: str) -> Any:
if subfunc == 'option':
2019-02-08 11:11:06 +01:00
return TiramisuOptionOption(self.config,
self.path,
2019-01-10 09:54:22 +01:00
self.schema,
2019-01-18 14:15:05 +01:00
self.model,
self.form)
2019-01-10 09:54:22 +01:00
if subfunc == 'property':
2019-02-08 11:30:29 +01:00
return TiramisuOptionProperty(self.config,
self.path,
2019-02-05 07:01:22 +01:00
self.model.get(self.path, {}))
2019-01-19 10:54:18 +01:00
if subfunc == 'value':
return TiramisuOptionValue(self.config,
self.schema,
self.model,
self.form,
self.temp,
self.path,
self.index)
2019-01-10 09:54:22 +01:00
raise APIError(_('please specify a valid sub function ({})').format(subfunc))
def group_type(self):
2019-04-03 19:42:59 +02:00
if self.config.is_hidden(self.path, None):
2019-01-10 09:54:22 +01:00
# FIXME
return 'default'
raise PropertiesOptionError(None, None, None, opt_type='optiondescription')
2018-12-24 09:28:44 +01:00
class TiramisuOption:
2019-01-10 09:54:22 +01:00
# config.option(path) (with path == Option)
2018-12-24 09:28:44 +01:00
def __init__(self,
config: 'Config',
schema: Dict,
model: List[Dict],
form: List[Dict],
temp: List[Dict],
path: str,
index: Optional[int]) -> None:
self.config = config
self.schema = schema
self.model = model
self.form = form
self.temp = temp
self.path = path
self.index = index
def __getattr__(self,
subfunc: str) -> Any:
if subfunc == 'option':
if self.index != None:
raise NotImplementedError()
2019-02-08 11:11:06 +01:00
return TiramisuOptionOption(self.config,
self.path,
2019-01-10 09:54:22 +01:00
self.schema,
2019-01-18 14:15:05 +01:00
self.model,
self.form)
2018-12-24 09:28:44 +01:00
if subfunc == 'value':
return TiramisuOptionValue(self.config,
self.schema,
self.model,
self.form,
self.temp,
self.path,
self.index)
2019-01-16 09:29:01 +01:00
if subfunc == 'owner':
return TiramisuOptionOwner(self.config,
self.schema,
self.model,
self.form,
self.temp,
self.path,
self.index)
2018-12-24 09:28:44 +01:00
if subfunc == 'property':
2019-02-08 11:30:29 +01:00
return TiramisuOptionProperty(self.config,
self.path,
self.index,
2019-02-05 07:01:22 +01:00
self.model.get(self.path, {}))
2018-12-24 09:28:44 +01:00
raise APIError(_('please specify a valid sub function ({})').format(subfunc))
class TiramisuContextProperty:
2019-01-10 09:54:22 +01:00
# config.property
2018-12-24 09:28:44 +01:00
# def __init__(self,
# json):
# self.json = json
def get(self):
# FIXME ?
return ['demoting_error_warning']
2019-01-10 09:54:22 +01:00
class ContextOption(_Option):
# config.option
def __init__(self,
config: 'Config',
model: Dict,
form: Dict,
schema: Dict,
temp: Dict) -> None:
self.config = config
self.model = model
self.form = form
2019-02-05 07:01:22 +01:00
self.schema = {'properties': schema}
2019-01-10 09:54:22 +01:00
self.temp = temp
2019-02-08 10:51:38 +01:00
self.index = None
2019-01-10 09:54:22 +01:00
def __call__(self,
path: str,
index: Optional[int]=None) -> TiramisuOption:
schema = self.config.get_schema(path)
if schema['type'] in ['object', 'array']:
return TiramisuOptionDescription(self.config,
schema,
self.model,
self.form,
self.temp,
path)
2019-01-10 09:54:22 +01:00
return TiramisuOption(self.config,
schema,
2019-01-10 09:54:22 +01:00
self.model,
self.form,
self.temp,
path,
index)
class ContextValue(_Value):
# config.value
def __init__(self,
config: 'Config',
model: Dict,
form: Dict,
schema: Dict,
temp: Dict) -> None:
self.config = config
self.model = model
self.form = form
first = next(iter(schema.keys()))
self.path = first.rsplit('.', 1)[0]
2019-01-16 17:45:44 +01:00
self.schema = {'properties': schema}
2019-01-10 09:54:22 +01:00
self.temp = temp
def __call__(self) -> TiramisuOptionValue:
return TiramisuOptionValue(self.config,
self.schema,
self.model,
self.form,
self.temp,
path,
index)
2019-01-16 17:45:44 +01:00
def mandatory(self):
for key, value in self.dict().items():
if self.model.get(key, {}).get('required') and \
value is None or \
(self.schema.get('isMulti') and (None in value or '' in value)):
yield key
2019-01-10 09:54:22 +01:00
2018-12-24 09:28:44 +01:00
class Config:
2019-01-10 09:54:22 +01:00
# config
2018-12-24 09:28:44 +01:00
def __init__(self,
json):
2019-04-03 19:48:45 +02:00
if json.get('version') != TIRAMISU_JSON_VERSION:
raise Exception('incompatible tiramisu-json format version (got {}, expected {})'.format(json.get('version', '0.0'), TIRAMISU_JSON_VERSION))
2019-04-03 19:42:59 +02:00
self.model = json['model']
self.form = json['form']
2018-12-24 09:28:44 +01:00
self.form = {}
2019-04-03 19:42:59 +02:00
# support pattern
for key, option in json['form'].items():
if key != 'null':
2018-12-24 09:28:44 +01:00
if 'pattern' in option:
option['pattern'] = re.compile(option['pattern'])
self.temp = {}
self.schema = json['schema']
self.updates = []
2019-01-10 09:54:22 +01:00
first_path = next(iter(self.schema.keys()))
if '.' in first_path:
self.root = first_path.rsplit('.', 1)[0]
else:
self.root = ''
2018-12-24 09:28:44 +01:00
def __getattr__(self,
subfunc: str) -> Any:
if subfunc == 'property':
return TiramisuContextProperty()
2019-01-10 09:54:22 +01:00
if subfunc == 'option':
return ContextOption(self,
self.model,
self.form,
self.schema,
self.temp)
if subfunc == 'value':
return ContextValue(self,
self.model,
self.form,
self.schema,
self.temp)
2018-12-24 09:28:44 +01:00
raise APIError(_('please specify a valid sub function ({})').format(subfunc))
def add_value(self,
path: str,
index: Optional[int],
value: Any,
remote: bool) -> None:
self.updates_value('add',
path,
index,
value,
remote,
None)
def modify_value(self,
path: str,
index: Optional[int],
value: Any,
remote: bool) -> None:
schema = self.get_schema(path)
if value and isinstance(value, list) and value[-1] is undefined:
new_value = schema.get('defaultvalue')
if new_value is None:
len_value = len(value)
2019-02-05 07:01:22 +01:00
schema_value = schema.get('value', [])
if len(schema_value) >= len_value:
new_value = schema_value[len_value - 1]
2018-12-24 09:28:44 +01:00
value[-1] = new_value
2019-02-07 16:23:41 +01:00
2018-12-24 09:28:44 +01:00
self.updates_value('modify',
path,
index,
value,
remote,
None)
def delete_value(self,
path: str,
index: Optional[int],
remote: bool) -> None:
self.updates_value('delete',
path,
index,
2019-03-16 22:51:39 +01:00
None,
2018-12-24 09:28:44 +01:00
remote,
None)
2019-02-16 20:23:50 +01:00
def get_properties(self,
model,
path,
index,
2019-02-16 20:23:50 +01:00
only_raises=True):
props = model.get('properties', [])[:]
if model.get('required'):
if self.get_schema(path).get('isMulti', False):
props.append('empty')
else:
props.append('mandatory')
if model.get('needs_len'):
props.append('mandatory')
if model.get('readOnly'):
props.append('frozen')
2019-04-03 19:42:59 +02:00
if only_raises and self.is_hidden(path,
index):
2019-02-16 20:23:50 +01:00
props.append('hidden')
if self.form.get(path, {}).get('clearable'):
props.append('clearable')
return props
2018-12-24 09:28:44 +01:00
def get_schema(self,
path):
2019-01-10 09:54:22 +01:00
root_path = self.root
2019-01-19 10:54:18 +01:00
schema = {'properties': self.schema,
'type': 'object'}
2019-01-10 09:54:22 +01:00
if root_path:
root = self.root.split('.')
2019-02-14 21:51:49 +01:00
if not path.startswith(self.root):
2019-02-05 07:01:22 +01:00
raise Exception('cannot find {0}'.format(path))
2019-01-10 09:54:22 +01:00
subpaths = path.split('.')[len(root):]
2018-12-24 09:28:44 +01:00
else:
subpaths = path.split('.')
for subpath in subpaths:
if root_path:
root_path += '.' + subpath
else:
root_path = subpath
schema = schema['properties'][root_path]
2018-12-24 09:28:44 +01:00
return schema
def isfollower(self,
2019-02-05 07:01:22 +01:00
path: str) -> bool:
if '.' in path:
parent_schema = self.get_schema(path.rsplit('.', 1)[0])
leader = next(iter(parent_schema['properties'].keys()))
if parent_schema['type'] == 'array' and \
leader != path:
return True
return False
2019-04-03 19:42:59 +02:00
def is_hidden(self,
path: str,
index: Optional[int]) -> bool:
2019-04-03 19:42:59 +02:00
for property_, needs in {'hidden': True, 'display': False}.items():
if property_ in self.temp.get(path, {}):
value = self.temp[path][property_]
else:
2019-04-03 19:42:59 +02:00
if self.isfollower(path):
if self.model.get(path, {}).get('null', {}).get(property_, None) == needs:
return True
elif self.model.get(path, {}).get(property_, None) == needs:
return True
index = str(index)
2019-04-04 19:34:16 +02:00
if index != 'None' and index in self.model.get(path, {}) and self.model.get(path, {}).get(index, {}).get(property_, None) == needs:
2019-04-03 19:42:59 +02:00
return True
return False
2019-02-05 07:01:22 +01:00
def get_value(self,
2019-02-28 22:12:46 +01:00
path: str,
index: int=None) -> Any:
if index is None:
2019-02-28 22:12:46 +01:00
if 'value' in self.temp.get(path, {}):
value = self.temp[path]['value']
else:
value = self.model.get(path, {}).get('value')
if value is None and self.get_schema(path).get('isMulti', False):
value = []
2019-02-05 07:01:22 +01:00
else:
2019-04-03 19:42:59 +02:00
index = str(index)
if 'delete' in self.temp.get(path, {}):
value = None
elif index in self.temp.get(path, {}):
if 'delete' in self.temp[path][index]:
value = None
else:
value = self.temp[path]
2019-03-16 22:51:39 +01:00
else:
2019-04-03 19:42:59 +02:00
value = self.model.get(path)
if self.isfollower(path):
if self.is_hidden(path, index):
value = PropertiesOptionError(None, None, None, opt_type='option')
elif value is not None and index in value:
value = value[index]['value']
else:
2019-03-16 22:51:39 +01:00
value = self.get_schema(path).get('default')
else:
2019-04-03 19:42:59 +02:00
if value is not None and index in value and 'value' in value[index]:
value = value[index]['value']
else:
value = self.get_schema(path).get('default')
2019-02-05 07:01:22 +01:00
return value
2018-12-24 09:28:44 +01:00
2019-02-05 07:01:22 +01:00
def get_owner(self,
path: str,
index: int) -> str:
if not self.isfollower(path):
if 'owner' in self.temp.get(path, {}):
owner = self.temp[path]['owner']
else:
owner = self.model.get(path, {}).get('owner', 'default')
2019-02-05 07:01:22 +01:00
else:
if 'value' in self.temp.get(path, {}):
2019-04-03 19:42:59 +02:00
value = self.temp[path]
else:
2019-04-03 19:42:59 +02:00
value = self.model.get(path, {})
index = str(index)
if self.is_hidden(path, index):
raise PropertiesOptionError(None, None, None, opt_type='option')
if index in value:
2019-04-03 19:42:59 +02:00
owner = value[index]['owner']
else:
owner = 'default'
2019-02-05 07:01:22 +01:00
return owner
2018-12-24 09:28:44 +01:00
def updates_value(self,
action: str,
path: str,
index: Optional[int],
value: Optional[Any],
remote: bool,
leadership: Optional[str]) -> None:
2018-12-24 09:28:44 +01:00
update_last_action = False
if self.updates:
last_body = self.updates[-1]
if last_body['name'] == path:
2019-02-05 07:01:22 +01:00
if index is None and not 'index' in last_body:
2019-02-07 16:23:41 +01:00
last_action = last_body['action']
2018-12-24 09:28:44 +01:00
if last_action == action or \
last_action in ['delete', 'modify'] and action in ['delete', 'modify']:
2019-02-07 16:23:41 +01:00
update_last_action = True
2018-12-24 09:28:44 +01:00
elif index == None and action == 'delete':
for update in reversed(self.updates):
if leadership is None and update['name'] == path or \
leadership and path.startswith(leadership + '.'):
2018-12-24 09:28:44 +01:00
del self.updates[-1]
else:
break
elif last_body['index'] == index:
if last_body['action'] == 'add' and action == 'modify':
action = 'add'
update_last_action = True
elif last_body['action'] == action and action != 'delete' or \
last_body['action'] == 'modify' and action == 'delete':
update_last_action = True
elif last_body['action'] == 'add' and action == 'delete':
2019-01-10 09:54:22 +01:00
del self.updates[-1]
2018-12-24 09:28:44 +01:00
if update_last_action:
2019-03-16 22:51:39 +01:00
if action == 'delete' and value is None:
2018-12-24 09:28:44 +01:00
if 'value' in last_body:
del last_body['value']
else:
last_body['value'] = value
if index is None and 'index' in last_body:
del last_body['index']
last_body['action'] = action
else:
data = {'action': action,
'name': path}
2019-03-16 22:51:39 +01:00
if action != 'delete' and value is not None:
2018-12-24 09:28:44 +01:00
data['value'] = value
if index is not None:
data['index'] = index
self.updates.append(data)
if 'pattern' in self.form.get(path, {}) and (not isinstance(value, list) or undefined not in value):
match = self.test_value(path, value, remote)
else:
match = True
if match:
if remote:
2019-02-28 22:12:46 +01:00
self.updates_data(self.send_data({'updates': self.updates,
2019-04-03 19:42:59 +02:00
'model': self.model}))
2019-02-28 22:12:46 +01:00
2018-12-24 09:28:44 +01:00
else:
2019-03-16 22:51:39 +01:00
if action == 'delete':
if index is None:
2019-04-03 19:42:59 +02:00
# leader or standard option
# set value to default value
2019-03-16 22:51:39 +01:00
value = self.default_value(path)
2019-04-03 19:42:59 +02:00
self.temp[path] = {'value': value, 'owner': 'default'}
2019-03-16 22:51:39 +01:00
if self.option(path).option.isleader():
2019-04-03 19:42:59 +02:00
# if leader, set follower to default value
2019-03-16 22:51:39 +01:00
leadership_path = path.rsplit('.', 1)[0]
parent_schema = self.get_schema(leadership_path)
iter_leadership = list(parent_schema['properties'].keys())
for follower in iter_leadership[1:]:
2019-04-03 19:42:59 +02:00
# delete all values
self.temp[follower] = {'delete': True}
2019-03-16 22:51:39 +01:00
elif self.option(path).option.isleader():
2019-04-03 19:42:59 +02:00
# if remove an indexed leader value
2019-03-16 22:51:39 +01:00
old_value = self.option(path).value.get()
old_value.pop(index)
2019-04-03 19:42:59 +02:00
self.temp[path] = {'value': old_value, 'owner': 'tmp'}
2019-03-16 22:51:39 +01:00
leadership_path = path.rsplit('.', 1)[0]
parent_schema = self.get_schema(leadership_path)
iter_leadership = list(parent_schema['properties'].keys())
for follower in iter_leadership[1:]:
2019-04-03 19:42:59 +02:00
# remove value for this index and reduce len
#FIXME on ne reduce pas la longueur !!!!
self.temp.setdefault(follower, {})[str(index)] = {'delete': True}
2019-03-16 22:51:39 +01:00
else:
2019-04-03 19:42:59 +02:00
# it's a follower with index
self.temp.setdefault(path, {})[str(index)] = {'delete': True}
2019-03-16 22:51:39 +01:00
elif index is None:
2019-04-03 19:42:59 +02:00
# set a value for a not follower option
self.temp[path] = {'value': value, 'owner': 'tmp'}
2019-03-16 22:51:39 +01:00
else:
2019-04-03 19:42:59 +02:00
# set a value for a follower option
self.temp.setdefault(path, {})[str(index)] = {'value': value, 'owner': 'tmp'}
self.set_dependencies(path, value)
2018-12-24 09:28:44 +01:00
self.set_not_equal(path, value)
self.do_copy(path, value)
2019-03-16 22:51:39 +01:00
def default_value(self, path):
schema = self.get_schema(path)
value = schema.get('value');
if value is None and schema.get('isMulti', False):
value = []
return value
2019-02-28 22:12:46 +01:00
def updates_data(self, data):
self.updates = []
self.temp.clear()
2019-04-03 19:42:59 +02:00
self.model = data['model']
2019-02-28 22:12:46 +01:00
2018-12-24 09:28:44 +01:00
def test_value(self,
path: str,
value: Any,
remote: bool):
if isinstance(value, list):
for val in value:
if not self.test_value(path, val, remote):
return False
return True
else:
if value is None:
match = True
else:
match = self.form[path]['pattern'].search(value)
if not remote:
if not match:
self.temp.setdefault(path, {})['error'] = ['']
elif 'error' in self.model[path]:
del self.temp[path]['error']
return match
def set_dependencies(self,
path: str,
2019-02-05 07:01:22 +01:00
ori_value: Any,
force_hide: bool=False) -> None:
2018-12-24 09:28:44 +01:00
dependencies = self.form.get(path, {}).get('dependencies', {})
if dependencies:
2019-02-05 07:01:22 +01:00
if ori_value in dependencies['expected']:
expected = dependencies['expected'][ori_value]
2018-12-24 09:28:44 +01:00
else:
expected = dependencies['default']
for action in ['hide', 'show']:
expected_actions = expected.get(action)
if expected_actions:
if force_hide:
hidden = True
else:
hidden = action == 'hide'
2018-12-24 09:28:44 +01:00
for expected_path in expected_actions:
self.temp.setdefault(expected_path, {})['hidden'] = hidden
2019-02-05 07:01:22 +01:00
if 'value' in self.temp[expected_path]:
value = self.temp[expected_path]['value']
else:
value = self.model[expected_path].get('value')
self.set_dependencies(expected_path, value, hidden)
2018-12-24 09:28:44 +01:00
def set_not_equal(self,
path: str,
value: Any) -> None:
not_equal = self.form.get(path, {}).get('not_equal', {})
if not_equal:
vals = []
opts = []
if isinstance(value, list):
for val in value:
vals.append(val)
opts.append(path)
else:
vals.append(value)
opts.append(path)
for path_ in self.form[path]['not_equal']['options']:
schema = self.get_schema(path_)
2019-02-14 21:25:00 +01:00
p_value = self.get_value(path_)
2018-12-24 09:28:44 +01:00
if isinstance(p_value, list):
for val in p_value:
vals.append(val)
opts.append(path_)
else:
vals.append(p_value)
opts.append(path_)
equal = []
warnings_only = self.form[path]['not_equal'].get('warnings', False)
if warnings_only:
msg = _('should be different from the value of "{}"')
msgcurr = _('value for {} should be different')
else:
msg = _('must be different from the value of "{}"')
msgcurr = _('value for {} must be different')
for idx_inf, val_inf in enumerate(vals):
for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
if val_inf == val_sup is not None:
for opt_ in [opts[idx_inf], opts[idx_inf + idx_sup + 1]]:
if opt_ not in equal:
equal.append(opt_)
if equal:
equal_name = {}
for opt in equal:
schema = self.get_schema(opt)
equal_name[opt] = schema['title']
for opt_ in equal:
display_equal = []
for opt__ in equal:
if opt_ != opt__:
display_equal.append(equal_name[opt_])
display_equal = ', '.join(display_equal)
if opt_ == path:
msg_ = msgcurr.format(display_equal)
else:
msg_ = msg.format(display_equal)
if warnings_only:
self.model[opt_].setdefault('warnings', []).append(msg_)
else:
self.model[opt_].setdefault('error', []).append(msg_)
else:
for opt in opts:
if 'warnings' in self.model[opt]:
del self.model[opt]['warnings']
if 'error' in self.model[opt]:
del self.model[opt]['error']
def do_copy(self,
path: str,
value: Any) -> None:
copy = self.form.get(path, {}).get('copy')
if copy:
for opt in copy:
# FIXME follower!
owner = self.get_owner(opt, None)
2018-12-24 09:28:44 +01:00
if owner == 'default':
2019-02-05 07:01:22 +01:00
# do not change in this.temp, it's default value
2018-12-24 09:28:44 +01:00
self.model[opt]['value'] = value
def send_data(self,
updates):
raise NotImplementedError('please implement send_data function')