def launch(self, session, event): ca_mongoid = lib.get_ca_mongoid() # If mongo_id textfield has changed: RETURN! # - infinite loop for ent in event['data']['entities']: if ent.get('keys') is not None: if ca_mongoid in ent['keys']: return entities = self._get_entities(session, event, self.ignore_entityType) ft_project = None # get project for entity in entities: try: base_proj = entity['link'][0] except Exception: continue ft_project = session.get(base_proj['type'], base_proj['id']) break # check if project is set to auto-sync if (ft_project is None or 'avalon_auto_sync' not in ft_project['custom_attributes'] or ft_project['custom_attributes']['avalon_auto_sync'] is False): return # check if project have Custom Attribute 'avalon_mongo_id' if ca_mongoid not in ft_project['custom_attributes']: message = ( "Custom attribute '{}' for 'Project' is not created" " or don't have set permissions for API").format(ca_mongoid) self.log.warning(message) self.show_message(event, message, False) return # get avalon project if possible import_entities = [] custom_attributes = lib.get_avalon_attr(session) avalon_project = lib.get_avalon_project(ft_project) if avalon_project is None: import_entities.append(ft_project) for entity in entities: if entity.entity_type.lower() in ['task']: entity = entity['parent'] if 'custom_attributes' not in entity: continue if ca_mongoid not in entity['custom_attributes']: message = ("Custom attribute '{}' for '{}' is not created" " or don't have set permissions for API").format( ca_mongoid, entity.entity_type) self.log.warning(message) self.show_message(event, message, False) return if entity not in import_entities: import_entities.append(entity) if len(import_entities) < 1: return try: for entity in import_entities: result = lib.import_to_avalon( session=session, entity=entity, ft_project=ft_project, av_project=avalon_project, custom_attributes=custom_attributes) if 'errors' in result and len(result['errors']) > 0: session.commit() lib.show_errors(self, event, result['errors']) return if avalon_project is None: if 'project' in result: avalon_project = result['project'] except Exception as e: session.reset() # reset session to clear it message = str(e) title = 'Hey You! Unknown Error has been raised! (*look below*)' ftrack_message = ('SyncToAvalon event ended with unexpected error' ' please check log file or contact Administrator' ' for more information.') items = [{ 'type': 'label', 'value': '# Fatal Error' }, { 'type': 'label', 'value': '<p>{}</p>'.format(ftrack_message) }] self.show_interface(items, title, event=event) self.log.error('Fatal error during sync: {}'.format(message), exc_info=True) return
def interface(self, session, entities, event): if event['data'].get('values', {}): return # Inform user that this may take a while self.show_message(event, "Preparing data... Please wait", True) self.log.debug("Loading custom attributes") cust_attrs, hier_cust_attrs = get_avalon_attr(session, True) project_defaults = config.get_presets(entities[0]["full_name"]).get( "ftrack", {}).get("project_defaults", {}) self.log.debug("Preparing data which will be shown") attributes_to_set = {} for attr in hier_cust_attrs: key = attr["key"] attributes_to_set[key] = { "label": attr["label"], "object": attr, "default": project_defaults.get(key) } for attr in cust_attrs: if attr["entity_type"].lower() != "show": continue key = attr["key"] attributes_to_set[key] = { "label": attr["label"], "object": attr, "default": project_defaults.get(key) } # Sort by label attributes_to_set = dict( sorted(attributes_to_set.items(), key=lambda x: x[1]["label"])) self.log.debug("Preparing interface for keys: \"{}\"".format( str([key for key in attributes_to_set]))) item_splitter = {'type': 'label', 'value': '---'} title = "Prepare Project" items = [] # Ask if want to trigger Action Create Folder Structure items.append({ "type": "label", "value": "<h3>Want to create basic Folder Structure?</h3>" }) items.append({ "name": self.create_project_structure_key, "type": "boolean", "value": False, "label": "Check if Yes" }) items.append(item_splitter) items.append({ "type": "label", "value": "<h3>Set basic Attributes:</h3>" }) multiselect_enumerators = [] # This item will be last (before enumerators) # - sets value of auto synchronization auto_sync_name = "avalon_auto_sync" auto_sync_item = { "name": auto_sync_name, "type": "boolean", "value": project_defaults.get(auto_sync_name, False), "label": "AutoSync to Avalon" } for key, in_data in attributes_to_set.items(): attr = in_data["object"] # initial item definition item = {"name": key, "label": in_data["label"]} # cust attr type - may have different visualization type_name = attr["type"]["name"].lower() easy_types = ["text", "boolean", "date", "number"] easy_type = False if type_name in easy_types: easy_type = True elif type_name == "enumerator": attr_config = json.loads(attr["config"]) attr_config_data = json.loads(attr_config["data"]) if attr_config["multiSelect"] is True: multiselect_enumerators.append(item_splitter) multiselect_enumerators.append({ "type": "label", "value": in_data["label"] }) default = in_data["default"] names = [] for option in sorted(attr_config_data, key=lambda x: x["menu"]): name = option["value"] new_name = "__{}__{}".format(key, name) names.append(new_name) item = { "name": new_name, "type": "boolean", "label": "- {}".format(option["menu"]) } if default: if (isinstance(default, list) or isinstance(default, tuple)): if name in default: item["value"] = True else: if name == default: item["value"] = True multiselect_enumerators.append(item) multiselect_enumerators.append({ "type": "hidden", "name": "__hidden__{}".format(key), "value": json.dumps(names) }) else: easy_type = True item["data"] = attr_config_data else: self.log.warning( ("Custom attribute \"{}\" has type \"{}\"." " I don't know how to handle").format(key, type_name)) items.append({ "type": "label", "value": ("!!! Can't handle Custom attritubte type \"{}\"" " (key: \"{}\")").format(type_name, key) }) if easy_type: item["type"] = type_name # default value in interface default = in_data["default"] if default is not None: item["value"] = default items.append(item) # Add autosync attribute items.append(auto_sync_item) # Add enumerator items at the end for item in multiselect_enumerators: items.append(item) return {'items': items, 'title': title}
def launch(self, session, entities, event): time_start = time.time() message = "" # JOB SETTINGS userId = event['source']['user']['id'] user = session.query('User where id is ' + userId).one() job = session.create( 'Job', { 'user': user, 'status': 'running', 'data': json.dumps({'description': 'Sync Ftrack to Avalon.'}) }) session.commit() try: self.log.debug("Preparing entities for synchronization") if entities[0].entity_type.lower() == "project": ft_project_name = entities[0]["full_name"] else: ft_project_name = entities[0]["project"]["full_name"] project_entities = session.query( self.entities_query.format(ft_project_name)).all() ft_project = session.query( self.project_query.format(ft_project_name)).one() entities_by_id = {} entities_by_parent = collections.defaultdict(list) entities_by_id[ft_project["id"]] = ft_project for ent in project_entities: entities_by_id[ent["id"]] = ent entities_by_parent[ent["parent_id"]].append(ent) importable = [] for ent_info in event["data"]["selection"]: ent = entities_by_id[ent_info["entityId"]] for link_ent_info in ent["link"]: link_ent = entities_by_id[link_ent_info["id"]] if (ent.entity_type.lower() in self.ignore_entity_types or link_ent in importable): continue importable.append(link_ent) def add_children(parent_id): ents = entities_by_parent[parent_id] for ent in ents: if ent.entity_type.lower() in self.ignore_entity_types: continue if ent not in importable: importable.append(ent) add_children(ent["id"]) # add children of selection to importable for ent_info in event["data"]["selection"]: add_children(ent_info["entityId"]) # Check names: REGEX in schema/duplicates - raise error if found all_names = [] duplicates = [] for entity in importable: lib.avalon_check_name(entity) if entity.entity_type.lower() == "project": continue if entity['name'] in all_names: duplicates.append("'{}'".format(entity['name'])) else: all_names.append(entity['name']) if len(duplicates) > 0: # TODO Show information to user and return False raise ValueError("Entity name duplication: {}".format( ", ".join(duplicates))) # ----- PROJECT ------ avalon_project = lib.get_avalon_project(ft_project) custom_attributes = lib.get_avalon_attr(session) # Import all entities to Avalon DB for entity in importable: result = lib.import_to_avalon( session=session, entity=entity, ft_project=ft_project, av_project=avalon_project, custom_attributes=custom_attributes) # TODO better error handling # maybe split into critical, warnings and messages? if 'errors' in result and len(result['errors']) > 0: job['status'] = 'failed' session.commit() lib.show_errors(self, event, result['errors']) return { 'success': False, 'message': "Sync to avalon FAILED" } if avalon_project is None: if 'project' in result: avalon_project = result['project'] job['status'] = 'done' session.commit() except ValueError as ve: # TODO remove this part!!!! job['status'] = 'failed' session.commit() message = str(ve) self.log.error('Error during syncToAvalon: {}'.format(message), exc_info=True) except Exception as e: job['status'] = 'failed' session.commit() exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] log_message = "{}/{}/Line: {}".format(exc_type, fname, exc_tb.tb_lineno) self.log.error('Error during syncToAvalon: {}'.format(log_message), exc_info=True) # TODO add traceback to message and show to user message = ('Unexpected Error' ' - Please check Log for more information') finally: if job['status'] in ['queued', 'running']: job['status'] = 'failed' session.commit() time_end = time.time() self.log.debug("Synchronization took \"{}\"".format( str(time_end - time_start))) if job["status"] != "failed": self.log.debug("Triggering Sync hierarchical attributes") self.trigger_action("sync.hierarchical.attrs", event) if len(message) > 0: message = "Unable to sync: {}".format(message) return {'success': False, 'message': message} return {'success': True, 'message': "Synchronization was successfull"}
def launch(self, session, entities, event): message = "" # JOB SETTINGS userId = event['source']['user']['id'] user = session.query('User where id is ' + userId).one() job = session.create( 'Job', { 'user': user, 'status': 'running', 'data': json.dumps({'description': 'Sync Ftrack to Avalon.'}) }) session.commit() try: self.importable = [] # get from top entity in hierarchy all parent entities top_entity = entities[0]['link'] if len(top_entity) > 1: for e in top_entity: parent_entity = session.get(e['type'], e['id']) self.importable.append(parent_entity) # get all child entities separately/unique for entity in entities: self.add_childs_to_importable(entity) # Check names: REGEX in schema/duplicates - raise error if found all_names = [] duplicates = [] for entity in self.importable: ftracklib.avalon_check_name(entity) if entity['name'] in all_names: duplicates.append("'{}'".format(e['name'])) else: all_names.append(entity['name']) if len(duplicates) > 0: raise ValueError("Entity name duplication: {}".format( ", ".join(duplicates))) # ----- PROJECT ------ # store Ftrack project- self.importable[0] must be project entity!! ft_project = self.importable[0] avalon_project = ftracklib.get_avalon_project(ft_project) custom_attributes = ftracklib.get_avalon_attr(session) # Import all entities to Avalon DB for entity in self.importable: result = ftracklib.import_to_avalon( session=session, entity=entity, ft_project=ft_project, av_project=avalon_project, custom_attributes=custom_attributes) if 'errors' in result and len(result['errors']) > 0: job['status'] = 'failed' session.commit() ftracklib.show_errors(self, event, result['errors']) return { 'success': False, 'message': "Sync to avalon FAILED" } if avalon_project is None: if 'project' in result: avalon_project = result['project'] job['status'] = 'done' except ValueError as ve: job['status'] = 'failed' message = str(ve) self.log.error('Error during syncToAvalon: {}'.format(message)) except Exception as e: job['status'] = 'failed' exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] log_message = "{}/{}/Line: {}".format(exc_type, fname, exc_tb.tb_lineno) self.log.error('Error during syncToAvalon: {}'.format(log_message)) message = ('Unexpected Error' ' - Please check Log for more information') finally: if job['status'] in ['queued', 'running']: job['status'] = 'failed' session.commit() if len(message) > 0: message = "Unable to sync: {}".format(message) return {'success': False, 'message': message} return {'success': True, 'message': "Synchronization was successfull"}