diff --git a/pype/settings/entities/dict_mutable_keys_entity.py b/pype/settings/entities/dict_mutable_keys_entity.py index 2fd2b87311..8c9b5e03b1 100644 --- a/pype/settings/entities/dict_mutable_keys_entity.py +++ b/pype/settings/entities/dict_mutable_keys_entity.py @@ -7,7 +7,8 @@ from .lib import ( from . import EndpointEntity from .exceptions import ( DefaultsNotDefined, - StudioDefaultsNotDefined + StudioDefaultsNotDefined, + RequiredKeyModified ) from pype.settings.constants import ( METADATA_KEYS, @@ -51,6 +52,8 @@ class DictMutableKeysEntity(EndpointEntity): return key in self.children_by_key def pop(self, key, *args, **kwargs): + if key in self.required_keys: + raise RequiredKeyModified(self.path, key) result = self.children_by_key.pop(key, *args, **kwargs) self.on_change() return result @@ -93,6 +96,9 @@ class DictMutableKeysEntity(EndpointEntity): child_obj.set(value) def change_key(self, old_key, new_key): + if old_key in self.required_keys: + raise RequiredKeyModified(self.path, old_key) + if new_key == old_key: return self.children_by_key[new_key] = self.children_by_key.pop(old_key) @@ -309,6 +315,10 @@ class DictMutableKeysEntity(EndpointEntity): for key in tuple(self.children_by_key.keys()): self.children_by_key.pop(key) + for required_key in self.required_keys: + if required_key not in new_value: + new_value[required_key] = NOT_SET + # Create new children children_label_by_id = {} metadata_labels = metadata.get(M_DYNAMIC_KEY_LABEL) or {} @@ -441,7 +451,13 @@ class DictMutableKeysEntity(EndpointEntity): def update_default_value(self, value): value = self._check_update_value(value, "default") - self.has_default_value = value is not NOT_SET + has_default_value = value is not NOT_SET + if has_default_value: + for required_key in self.required_keys: + if required_key not in value: + has_default_value = False + break + self.has_default_value = has_default_value value, metadata = self._prepare_value(value) self._default_value = value self._default_metadata = metadata diff --git a/pype/settings/entities/exceptions.py b/pype/settings/entities/exceptions.py index 951cd07243..7080a9b187 100644 --- a/pype/settings/entities/exceptions.py +++ b/pype/settings/entities/exceptions.py @@ -28,7 +28,17 @@ class InvalidValueType(Exception): super(InvalidValueType, self).__init__(msg) -class SchemaMissingFileInfo(Exception): +class RequiredKeyModified(KeyError): + def __init__(self, entity_path, key): + msg = "{} - Tried to modify required key \"{}\"." + super(RequiredKeyModified, self).__init__(msg.format(entity_path, key)) + + +class SchemaError(Exception): + pass + + +class SchemaMissingFileInfo(SchemaError): def __init__(self, invalid): full_path_keys = [] for item in invalid: @@ -41,7 +51,7 @@ class SchemaMissingFileInfo(Exception): super(SchemaMissingFileInfo, self).__init__(msg) -class SchemeGroupHierarchyBug(Exception): +class SchemeGroupHierarchyBug(SchemaError): def __init__(self, entity_path): msg = ( "Items with attribute \"is_group\" can't have another item with" @@ -50,7 +60,7 @@ class SchemeGroupHierarchyBug(Exception): super(SchemeGroupHierarchyBug, self).__init__(msg) -class SchemaDuplicatedKeys(Exception): +class SchemaDuplicatedKeys(SchemaError): def __init__(self, entity_path, key): msg = ( "Schema item contain duplicated key \"{}\" in" @@ -59,7 +69,7 @@ class SchemaDuplicatedKeys(Exception): super(SchemaDuplicatedKeys, self).__init__(msg) -class SchemaDuplicatedEnvGroupKeys(Exception): +class SchemaDuplicatedEnvGroupKeys(SchemaError): def __init__(self, invalid): items = [] for key_path, keys in invalid.items(): @@ -74,7 +84,7 @@ class SchemaDuplicatedEnvGroupKeys(Exception): super(SchemaDuplicatedEnvGroupKeys, self).__init__(msg) -class SchemaTemplateMissingKeys(Exception): +class SchemaTemplateMissingKeys(SchemaError): def __init__(self, missing_keys, required_keys, template_name=None): self.missing_keys = missing_keys self.required_keys = required_keys diff --git a/pype/settings/entities/schemas/system_schema/example_schema.json b/pype/settings/entities/schemas/system_schema/example_schema.json index 6e7a47d1bf..48a21cc0c6 100644 --- a/pype/settings/entities/schemas/system_schema/example_schema.json +++ b/pype/settings/entities/schemas/system_schema/example_schema.json @@ -141,6 +141,16 @@ "maximum": 100 } }, + { + "type": "dict-modifiable", + "key": "modifiable_dict_with_required_keys", + "label": "Modifiable dict with required keys", + "required_keys": [ + "key_1", + "key_2" + ], + "object_type": "text" + }, { "type": "list-strict", "key": "strict_list_labels_horizontal", diff --git a/pype/tools/settings/settings/widgets/dict_mutable_widget.py b/pype/tools/settings/settings/widgets/dict_mutable_widget.py index b27e0e492b..0cb051082e 100644 --- a/pype/tools/settings/settings/widgets/dict_mutable_widget.py +++ b/pype/tools/settings/settings/widgets/dict_mutable_widget.py @@ -358,7 +358,8 @@ class ModifiableDictItem(QtWidgets.QWidget): self.add_btn.setEnabled(False) def set_as_last_required(self): - self.add_btn.setEnabled(True) + if not self.collapsible_key: + self.add_btn.setEnabled(True) def _on_focus_lose(self): if ( @@ -827,10 +828,25 @@ class DictMutableKeysWidget(BaseWidget): while self.input_fields: self.remove_row(self.input_fields[0]) - for key, child_entity in self.entity.items(): + keys_order = list(self.entity.required_keys) + last_required = None + if keys_order: + last_required = keys_order[-1] + for key in self.entity.keys(): + if key in keys_order: + continue + keys_order.append(key) + + for key in keys_order: + child_entity = self.entity[key] input_field = self.add_widget_for_child(child_entity) input_field.origin_key = key - input_field.set_key(key) + if key in self.entity.required_keys: + input_field.set_as_required(key) + if key == last_required: + input_field.set_as_last_required() + else: + input_field.set_key(key) if self.entity.collapsible_key: label = self.entity.get_child_label(child_entity) input_field.origin_key_label = label