From c3c415e4059f1cd39f1a99cf855cecb73ce08c83 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 21 Dec 2020 19:09:44 +0100 Subject: [PATCH] actions are discovered by settings --- .../action_clean_hierarchical_attributes.py | 12 +- .../actions/action_create_cust_attrs.py | 5 +- .../ftrack/actions/action_delete_asset.py | 24 +-- .../actions/action_delete_old_versions.py | 18 +- .../modules/ftrack/actions/action_delivery.py | 9 +- .../ftrack/actions/action_job_killer.py | 5 +- .../ftrack/actions/action_prepare_project.py | 13 +- pype/modules/ftrack/actions/action_seed.py | 4 +- .../action_store_thumbnails_to_avalon.py | 11 +- .../ftrack/actions/action_sync_to_avalon.py | 14 +- .../actions/action_thumbnail_to_childern.py | 6 +- .../events/event_task_to_parent_status.py | 4 +- .../ftrack/lib/ftrack_action_handler.py | 167 ++++++++++++------ .../modules/ftrack/lib/ftrack_base_handler.py | 7 +- 14 files changed, 190 insertions(+), 109 deletions(-) diff --git a/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py b/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py index dc3a638192..f9824ec8ea 100644 --- a/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py +++ b/pype/modules/ftrack/actions/action_clean_hierarchical_attributes.py @@ -9,7 +9,6 @@ class CleanHierarchicalAttrsAction(BaseAction): label = "Pype Admin" variant = "- Clean hierarchical custom attributes" description = "Unset empty hierarchical attribute values." - role_list = ["Pypeclub", "Administrator", "Project Manager"] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") all_project_entities_query = ( @@ -20,12 +19,17 @@ class CleanHierarchicalAttrsAction(BaseAction): "select value, entity_id from CustomAttributeValue " "where entity_id in ({}) and configuration_id is \"{}\"" ) + settings_key = "clean_hierarchical_attr" def discover(self, session, entities, event): """Show only on project entity.""" - if len(entities) == 1 and entities[0].entity_type.lower() == "project": - return True - return False + if ( + len(entities) != 1 + or entities[0].entity_type.lower() != "project" + ): + return False + + return self.valid_roles(session, entities, event) def launch(self, session, entities, event): project = entities[0] diff --git a/pype/modules/ftrack/actions/action_create_cust_attrs.py b/pype/modules/ftrack/actions/action_create_cust_attrs.py index 9d6c16b556..a6601775f1 100644 --- a/pype/modules/ftrack/actions/action_create_cust_attrs.py +++ b/pype/modules/ftrack/actions/action_create_cust_attrs.py @@ -131,9 +131,8 @@ class CustomAttributes(BaseAction): variant = '- Create/Update Avalon Attributes' #: Action description. description = 'Creates Avalon/Mongo ID for double check' - #: roles that are allowed to register this action - role_list = ['Pypeclub', 'Administrator'] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + settings_key = "create_update_attributes" required_keys = ("key", "label", "type") @@ -150,7 +149,7 @@ class CustomAttributes(BaseAction): Validation - action is only for Administrators ''' - return True + return self.valid_roles(session, entities, event) def launch(self, session, entities, event): # JOB SETTINGS diff --git a/pype/modules/ftrack/actions/action_delete_asset.py b/pype/modules/ftrack/actions/action_delete_asset.py index 4720273c81..3bdbbe2470 100644 --- a/pype/modules/ftrack/actions/action_delete_asset.py +++ b/pype/modules/ftrack/actions/action_delete_asset.py @@ -18,8 +18,8 @@ class DeleteAssetSubset(BaseAction): #: Action description. description = "Removes from Avalon with all childs and asset from Ftrack" icon = statics_icon("ftrack", "action_icons", "DeleteAsset.svg") - #: roles that are allowed to register this action - role_list = ["Pypeclub", "Administrator", "Project Manager"] + + settings_key = "delete_asset_subset" #: Db connection dbcon = AvalonMongoDB() @@ -32,17 +32,21 @@ class DeleteAssetSubset(BaseAction): """ Validation """ task_ids = [] for ent_info in event["data"]["selection"]: - entType = ent_info.get("entityType", "") - if entType == "task": + if ent_info.get("entityType") == "task": task_ids.append(ent_info["entityId"]) + is_valid = False for entity in entities: - ftrack_id = entity["id"] - if ftrack_id not in task_ids: - continue - if entity.entity_type.lower() != "task": - return True - return False + if ( + entity["id"] in task_ids + and entity.entity_type.lower() != "task" + ): + is_valid = True + break + + if is_valid: + is_valid = self.valid_roles(session, entities, event) + return is_valid def _launch(self, event): try: diff --git a/pype/modules/ftrack/actions/action_delete_old_versions.py b/pype/modules/ftrack/actions/action_delete_old_versions.py index 31d15da9e5..e1c1e173a3 100644 --- a/pype/modules/ftrack/actions/action_delete_old_versions.py +++ b/pype/modules/ftrack/actions/action_delete_old_versions.py @@ -21,7 +21,6 @@ class DeleteOldVersions(BaseAction): "Delete files from older publishes so project can be" " archived with only lates versions." ) - role_list = ["Pypeclub", "Project Manager", "Administrator"] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") dbcon = AvalonMongoDB() @@ -31,13 +30,16 @@ class DeleteOldVersions(BaseAction): sequence_splitter = "__sequence_splitter__" def discover(self, session, entities, event): - ''' Validation ''' - selection = event["data"].get("selection") or [] - for entity in selection: - entity_type = (entity.get("entityType") or "").lower() - if entity_type == "assetversion": - return True - return False + """ Validation. """ + is_valid = False + for entity in entities: + if entity.entity_type.lower() == "assetversion": + is_valid = True + break + + if is_valid: + is_valid = self.valid_roles(session, entities, event) + return is_valid def interface(self, session, entities, event): # TODO Add roots existence validation diff --git a/pype/modules/ftrack/actions/action_delivery.py b/pype/modules/ftrack/actions/action_delivery.py index 853fe64ec7..e9e939bb47 100644 --- a/pype/modules/ftrack/actions/action_delivery.py +++ b/pype/modules/ftrack/actions/action_delivery.py @@ -23,6 +23,7 @@ class Delivery(BaseAction): description = "Deliver data to client" role_list = ["Pypeclub", "Administrator", "Project manager"] icon = statics_icon("ftrack", "action_icons", "Delivery.svg") + settings_key = "delivery_action" def __init__(self, *args, **kwargs): self.db_con = AvalonMongoDB() @@ -30,11 +31,15 @@ class Delivery(BaseAction): super(Delivery, self).__init__(*args, **kwargs) def discover(self, session, entities, event): + is_valid = False for entity in entities: if entity.entity_type.lower() == "assetversion": - return True + is_valid = True + break - return False + if is_valid: + is_valid = self.valid_roles(session, entities, event) + return is_valid def interface(self, session, entities, event): if event["data"].get("values", {}): diff --git a/pype/modules/ftrack/actions/action_job_killer.py b/pype/modules/ftrack/actions/action_job_killer.py index cb193b88ce..1ddd1383a7 100644 --- a/pype/modules/ftrack/actions/action_job_killer.py +++ b/pype/modules/ftrack/actions/action_job_killer.py @@ -13,13 +13,12 @@ class JobKiller(BaseAction): #: Action description. description = 'Killing selected running jobs' #: roles that are allowed to register this action - role_list = ['Pypeclub', 'Administrator'] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + settings_key = "job_killer" def discover(self, session, entities, event): ''' Validation ''' - - return True + return self.valid_roles(session, entities, event) def interface(self, session, entities, event): if not event['data'].get('values', {}): diff --git a/pype/modules/ftrack/actions/action_prepare_project.py b/pype/modules/ftrack/actions/action_prepare_project.py index 98493f65c7..3a955067d8 100644 --- a/pype/modules/ftrack/actions/action_prepare_project.py +++ b/pype/modules/ftrack/actions/action_prepare_project.py @@ -16,22 +16,23 @@ class PrepareProject(BaseAction): #: Action description. description = 'Set basic attributes on the project' #: roles that are allowed to register this action - role_list = ["Pypeclub", "Administrator", "Project manager"] icon = statics_icon("ftrack", "action_icons", "PrepareProject.svg") + settings_key = "prepare_project" + # Key to store info about trigerring create folder structure create_project_structure_key = "create_folder_structure" item_splitter = {'type': 'label', 'value': '---'} def discover(self, session, entities, event): ''' Validation ''' - if len(entities) != 1: + if ( + len(entities) != 1 + or entities[0].entity_type.lower() != "project" + ): return False - if entities[0].entity_type.lower() != "project": - return False - - return True + return self.valid_roles(session, entities, event) def interface(self, session, entities, event): if event['data'].get('values', {}): diff --git a/pype/modules/ftrack/actions/action_seed.py b/pype/modules/ftrack/actions/action_seed.py index 2610a25024..549afc660c 100644 --- a/pype/modules/ftrack/actions/action_seed.py +++ b/pype/modules/ftrack/actions/action_seed.py @@ -15,7 +15,6 @@ class SeedDebugProject(BaseAction): #: priority priority = 100 #: roles that are allowed to register this action - role_list = ["Pypeclub"] icon = statics_icon("ftrack", "action_icons", "SeedProject.svg") # Asset names which will be created in `Assets` entity @@ -58,9 +57,12 @@ class SeedDebugProject(BaseAction): existing_projects = None new_project_item = "< New Project >" current_project_item = "< Current Project >" + settings_key = "seed_project" def discover(self, session, entities, event): ''' Validation ''' + if not self.valid_roles(session, entities, event): + return False return True def interface(self, session, entities, event): diff --git a/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py b/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py index 6df8271381..84f857e37a 100644 --- a/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py +++ b/pype/modules/ftrack/actions/action_store_thumbnails_to_avalon.py @@ -21,8 +21,8 @@ class StoreThumbnailsToAvalon(BaseAction): # Action description description = 'Test action' # roles that are allowed to register this action - role_list = ["Pypeclub", "Administrator", "Project Manager"] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + settings_key = "store_thubmnail_to_avalon" thumbnail_key = "AVALON_THUMBNAIL_ROOT" @@ -31,10 +31,15 @@ class StoreThumbnailsToAvalon(BaseAction): super(StoreThumbnailsToAvalon, self).__init__(*args, **kwargs) def discover(self, session, entities, event): + is_valid = False for entity in entities: if entity.entity_type.lower() == "assetversion": - return True - return False + is_valid = True + break + + if is_valid: + is_valid = self.valid_roles(session, entities, event) + return is_valid def launch(self, session, entities, event): user = session.query( diff --git a/pype/modules/ftrack/actions/action_sync_to_avalon.py b/pype/modules/ftrack/actions/action_sync_to_avalon.py index 6077511092..b86b469d1c 100644 --- a/pype/modules/ftrack/actions/action_sync_to_avalon.py +++ b/pype/modules/ftrack/actions/action_sync_to_avalon.py @@ -41,20 +41,26 @@ class SyncToAvalonLocal(BaseAction): #: priority priority = 200 #: roles that are allowed to register this action - role_list = ["Pypeclub"] icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + settings_key = "sync_to_avalon_local" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.entities_factory = SyncEntitiesFactory(self.log, self.session) def discover(self, session, entities, event): - ''' Validation ''' + """ Validate selection. """ + is_valid = False for ent in event["data"]["selection"]: # Ignore entities that are not tasks or projects if ent["entityType"].lower() in ["show", "task"]: - return True - return False + is_valid = True + break + + if is_valid: + is_valid = self.valid_roles(session, entities, event) + return is_valid def launch(self, session, in_entities, event): time_start = time.time() diff --git a/pype/modules/ftrack/actions/action_thumbnail_to_childern.py b/pype/modules/ftrack/actions/action_thumbnail_to_childern.py index 604688d221..b90dfa027c 100644 --- a/pype/modules/ftrack/actions/action_thumbnail_to_childern.py +++ b/pype/modules/ftrack/actions/action_thumbnail_to_childern.py @@ -15,11 +15,9 @@ class ThumbToChildren(BaseAction): icon = statics_icon("ftrack", "action_icons", "Thumbnail.svg") def discover(self, session, entities, event): - ''' Validation ''' - - if (len(entities) != 1 or entities[0].entity_type in ['Project']): + """Show only on project.""" + if (len(entities) != 1 or entities[0].entity_type in ["Project"]): return False - return True def launch(self, session, entities, event): diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index 2bb7be1a26..30c995495e 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -61,8 +61,8 @@ class TaskStatusToParent(BaseEvent): session, event, project_id ) # Load settings - project_settings = self.get_settings_for_project( - session, event, project_entity=project_entity + project_settings = self.get_project_settings_from_event( + event, project_entity ) # Prepare loaded settings and check if can be processed diff --git a/pype/modules/ftrack/lib/ftrack_action_handler.py b/pype/modules/ftrack/lib/ftrack_action_handler.py index aa6cb7c1b0..11952cf3c0 100644 --- a/pype/modules/ftrack/lib/ftrack_action_handler.py +++ b/pype/modules/ftrack/lib/ftrack_action_handler.py @@ -29,6 +29,8 @@ class BaseAction(BaseHandler): icon = None type = 'Action' + settings_frack_subkey = "user_handlers" + def __init__(self, session): '''Expects a ftrack_api.Session instance''' if self.label is None: @@ -88,63 +90,6 @@ class BaseAction(BaseHandler): }] } - @classmethod - def get_user_entity_from_event(cls, session, event): - """Query user entity from event.""" - not_set = object() - - # Check if user is already stored in event data - user_entity = event["data"].get("user_entity", not_set) - if user_entity is not_set: - # Query user entity from event - user_info = event.get("source", {}).get("user", {}) - user_id = user_info.get("id") - username = user_info.get("username") - if user_id: - user_entity = session.query( - "User where id is {}".format(user_id) - ).first() - if not user_entity and username: - user_entity = session.query( - "User where username is {}".format(username) - ).first() - event["data"]["user_entity"] = user_entity - - return user_entity - - @classmethod - def get_user_roles_from_event(cls, session, event): - """Query user entity from event.""" - not_set = object() - - user_roles = event["data"].get("user_roles", not_set) - if user_roles is not_set: - user_roles = [] - user_entity = cls.get_user_entity_from_event(session, event) - for role in user_entity["user_security_roles"]: - user_roles.append(role["security_role"]["name"].lower()) - event["data"]["user_roles"] = user_roles - return user_roles - - def get_project_entity_from_event(self, session, event, entities): - """Load or query and fill project entity from/to event data. - - Project data are stored by ftrack id because in most cases it is - easier to access project id than project name. - - Args: - session (ftrack_api.Session): Current session. - event (ftrack_api.Event): Processed event by session. - entities (list): Ftrack entities of selection. - """ - - # Try to get project entity from event - project_entity = event["data"].get("project_entity") - if not project_entity: - project_entity = self.get_project_from_entity(entities[0]) - event["data"]["project_entity"] = project_entity - return project_entity - def discover(self, session, entities, event): '''Return true if we can handle the selected entities. @@ -253,6 +198,112 @@ class BaseAction(BaseHandler): return result + @staticmethod + def roles_check(settings_roles, user_roles, default=True): + """Compare roles from setting and user's roles. + + Args: + settings_roles(list): List of role names from settings. + user_roles(list): User's lowered role names. + default(bool): If `settings_roles` is empty list. + + Returns: + bool: `True` if user has at least one role from settings or + default if `settings_roles` is empty. + """ + if not settings_roles: + return default + + for role_name in settings_roles: + if role_name.lower() in user_roles: + return True + return False + + @classmethod + def get_user_entity_from_event(cls, session, event): + """Query user entity from event.""" + not_set = object() + + # Check if user is already stored in event data + user_entity = event["data"].get("user_entity", not_set) + if user_entity is not_set: + # Query user entity from event + user_info = event.get("source", {}).get("user", {}) + user_id = user_info.get("id") + username = user_info.get("username") + if user_id: + user_entity = session.query( + "User where id is {}".format(user_id) + ).first() + if not user_entity and username: + user_entity = session.query( + "User where username is {}".format(username) + ).first() + event["data"]["user_entity"] = user_entity + + return user_entity + + @classmethod + def get_user_roles_from_event(cls, session, event): + """Query user entity from event.""" + not_set = object() + + user_roles = event["data"].get("user_roles", not_set) + if user_roles is not_set: + user_roles = [] + user_entity = cls.get_user_entity_from_event(session, event) + for role in user_entity["user_security_roles"]: + user_roles.append(role["security_role"]["name"].lower()) + event["data"]["user_roles"] = user_roles + return user_roles + + def get_project_entity_from_event(self, session, event, entities): + """Load or query and fill project entity from/to event data. + + Project data are stored by ftrack id because in most cases it is + easier to access project id than project name. + + Args: + session (ftrack_api.Session): Current session. + event (ftrack_api.Event): Processed event by session. + entities (list): Ftrack entities of selection. + """ + + # Try to get project entity from event + project_entity = event["data"].get("project_entity") + if not project_entity: + project_entity = self.get_project_from_entity( + entities[0], session + ) + event["data"]["project_entity"] = project_entity + return project_entity + + def get_ftrack_settings(self, session, event, entities): + project_entity = self.get_project_entity_from_event( + session, event, entities + ) + project_settings = self.get_project_settings_from_event( + event, project_entity + ) + return project_settings["ftrack"] + + def valid_roles(self, session, entities, event): + """Validate user roles by settings. + + Method requires to have set `settings_key` attribute. + """ + ftrack_settings = self.get_ftrack_settings(session, event, entities) + settings = ( + ftrack_settings[self.settings_frack_subkey][self.settings_key] + ) + if not settings.get("enabled", True): + return False + + user_role_list = self.get_user_roles_from_event(session, event) + if not self.roles_check(settings.get("role_list"), user_role_list): + return False + return True + class ServerAction(BaseAction): """Action class meant to be used on event server. @@ -261,6 +312,8 @@ class ServerAction(BaseAction): For the same reason register is modified to not filter topics by username. """ + settings_frack_subkey = "events" + def register(self): """Register subcription to Ftrack event hub.""" self.session.event_hub.subscribe( diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/pype/modules/ftrack/lib/ftrack_base_handler.py index 00a3cd8cdc..2a8f400101 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/pype/modules/ftrack/lib/ftrack_base_handler.py @@ -528,7 +528,7 @@ class BaseHandler(object): "Publishing event: {}" ).format(str(event.__dict__))) - def get_project_from_entity(self, entity): + def get_project_from_entity(self, entity, session=None): low_entity_type = entity.entity_type.lower() if low_entity_type == "project": return entity @@ -549,7 +549,10 @@ class BaseHandler(object): return parent["project"] project_data = entity["link"][0] - return self.session.query( + + if session is None: + session = self.session + return session.query( "Project where id is {}".format(project_data["id"]) ).one()