better leader/follower support

This commit is contained in:
Emmanuel Garette 2019-05-08 20:15:46 +02:00
parent 626184fee7
commit 4ec269f2b9
1 changed files with 192 additions and 66 deletions

View File

@ -71,12 +71,7 @@ class TiramisuOptionOption:
return self.schema['type'] == 'array' return self.schema['type'] == 'array'
def isleader(self): def isleader(self):
if '.' in self._path: return self.config.isleader(self._path)
parent_schema = self.config.get_schema(self._path.rsplit('.', 1)[0])
if parent_schema['type'] == 'array':
leader = next(iter(parent_schema['properties'].keys()))
return leader == self._path
return False
def isfollower(self): def isfollower(self):
return self.config.isfollower(self._path) return self.config.isfollower(self._path)
@ -109,6 +104,13 @@ class TiramisuOptionOption:
# FIXME # FIXME
return None return None
def pattern(self):
if self._path in self.form:
return self.form[self._path].get('pattern')
else:
return None
class TiramisuOptionProperty: class TiramisuOptionProperty:
# config.option(path).property # config.option(path).property
def __init__(self, def __init__(self,
@ -268,6 +270,9 @@ class TiramisuOptionValue(_Value):
def set(self, value): def set(self, value):
type_ = self.schema['type'] type_ = self.schema['type']
leader_old_value = undefined
if self.config.isleader(self.path):
leader_old_value = self.config.get_value(self.path)
remote = self.form.get(self.path, {}).get('remote', False) remote = self.form.get(self.path, {}).get('remote', False)
if self.index is None and self.schema.get('isMulti', False): if self.index is None and self.schema.get('isMulti', False):
if not isinstance(value, list): if not isinstance(value, list):
@ -287,14 +292,13 @@ class TiramisuOptionValue(_Value):
self.config.modify_value(self.path, self.config.modify_value(self.path,
self.index, self.index,
value, value,
remote) remote,
leader_old_value)
self._display_warnings(self.path, value, type_, self.schema['name']) self._display_warnings(self.path, value, type_, self.schema['name'])
def reset(self): def reset(self):
remote = self.form.get(self.path, {}).get('remote', False)
self.config.delete_value(self.path, self.config.delete_value(self.path,
self.index, self.index)
remote)
def default(self): def default(self):
return self.schema.get('value') return self.schema.get('value')
@ -354,8 +358,8 @@ class TiramisuOptionDescription(_Option):
return TiramisuOptionProperty(self.config, return TiramisuOptionProperty(self.config,
self.path, self.path,
self.model.get(self.path, {})) self.model.get(self.path, {}))
if subfunc == 'value': if subfunc == 'value' and self.schema['type'] == 'array':
return TiramisuOptionValue(self.config, return TiramisuLeadershipValue(self.config,
self.schema, self.schema,
self.model, self.model,
self.form, self.form,
@ -371,6 +375,30 @@ class TiramisuOptionDescription(_Option):
raise PropertiesOptionError(None, None, None, opt_type='optiondescription') raise PropertiesOptionError(None, None, None, opt_type='optiondescription')
class TiramisuLeadershipValue:
def __init__(self,
config,
schema,
model,
form,
temp,
path):
self.config = config
self.schema = schema
self.model = model
self.form = form
self.temp = temp
self.path = path
def len(self):
return len(self.config.get_value(self.schema['properties'][0]))
def pop(self,
index: int) -> None:
leadership_path = self.schema['properties'][0]
self.config.delete_value(leadership_path, index)
class TiramisuOption: class TiramisuOption:
# config.option(path) (with path == Option) # config.option(path) (with path == Option)
def __init__(self, def __init__(self,
@ -520,9 +548,8 @@ class Config:
self.model = json['model'] self.model = json['model']
self.form = json['form'] self.form = json['form']
# support pattern # support pattern
for key, option in json['form'].items(): for key, option in self.form.items():
if key != 'null': if key != 'null' and 'pattern' in option:
if 'pattern' in option:
option['pattern'] = re.compile(option['pattern']) option['pattern'] = re.compile(option['pattern'])
self.temp = {} self.temp = {}
self.schema = json['schema'] self.schema = json['schema']
@ -556,18 +583,22 @@ class Config:
index: Optional[int], index: Optional[int],
value: Any, value: Any,
remote: bool) -> None: remote: bool) -> None:
self.manage_updates('add',
path,
index,
value)
self.updates_value('add', self.updates_value('add',
path, path,
index, index,
value, value,
remote, remote)
None)
def modify_value(self, def modify_value(self,
path: str, path: str,
index: Optional[int], index: Optional[int],
value: Any, value: Any,
remote: bool) -> None: remote: bool,
leader_old_value: Any) -> None:
schema = self.get_schema(path) schema = self.get_schema(path)
if value and isinstance(value, list) and value[-1] is undefined: if value and isinstance(value, list) and value[-1] is undefined:
new_value = schema.get('defaultvalue') new_value = schema.get('defaultvalue')
@ -578,23 +609,31 @@ class Config:
new_value = schema_value[len_value - 1] new_value = schema_value[len_value - 1]
value[-1] = new_value value[-1] = new_value
self.manage_updates('modify',
path,
index,
value)
self.updates_value('modify', self.updates_value('modify',
path, path,
index, index,
value, value,
remote, remote,
None) False,
leader_old_value)
def delete_value(self, def delete_value(self,
path: str, path: str,
index: Optional[int], index: Optional[int]) -> None:
remote: bool) -> None: remote = self.form.get(path, {}).get('remote', False)
self.manage_updates('delete',
path,
index,
None)
self.updates_value('delete', self.updates_value('delete',
path, path,
index, index,
None, None,
remote, remote)
None)
def get_properties(self, def get_properties(self,
model, model,
@ -638,6 +677,16 @@ class Config:
schema = schema['properties'][root_path] schema = schema['properties'][root_path]
return schema return schema
def isleader(self,
path):
if '.' in path:
parent_schema = self.get_schema(path.rsplit('.', 1)[0])
if parent_schema['type'] == 'array':
leader = next(iter(parent_schema['properties'].keys()))
return leader == path
return False
def isfollower(self, def isfollower(self,
path: str) -> bool: path: str) -> bool:
if '.' in path: if '.' in path:
@ -652,16 +701,18 @@ class Config:
path: str, path: str,
index: Optional[int]) -> bool: index: Optional[int]) -> bool:
for property_, needs in {'hidden': True, 'display': False}.items(): for property_, needs in {'hidden': True, 'display': False}.items():
if index is not None and property_ in self.temp.get(path, {}).get(str(index), {}):
return self.temp[path][str(index)][property_]
if property_ in self.temp.get(path, {}): if property_ in self.temp.get(path, {}):
return self.temp[path][property_] return self.temp[path][property_]
else: elif self.isfollower(path):
if self.isfollower(path):
if self.model.get(path, {}).get('null', {}).get(property_, None) == needs: if self.model.get(path, {}).get('null', {}).get(property_, None) == needs:
return True return True
elif self.model.get(path, {}).get(property_, None) == needs: elif self.model.get(path, {}).get(property_, None) == needs:
return True return True
if index is not None:
index = str(index) index = str(index)
if index != 'None' and index in self.model.get(path, {}) and self.model.get(path, {}).get(index, {}).get(property_, None) == needs: if self.model.get(path, {}).get(index, {}).get(property_) == needs:
return True return True
return False return False
@ -683,7 +734,7 @@ class Config:
index = str(index) index = str(index)
if 'delete' in self.temp.get(path, {}): if 'delete' in self.temp.get(path, {}):
value = None value = None
elif index in self.temp.get(path, {}) and 'delete' in self.temp[path][index]: elif self.temp.get(path, {}).get(index, {}).get('delete') == True:
value = None value = None
elif 'value' in self.temp.get(path, {}).get(index, {}): elif 'value' in self.temp.get(path, {}).get(index, {}):
value = self.temp[path] value = self.temp[path]
@ -703,6 +754,8 @@ class Config:
value = schema.get('default') value = schema.get('default')
if value is None and schema.get('isSubMulti', False): if value is None and schema.get('isSubMulti', False):
value = [] value = []
if isinstance(value, list):
value = value.copy()
return value return value
def get_owner(self, def get_owner(self,
@ -721,19 +774,17 @@ class Config:
index = str(index) index = str(index)
if self.is_hidden(path, index): if self.is_hidden(path, index):
raise PropertiesOptionError(None, None, None, opt_type='option') raise PropertiesOptionError(None, None, None, opt_type='option')
if index in value: if 'owner' in value.get(index, {}):
owner = value[index]['owner'] owner = value[index]['owner']
else: else:
owner = 'default' owner = 'default'
return owner return owner
def updates_value(self, def manage_updates(self,
action: str, action,
path: str, path,
index: Optional[int], index,
value: Optional[Any], value):
remote: bool,
leadership: Optional[str]) -> None:
update_last_action = False update_last_action = False
if self.updates: if self.updates:
last_body = self.updates[-1] last_body = self.updates[-1]
@ -745,8 +796,7 @@ class Config:
update_last_action = True update_last_action = True
elif index == None and action == 'delete': elif index == None and action == 'delete':
for update in reversed(self.updates): for update in reversed(self.updates):
if leadership is None and update['name'] == path or \ if update['name'] == path:
leadership and path.startswith(leadership + '.'):
del self.updates[-1] del self.updates[-1]
else: else:
break break
@ -777,6 +827,15 @@ class Config:
if index is not None: if index is not None:
data['index'] = index data['index'] = index
self.updates.append(data) self.updates.append(data)
def updates_value(self,
action: str,
path: str,
index: Optional[int],
value: Optional[Any],
remote: bool,
default_value: bool=False,
leader_old_value: Optional[Any]=undefined) -> None:
if 'pattern' in self.form.get(path, {}) and (not isinstance(value, list) or undefined not in value): if 'pattern' in self.form.get(path, {}) and (not isinstance(value, list) or undefined not in value):
match = self.test_value(path, value, remote) match = self.test_value(path, value, remote)
else: else:
@ -792,7 +851,7 @@ class Config:
# leader or standard option # leader or standard option
# set value to default value # set value to default value
value = self.default_value(path) value = self.default_value(path)
self.temp[path] = {'value': value, 'owner': 'default'} self._set_temp_value(path, None, value, 'default')
if self.option(path).option.isleader(): if self.option(path).option.isleader():
# if leader, set follower to default value # if leader, set follower to default value
leadership_path = path.rsplit('.', 1)[0] leadership_path = path.rsplit('.', 1)[0]
@ -800,37 +859,98 @@ class Config:
iter_leadership = list(parent_schema['properties'].keys()) iter_leadership = list(parent_schema['properties'].keys())
for follower in iter_leadership[1:]: for follower in iter_leadership[1:]:
# delete all values # delete all values
self.temp[follower] = {'delete': True} self._del_temp_value(follower, None)
value = self.get_value(path)
elif self.option(path).option.isleader(): elif self.option(path).option.isleader():
# if remove an indexed leader value # if remove an indexed leader value
old_value = self.option(path).value.get() leader_value = self.option(path).value.get()
old_value.pop(index) leader_value.pop(index)
self.temp[path] = {'value': old_value, 'owner': 'tmp'} max_value = len(leader_value)
self._set_temp_value(path, None, leader_value, 'tmp')
leadership_path = path.rsplit('.', 1)[0] leadership_path = path.rsplit('.', 1)[0]
parent_schema = self.get_schema(leadership_path) parent_schema = self.get_schema(leadership_path)
iter_leadership = list(parent_schema['properties'].keys()) iter_leadership = list(parent_schema['properties'].keys())
for follower in iter_leadership[1:]: for follower in iter_leadership[1:]:
# remove value for this index and reduce len # remove value for this index and reduce len
#FIXME on ne reduce pas la longueur !!!! new_temp = {}
self.temp.setdefault(follower, {})[str(index)] = {'delete': True} for idx in range(max_value):
if index == idx:
continue
cur_index = idx
if index < cur_index:
cur_index = -1
if 'delete' in self.temp.get(follower, {}):
#FIXME copier les attributs hidden, ... depuis self.temp[follower][index] ?
new_temp[str(cur_index)] = {'delete': True}
elif self.temp.get(follower, {}).get(str(idx)) is not None:
new_temp[str(cur_index)] = self.temp[follower][str(idx)]
elif self.model.get(follower, {}).get(str(idx)) is not None:
new_temp[str(cur_index)] = self.model[follower][str(idx)]
if self.model[follower].get(str(max_value)) is not None:
# FIXME copier les attributs hidden, ... depuis self.temp[follower][index] ?
new_temp[str(max_value)] = {'delete': True}
self.temp[follower] = new_temp
value = leader_value
index = None
else: else:
# it's a follower with index # it's a follower with index
self.temp.setdefault(path, {})[str(index)] = {'delete': True} self.temp.setdefault(path, {})[str(index)] = {'delete': True}
self._del_temp_value(path, index)
value = self.get_value(path, index)
default_value = True
elif index is None: elif index is None:
# set a value for a not follower option # set a value for a not follower option
self.temp[path] = {'value': value, 'owner': 'tmp'} if default_value is True:
self.model[path]['value'] = value
else:
self._set_temp_value(path, None, value, 'tmp')
else: else:
# set a value for a follower option # set a value for a follower option
self.temp.setdefault(path, {})[str(index)] = {'value': value, 'owner': 'tmp'} self.temp.setdefault(path, {})[str(index)] = {'value': value, 'owner': 'tmp'}
self.set_dependencies(path, value) if default_value is True:
self.model[path][str(index)]['value'] = value
else:
self._set_temp_value(path, index, value, 'tmp')
self.set_dependencies(path, value, index=index)
self.set_not_equal(path, value) self.set_not_equal(path, value)
self.do_copy(path, value) self.do_copy(path, value)
if leader_old_value is not undefined and len(leader_old_value) < len(value):
# if leader and length is change, display/hide follower from follower's default value
index = len(value) - 1
parent_path = '.'.join(path.split('.')[:-1])
followers = list(self.option(parent_path).list())[1:]
for follower in followers:
follower_path = follower.option.path()
follower_value = self.option(follower_path, index).value.get()
self.set_dependencies(follower_path, follower_value, None, index)
def _set_temp_value(self, path, index, value, owner):
if index is not None:
obj = self.temp.setdefault(path, {}).setdefault(str(index), {})
else:
obj = self.temp.setdefault(path, {})
if 'delete' in obj:
del obj['delete']
obj['value'] = value
obj['owner'] = owner
def _del_temp_value(self, path, index):
if index is not None:
obj = self.temp.setdefault(path, {}).setdefault(str(index), {})
else:
obj = self.temp.setdefault(path, {})
for key in ['value', 'owner']:
if key in obj:
del obj[key]
obj['delete'] = True
def default_value(self, path): def default_value(self, path):
schema = self.get_schema(path) schema = self.get_schema(path)
value = schema.get('value'); value = schema.get('value')
if value is None and schema.get('isMulti', False): if value is None and schema.get('isMulti', False):
value = [] value = []
elif isinstance(value, list):
value = value.copy()
return value return value
def updates_data(self, data): def updates_data(self, data):
@ -862,7 +982,8 @@ class Config:
def set_dependencies(self, def set_dependencies(self,
path: str, path: str,
ori_value: Any, ori_value: Any,
force_hide: bool=False) -> None: force_hide: bool=False,
index: Optional[int]=None) -> None:
dependencies = self.form.get(path, {}).get('dependencies', {}) dependencies = self.form.get(path, {}).get('dependencies', {})
if dependencies: if dependencies:
if ori_value in dependencies['expected']: if ori_value in dependencies['expected']:
@ -877,12 +998,12 @@ class Config:
else: else:
hidden = action == 'hide' hidden = action == 'hide'
for expected_path in expected_actions: for expected_path in expected_actions:
self.temp.setdefault(expected_path, {})['hidden'] = hidden if index is not None:
if 'value' in self.temp[expected_path]: self.temp.setdefault(expected_path, {}).setdefault(str(index), {})['hidden'] = hidden
value = self.temp[expected_path]['value']
else: else:
value = self.model[expected_path].get('value') self.temp.setdefault(expected_path, {})['hidden'] = hidden
self.set_dependencies(expected_path, value, hidden) value = self.get_value(expected_path, index)
self.set_dependencies(expected_path, value, hidden, index)
def set_not_equal(self, def set_not_equal(self,
path: str, path: str,
@ -957,9 +1078,14 @@ class Config:
# FIXME follower! # FIXME follower!
owner = self.get_owner(opt, None) owner = self.get_owner(opt, None)
if owner == 'default': if owner == 'default':
# do not change in this.temp, it's default value # do not change in self.temp, it's default value
self.model[opt]['value'] = value if self.model[opt]['value'] != value:
if isinstance(value, list):
value = value.copy()
# self.model[opt]['value'] = value
remote = self.form.get(opt, {}).get('remote', False)
self.updates_value('modify', opt, None, value, remote, True)
def send_data(self, def send_data(self,
updates): updates):
raise NotImplementedError('please implement send_data function') raise NotImplementedError('please implement send_data method')