diff --git a/openpype/modules/default_modules/sync_server/providers/dropbox.py b/openpype/modules/default_modules/sync_server/providers/dropbox.py index 0d735a0b59..2bc7a83a5b 100644 --- a/openpype/modules/default_modules/sync_server/providers/dropbox.py +++ b/openpype/modules/default_modules/sync_server/providers/dropbox.py @@ -101,9 +101,14 @@ class DropboxHandler(AbstractProvider): }, # roots could be overriden only on Project level, User cannot { - 'key': "roots", - 'label': "Roots", - 'type': 'dict' + "key": "roots", + "label": "Roots", + "type": "dict-roots", + "object_type": { + "type": "path", + "multiplatform": True, + "multipath": False + } } ] diff --git a/openpype/modules/default_modules/sync_server/providers/gdrive.py b/openpype/modules/default_modules/sync_server/providers/gdrive.py index 0aabd9fbcd..8c8447f8f0 100644 --- a/openpype/modules/default_modules/sync_server/providers/gdrive.py +++ b/openpype/modules/default_modules/sync_server/providers/gdrive.py @@ -131,9 +131,14 @@ class GDriveHandler(AbstractProvider): }, # roots could be overriden only on Project leve, User cannot { - 'key': "roots", - 'label': "Roots", - 'type': 'dict' + "key": "roots", + "label": "Roots", + "type": "dict-roots", + "object_type": { + "type": "path", + "multiplatform": True, + "multipath": False + } } ] return editable diff --git a/openpype/modules/default_modules/sync_server/providers/local_drive.py b/openpype/modules/default_modules/sync_server/providers/local_drive.py index 8e5f170bc9..e6c62f2daa 100644 --- a/openpype/modules/default_modules/sync_server/providers/local_drive.py +++ b/openpype/modules/default_modules/sync_server/providers/local_drive.py @@ -50,9 +50,14 @@ class LocalDriveHandler(AbstractProvider): # for non 'studio' sites, 'studio' is configured in Anatomy editable = [ { - 'key': "roots", - 'label': "Roots", - 'type': 'dict' + "key": "roots", + "label": "Roots", + "type": "dict-roots", + "object_type": { + "type": "path", + "multiplatform": True, + "multipath": False + } } ] return editable diff --git a/openpype/modules/default_modules/sync_server/providers/sftp.py b/openpype/modules/default_modules/sync_server/providers/sftp.py index 07450265e2..d737849cdc 100644 --- a/openpype/modules/default_modules/sync_server/providers/sftp.py +++ b/openpype/modules/default_modules/sync_server/providers/sftp.py @@ -139,9 +139,14 @@ class SFTPHandler(AbstractProvider): }, # roots could be overriden only on Project leve, User cannot { - 'key': "roots", - 'label': "Roots", - 'type': 'dict' + "key": "roots", + "label": "Roots", + "type": "dict-roots", + "object_type": { + "type": "path", + "multiplatform": True, + "multipath": False + } } ] return editable diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index 775bf40ac4..ccf2a5993e 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -112,7 +112,8 @@ from .enum_entity import ( from .list_entity import ListEntity from .dict_immutable_keys_entity import ( DictImmutableKeysEntity, - RootsDictEntity + RootsDictEntity, + SyncServerSites ) from .dict_mutable_keys_entity import DictMutableKeysEntity from .dict_conditional import ( @@ -173,6 +174,7 @@ __all__ = ( "DictImmutableKeysEntity", "RootsDictEntity", + "SyncServerSites", "DictMutableKeysEntity", diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index d0cd41d11c..6131fa2ac7 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -572,7 +572,7 @@ class RootsDictEntity(DictImmutableKeysEntity): object_type = {"type": object_type} self.object_type = object_type - if not self.is_group: + if self.group_item is None and not self.is_group: self.is_group = True schema_data = copy.deepcopy(self.schema_data) @@ -629,7 +629,7 @@ class RootsDictEntity(DictImmutableKeysEntity): self._add_children(schema_data) - self._set_children_values(state) + self._set_children_values(state, ignore_missing_defaults) super(RootsDictEntity, self).set_override_state( state, True @@ -652,11 +652,14 @@ class RootsDictEntity(DictImmutableKeysEntity): return super(RootsDictEntity, self).on_child_change(child_obj) - def _set_children_values(self, state): + def _set_children_values(self, state, ignore_missing_defaults): if state >= OverrideState.DEFAULTS: default_value = self._default_value if default_value is NOT_SET: - if state > OverrideState.DEFAULTS: + if ( + not ignore_missing_defaults + and state > OverrideState.DEFAULTS + ): raise DefaultsNotDefined(self) else: default_value = {} @@ -724,3 +727,195 @@ class RootsDictEntity(DictImmutableKeysEntity): self._project_value = value self._project_override_metadata = {} self.had_project_override = value is not NOT_SET + + +class SyncServerSites(DictImmutableKeysEntity): + """Dictionary enity for sync sites. + + Can be used only in project settings. + + Is loading sites from system settings. Uses site name as key and by site's + provider loads project settings schemas calling method + `get_project_settings_schema` on provider. + + Each provider have `enabled` boolean entity to be able know if site should + be enabled for the project. Enabled is by default set to False. + """ + schema_types = ["sync-server-sites"] + + def _item_initialization(self): + # Make sure this is a group + if self.group_item is None and not self.is_group: + self.is_group = True + + # Fake children for `dict` validations + self.schema_data["children"] = [] + # Site names changed or were removed + # - to find out that site names was removed so project values + # contain more data than should + self._sites_changed = False + + super(SyncServerSites, self)._item_initialization() + + def set_override_state(self, state, ignore_missing_defaults): + # Cleanup children related attributes + self.children = [] + self.non_gui_children = {} + self.gui_layout = [] + + # Create copy of schema + schema_data = copy.deepcopy(self.schema_data) + # Collect children + children = self._get_children() + schema_data["children"] = children + + self._add_children(schema_data) + + self._sites_changed = False + self._set_children_values(state, ignore_missing_defaults) + + super(SyncServerSites, self).set_override_state(state, True) + + @property + def has_unsaved_changes(self): + if self._sites_changed: + return True + return super(SyncServerSites, self).has_unsaved_changes + + @property + def has_studio_override(self): + if self._sites_changed: + return True + return super(SyncServerSites, self).has_studio_override + + @property + def has_project_override(self): + if self._sites_changed: + return True + return super(SyncServerSites, self).has_project_override + + def _get_children(self): + from openpype_modules import sync_server + + # Load system settings to find out all created sites + modules_entity = self.get_entity_from_path("system_settings/modules") + sync_server_settings_entity = modules_entity.get("sync_server") + + # Get project settings configurations for all providers + project_settings_schema = ( + sync_server + .SyncServerModule + .get_project_settings_schema() + ) + + children = [] + # Add 'enabled' for each site to be able know if should be used for + # the project + checkbox_child = { + "type": "boolean", + "key": "enabled", + "default": False + } + if sync_server_settings_entity is not None: + sites_entity = sync_server_settings_entity["sites"] + for site_name, provider_entity in sites_entity.items(): + provider_name = provider_entity["provider"].value + provider_children = copy.deepcopy( + project_settings_schema.get(provider_name) + ) or [] + provider_children.insert(0, copy.deepcopy(checkbox_child)) + children.append({ + "type": "dict", + "key": site_name, + "label": site_name, + "checkbox_key": "enabled", + "children": provider_children + }) + + return children + + def _set_children_values(self, state, ignore_missing_defaults): + current_site_names = set(self.non_gui_children.keys()) + + if state >= OverrideState.DEFAULTS: + default_value = self._default_value + if default_value is NOT_SET: + if ( + not ignore_missing_defaults + and state > OverrideState.DEFAULTS + ): + raise DefaultsNotDefined(self) + else: + default_value = {} + + for key, child_obj in self.non_gui_children.items(): + child_value = default_value.get(key, NOT_SET) + child_obj.update_default_value(child_value) + + if state >= OverrideState.STUDIO: + value = self._studio_value + if value is NOT_SET: + value = {} + + for key, child_obj in self.non_gui_children.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_studio_value(child_value) + + if state is OverrideState.STUDIO: + value_keys = set(value.keys()) + self._sites_changed = value_keys != current_site_names + + if state >= OverrideState.PROJECT: + value = self._project_value + if value is NOT_SET: + value = {} + + for key, child_obj in self.non_gui_children.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_project_value(child_value) + + if state is OverrideState.PROJECT: + value_keys = set(value.keys()) + self._sites_changed = value_keys != current_site_names + + def _update_current_metadata(self): + """Override this method as this entity should not have metadata.""" + self._metadata_are_modified = False + self._current_metadata = {} + + def update_default_value(self, value): + """Update default values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "default") + value, _ = self._prepare_value(value) + + self._default_value = value + self._default_metadata = {} + self.has_default_value = value is not NOT_SET + + def update_studio_value(self, value): + """Update studio override values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "studio override") + value, _ = self._prepare_value(value) + + self._studio_value = value + self._studio_override_metadata = {} + self.had_studio_override = value is not NOT_SET + + def update_project_value(self, value): + """Update project override values. + + Not an api method, should be called by parent. + """ + + value = self._check_update_value(value, "project override") + value, _metadata = self._prepare_value(value) + + self._project_value = value + self._project_override_metadata = {} + self.had_project_override = value is not NOT_SET