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