mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
- added show_message to BaseAction
- added priorities to register - test action show only to Pypeclub user - custom attributes can be created from json file (instructions inside)
This commit is contained in:
parent
46a1f54b82
commit
a4ec8b54f5
5 changed files with 627 additions and 254 deletions
|
|
@ -1,246 +0,0 @@
|
||||||
# :coding: utf-8
|
|
||||||
# :copyright: Copyright (c) 2017 ftrack
|
|
||||||
import sys
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
import ftrack_api
|
|
||||||
from ftrack_action_handler import BaseAction
|
|
||||||
|
|
||||||
from avalon import io, inventory, lib
|
|
||||||
from avalon.vendor import toml
|
|
||||||
|
|
||||||
|
|
||||||
class AvalonIdAttribute(BaseAction):
|
|
||||||
'''Edit meta data action.'''
|
|
||||||
|
|
||||||
#: Action identifier.
|
|
||||||
identifier = 'avalon.id.attribute'
|
|
||||||
#: Action label.
|
|
||||||
label = 'Create Avalon Attribute'
|
|
||||||
#: Action description.
|
|
||||||
description = 'Creates Avalon/Mongo ID for double check'
|
|
||||||
|
|
||||||
|
|
||||||
def discover(self, session, entities, event):
|
|
||||||
'''
|
|
||||||
Validation
|
|
||||||
- action is only for Administrators
|
|
||||||
'''
|
|
||||||
success = False
|
|
||||||
userId = event['source']['user']['id']
|
|
||||||
user = session.query('User where id is ' + userId).one()
|
|
||||||
for role in user['user_security_roles']:
|
|
||||||
if role['security_role']['name'] == 'Administrator':
|
|
||||||
success = True
|
|
||||||
|
|
||||||
return success
|
|
||||||
|
|
||||||
|
|
||||||
def launch(self, session, entities, event):
|
|
||||||
# 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': 'Custom Attribute creation.'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
session.commit()
|
|
||||||
try:
|
|
||||||
# Checkbox for event sync
|
|
||||||
cbxSyncName = 'avalon_auto_sync'
|
|
||||||
cbxSyncLabel = 'Avalon auto-sync'
|
|
||||||
cbxSyncExist = False
|
|
||||||
|
|
||||||
# Attribute Name and Label
|
|
||||||
custAttrName = 'avalon_mongo_id'
|
|
||||||
custAttrLabel = 'Avalon/Mongo Id'
|
|
||||||
|
|
||||||
attrs_update = set()
|
|
||||||
# Types that don't need object_type_id
|
|
||||||
base = {'show'}
|
|
||||||
|
|
||||||
# Don't create custom attribute on these entity types:
|
|
||||||
exceptions = ['task', 'milestone']
|
|
||||||
exceptions.extend(base)
|
|
||||||
# Get all possible object types
|
|
||||||
all_obj_types = session.query('ObjectType').all()
|
|
||||||
count_types = len(all_obj_types)
|
|
||||||
# Filter object types by exceptions
|
|
||||||
for index in range(count_types):
|
|
||||||
i = count_types - 1 - index
|
|
||||||
name = all_obj_types[i]['name'].lower()
|
|
||||||
|
|
||||||
if " " in name:
|
|
||||||
name = name.replace(" ","")
|
|
||||||
|
|
||||||
if name in exceptions:
|
|
||||||
all_obj_types.pop(i)
|
|
||||||
|
|
||||||
# Get IDs of filtered object types
|
|
||||||
all_obj_types_id = set()
|
|
||||||
|
|
||||||
for obj in all_obj_types:
|
|
||||||
all_obj_types_id.add(obj['id'])
|
|
||||||
|
|
||||||
# Get all custom attributes
|
|
||||||
current_cust_attr = session.query('CustomAttributeConfiguration').all()
|
|
||||||
# Filter already existing AvalonMongoID attr.
|
|
||||||
for attr in current_cust_attr:
|
|
||||||
if attr['key'] == cbxSyncName:
|
|
||||||
cbxSyncExist = True
|
|
||||||
cbxAttribute = attr
|
|
||||||
if attr['key'] == custAttrName:
|
|
||||||
if attr['entity_type'] in base:
|
|
||||||
base.remove(attr['entity_type'])
|
|
||||||
attrs_update.add(attr)
|
|
||||||
if attr['object_type_id'] in all_obj_types_id:
|
|
||||||
all_obj_types_id.remove(attr['object_type_id'])
|
|
||||||
attrs_update.add(attr)
|
|
||||||
|
|
||||||
# Set session back to begin("session.query" raises error on commit)
|
|
||||||
session.rollback()
|
|
||||||
# Set security roles for attribute
|
|
||||||
role_api = session.query('SecurityRole where name is "API"').one()
|
|
||||||
role_admin = session.query('SecurityRole where name is "Administrator"').one()
|
|
||||||
roles = [role_api,role_admin]
|
|
||||||
|
|
||||||
# Set Text type of Attribute
|
|
||||||
custom_attribute_type = session.query(
|
|
||||||
'CustomAttributeType where name is "text"'
|
|
||||||
).one()
|
|
||||||
# Get/Set 'avalon' group
|
|
||||||
groups = session.query('CustomAttributeGroup where name is "avalon"').all()
|
|
||||||
if len(groups) > 1:
|
|
||||||
msg = "There are more Custom attribute groups with name 'avalon'"
|
|
||||||
self.log.warning(msg)
|
|
||||||
return { 'success': False, 'message':msg }
|
|
||||||
|
|
||||||
elif len(groups) < 1:
|
|
||||||
group = session.create('CustomAttributeGroup', {
|
|
||||||
'name': 'avalon',
|
|
||||||
})
|
|
||||||
session.commit()
|
|
||||||
else:
|
|
||||||
group = groups[0]
|
|
||||||
|
|
||||||
# Checkbox for auto-sync event / Create or Update(roles + group)
|
|
||||||
if cbxSyncExist is False:
|
|
||||||
cbxType = session.query('CustomAttributeType where name is "boolean"').first()
|
|
||||||
session.create('CustomAttributeConfiguration', {
|
|
||||||
'entity_type': 'show',
|
|
||||||
'type': cbxType,
|
|
||||||
'label': cbxSyncLabel,
|
|
||||||
'key': cbxSyncName,
|
|
||||||
'default': False,
|
|
||||||
'write_security_roles': roles,
|
|
||||||
'read_security_roles': roles,
|
|
||||||
'group':group,
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
cbxAttribute['write_security_roles'] = roles
|
|
||||||
cbxAttribute['read_security_roles'] = roles
|
|
||||||
cbxAttribute['group'] = group
|
|
||||||
|
|
||||||
for entity_type in base:
|
|
||||||
# Create a custom attribute configuration.
|
|
||||||
session.create('CustomAttributeConfiguration', {
|
|
||||||
'entity_type': entity_type,
|
|
||||||
'type': custom_attribute_type,
|
|
||||||
'label': custAttrLabel,
|
|
||||||
'key': custAttrName,
|
|
||||||
'default': '',
|
|
||||||
'write_security_roles': roles,
|
|
||||||
'read_security_roles': roles,
|
|
||||||
'group':group,
|
|
||||||
'config': json.dumps({'markdown': False})
|
|
||||||
})
|
|
||||||
|
|
||||||
for type in all_obj_types_id:
|
|
||||||
# Create a custom attribute configuration.
|
|
||||||
session.create('CustomAttributeConfiguration', {
|
|
||||||
'entity_type': 'task',
|
|
||||||
'object_type_id': type,
|
|
||||||
'type': custom_attribute_type,
|
|
||||||
'label': custAttrLabel,
|
|
||||||
'key': custAttrName,
|
|
||||||
'default': '',
|
|
||||||
'write_security_roles': roles,
|
|
||||||
'read_security_roles': roles,
|
|
||||||
'group':group,
|
|
||||||
'config': json.dumps({'markdown': False})
|
|
||||||
})
|
|
||||||
|
|
||||||
for attr in attrs_update:
|
|
||||||
attr['write_security_roles'] = roles
|
|
||||||
attr['read_security_roles'] = roles
|
|
||||||
attr['group'] = group
|
|
||||||
|
|
||||||
job['status'] = 'done'
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
session.rollback()
|
|
||||||
job['status'] = 'failed'
|
|
||||||
session.commit()
|
|
||||||
self.log.error("Creating custom attributes failed ({})".format(e))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def register(session, **kw):
|
|
||||||
'''Register plugin. Called when used as an plugin.'''
|
|
||||||
|
|
||||||
# Validate that session is an instance of ftrack_api.Session. If not,
|
|
||||||
# assume that register is being called from an old or incompatible API and
|
|
||||||
# return without doing anything.
|
|
||||||
if not isinstance(session, ftrack_api.session.Session):
|
|
||||||
return
|
|
||||||
|
|
||||||
action_handler = AvalonIdAttribute(session)
|
|
||||||
action_handler.register()
|
|
||||||
|
|
||||||
|
|
||||||
def main(arguments=None):
|
|
||||||
'''Set up logging and register action.'''
|
|
||||||
if arguments is None:
|
|
||||||
arguments = []
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
# Allow setting of logging level from arguments.
|
|
||||||
loggingLevels = {}
|
|
||||||
for level in (
|
|
||||||
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
|
|
||||||
logging.ERROR, logging.CRITICAL
|
|
||||||
):
|
|
||||||
loggingLevels[logging.getLevelName(level).lower()] = level
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
'-v', '--verbosity',
|
|
||||||
help='Set the logging output verbosity.',
|
|
||||||
choices=loggingLevels.keys(),
|
|
||||||
default='info'
|
|
||||||
)
|
|
||||||
namespace = parser.parse_args(arguments)
|
|
||||||
|
|
||||||
# Set up basic logging
|
|
||||||
logging.basicConfig(level=loggingLevels[namespace.verbosity])
|
|
||||||
|
|
||||||
session = ftrack_api.Session()
|
|
||||||
register(session)
|
|
||||||
|
|
||||||
# Wait for events
|
|
||||||
logging.info(
|
|
||||||
'Registered actions and listening for events. Use Ctrl-C to abort.'
|
|
||||||
)
|
|
||||||
session.event_hub.wait()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
raise SystemExit(main(sys.argv[1:]))
|
|
||||||
576
pype/ftrack/actions/action_create_cust_attrs.py
Normal file
576
pype/ftrack/actions/action_create_cust_attrs.py
Normal file
|
|
@ -0,0 +1,576 @@
|
||||||
|
# :coding: utf-8
|
||||||
|
# :copyright: Copyright (c) 2017 ftrack
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import ftrack_api
|
||||||
|
from ftrack_action_handler import BaseAction
|
||||||
|
|
||||||
|
"""
|
||||||
|
This action creates/updates custom attributes.
|
||||||
|
- first part take care about avalon_mongo_id attribute
|
||||||
|
- second part is based on json file in templates:
|
||||||
|
~/PYPE-TEMPLATES/presets/ftrack/ftrack_custom_attributes.json
|
||||||
|
- you can add Custom attributes based on these conditions
|
||||||
|
|
||||||
|
*** Required ***************************************************************
|
||||||
|
|
||||||
|
label (string)
|
||||||
|
- label that will show in ftrack
|
||||||
|
|
||||||
|
key (string)
|
||||||
|
- must contain only chars [a-z0-9_]
|
||||||
|
|
||||||
|
type (string)
|
||||||
|
- type of custom attribute
|
||||||
|
- possibilities: text, boolean, date, enumerator, dynamic enumerator, number
|
||||||
|
|
||||||
|
*** Required with conditions ***********************************************
|
||||||
|
|
||||||
|
entity_type (string)
|
||||||
|
- if 'is_hierarchical' is set to False
|
||||||
|
- type of entity
|
||||||
|
- possibilities: task, show, assetversion, user, list, asset
|
||||||
|
|
||||||
|
config (dictionary)
|
||||||
|
- for each entity type different requirements and possibilities:
|
||||||
|
- enumerator: multiSelect = True/False(default: False)
|
||||||
|
data = {key_1:value_1,key_2:value_2,..,key_n:value_n}
|
||||||
|
- 'data' is Required value with enumerator
|
||||||
|
- 'key' must contain only chars [a-z0-9_]
|
||||||
|
|
||||||
|
- number: isdecimal = True/False(default: False)
|
||||||
|
|
||||||
|
- text: markdown = True/False(default: False)
|
||||||
|
|
||||||
|
object_type (string)
|
||||||
|
- IF ENTITY_TYPE is set to 'task'
|
||||||
|
- default possibilities: Folder, Shot, Sequence, Task, Library,
|
||||||
|
Milestone, Episode, Asset Build,...
|
||||||
|
|
||||||
|
*** Optional ***************************************************************
|
||||||
|
|
||||||
|
write_security_roles/read_security_roles (array of strings)
|
||||||
|
- default: ["ALL"]
|
||||||
|
- strings should be role names (e.g.: ["API", "Administrator"])
|
||||||
|
- if set to ["ALL"] - all roles will be availabled
|
||||||
|
- if first is 'except' - roles will be set to all except roles in array
|
||||||
|
- Warning: Be carefull with except - roles can be different by company
|
||||||
|
- example:
|
||||||
|
write_security_roles = ["except", "User"]
|
||||||
|
read_security_roles = ["ALL"]
|
||||||
|
- User is unable to write but can read
|
||||||
|
|
||||||
|
group (string)
|
||||||
|
- default: None
|
||||||
|
- name of group
|
||||||
|
|
||||||
|
default
|
||||||
|
- default: None
|
||||||
|
- sets default value for custom attribute:
|
||||||
|
- text -> string
|
||||||
|
- number -> integer
|
||||||
|
- enumerator -> array with string of key/s
|
||||||
|
- boolean -> bool true/false
|
||||||
|
- date -> string in format: 'YYYY.MM.DD' or 'YYYY.MM.DD HH:mm:ss'
|
||||||
|
- example: "2018.12.24" / "2018.1.1 6:0:0"
|
||||||
|
- dynamic enumerator -> DON'T HAVE DEFAULT VALUE!!!
|
||||||
|
|
||||||
|
is_hierarchical (bool)
|
||||||
|
- default: False
|
||||||
|
- will set hierachical attribute
|
||||||
|
- False by default
|
||||||
|
|
||||||
|
EXAMPLE:
|
||||||
|
{
|
||||||
|
"avalon_auto_sync": {
|
||||||
|
"label": "Avalon auto-sync",
|
||||||
|
"key": "avalon_auto_sync",
|
||||||
|
"type": "boolean",
|
||||||
|
"entity_type": "show",
|
||||||
|
"group": "avalon",
|
||||||
|
"default": false,
|
||||||
|
"write_security_role": ["API","Administrator"],
|
||||||
|
"read_security_role": ["API","Administrator"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CustAttrException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CustomAttributes(BaseAction):
|
||||||
|
'''Edit meta data action.'''
|
||||||
|
|
||||||
|
#: Action identifier.
|
||||||
|
identifier = 'create.update.attributes'
|
||||||
|
#: Action label.
|
||||||
|
label = 'Create/Update Avalon Attributes'
|
||||||
|
#: Action description.
|
||||||
|
description = 'Creates Avalon/Mongo ID for double check'
|
||||||
|
|
||||||
|
def __init__(self, session):
|
||||||
|
super().__init__(session)
|
||||||
|
|
||||||
|
templates = os.environ['PYPE_STUDIO_TEMPLATES']
|
||||||
|
path_items = [templates,'presets','ftrack', 'ftrack_custom_attributes.json']
|
||||||
|
self.filepath = os.path.sep.join(path_items)
|
||||||
|
# self.all_current_attributes = session.query('CustomAttributeConfiguration').all()
|
||||||
|
self.types = {}
|
||||||
|
self.object_type_ids = {}
|
||||||
|
self.groups = {}
|
||||||
|
self.security_roles = {}
|
||||||
|
self.required_keys = ['key', 'label', 'type']
|
||||||
|
self.type_posibilities = ['text', 'boolean', 'date', 'enumerator',
|
||||||
|
'dynamic enumerator', 'number']
|
||||||
|
|
||||||
|
def discover(self, session, entities, event):
|
||||||
|
'''
|
||||||
|
Validation
|
||||||
|
- action is only for Administrators
|
||||||
|
'''
|
||||||
|
success = False
|
||||||
|
userId = event['source']['user']['id']
|
||||||
|
user = session.query('User where id is ' + userId).one()
|
||||||
|
for role in user['user_security_roles']:
|
||||||
|
if role['security_role']['name'] == 'Administrator':
|
||||||
|
success = True
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def launch(self, session, entities, event):
|
||||||
|
# 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': 'Custom Attribute creation.'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
session.commit()
|
||||||
|
try:
|
||||||
|
self.avalon_mongo_id_attributes(session)
|
||||||
|
self.custom_attributes_from_file(session, event)
|
||||||
|
|
||||||
|
job['status'] = 'done'
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
session.rollback()
|
||||||
|
job['status'] = 'failed'
|
||||||
|
session.commit()
|
||||||
|
self.log.error("Creating custom attributes failed ({})".format(e))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def avalon_mongo_id_attributes(self, session):
|
||||||
|
# Attribute Name and Label
|
||||||
|
cust_attr_name = 'avalon_mongo_id'
|
||||||
|
cust_attr_label = 'Avalon/Mongo Id'
|
||||||
|
|
||||||
|
# Types that don't need object_type_id
|
||||||
|
base = {'show'}
|
||||||
|
|
||||||
|
# Don't create custom attribute on these entity types:
|
||||||
|
exceptions = ['task', 'milestone']
|
||||||
|
exceptions.extend(base)
|
||||||
|
|
||||||
|
# Get all possible object types
|
||||||
|
all_obj_types = session.query('ObjectType').all()
|
||||||
|
|
||||||
|
# Filter object types by exceptions
|
||||||
|
filtered_types_id = set()
|
||||||
|
|
||||||
|
for obj_type in all_obj_types:
|
||||||
|
name = obj_type['name']
|
||||||
|
if " " in name:
|
||||||
|
name = name.replace(" ","")
|
||||||
|
|
||||||
|
if obj_type['name'] not in self.object_type_ids:
|
||||||
|
self.object_type_ids[name] = obj_type['id']
|
||||||
|
|
||||||
|
if name.lower() not in exceptions:
|
||||||
|
filtered_types_id.add(obj_type['id'])
|
||||||
|
|
||||||
|
# Set security roles for attribute
|
||||||
|
role_list = ["API","Administrator"]
|
||||||
|
roles = self.get_security_role(role_list)
|
||||||
|
# Set Text type of Attribute
|
||||||
|
custom_attribute_type = self.get_type('text')
|
||||||
|
# Set group to 'avalon'
|
||||||
|
group = self.get_group('avalon')
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
data['key'] = cust_attr_name
|
||||||
|
data['label'] = cust_attr_label
|
||||||
|
data['type'] = custom_attribute_type
|
||||||
|
data['default'] = ''
|
||||||
|
data['write_security_roles'] = roles
|
||||||
|
data['read_security_roles'] = roles
|
||||||
|
data['group'] = group
|
||||||
|
data['config'] = json.dumps({'markdown': False})
|
||||||
|
|
||||||
|
for entity_type in base:
|
||||||
|
data['entity_type'] = entity_type
|
||||||
|
self.process_attribute(data)
|
||||||
|
|
||||||
|
data['entity_type'] = 'task'
|
||||||
|
for object_type_id in filtered_types_id:
|
||||||
|
data['object_type_id'] = str(object_type_id)
|
||||||
|
self.process_attribute(data)
|
||||||
|
|
||||||
|
def custom_attributes_from_file(self, session, event):
|
||||||
|
try:
|
||||||
|
with open(self.filepath) as data_file:
|
||||||
|
json_dict = json.load(data_file)
|
||||||
|
except Exception as e:
|
||||||
|
msg = 'Loading "Custom attribute file" Failed. Please check log for more information'
|
||||||
|
self.log.warning("{} - {}".format(msg,str(e)))
|
||||||
|
self.show_message(event, msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
for cust_attr_name in json_dict:
|
||||||
|
try:
|
||||||
|
data = {}
|
||||||
|
cust_attr = json_dict[cust_attr_name]
|
||||||
|
# Get key, label, type
|
||||||
|
data.update(self.get_required(cust_attr))
|
||||||
|
# Get hierachical/ entity_type/ object_id
|
||||||
|
data.update(self.get_entity_type(cust_attr))
|
||||||
|
# Get group, default, security roles
|
||||||
|
data.update(self.get_optional(cust_attr))
|
||||||
|
# Process data
|
||||||
|
self.process_attribute(data)
|
||||||
|
|
||||||
|
except CustAttrException as cae:
|
||||||
|
msg = 'Custom attribute error "{}" - {}'.format(cust_attr_name, str(cae))
|
||||||
|
self.log.warning(msg)
|
||||||
|
self.show_message(event, msg)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def process_attribute(self, data):
|
||||||
|
existing_atr = self.session.query('CustomAttributeConfiguration').all()
|
||||||
|
matching = []
|
||||||
|
for attr in existing_atr:
|
||||||
|
if (attr['key'] != data['key'] or
|
||||||
|
attr['type']['name'] != data['type']['name']):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'is_hierarchical' in data:
|
||||||
|
if data['is_hierarchical'] == attr['is_hierarchical']:
|
||||||
|
matching.append(attr)
|
||||||
|
elif 'object_type_id' in data:
|
||||||
|
if (attr['entity_type'] == data['entity_type'] and
|
||||||
|
attr['object_type_id'] == data['object_type_id']):
|
||||||
|
matching.append(attr)
|
||||||
|
else:
|
||||||
|
if attr['entity_type'] == data['entity_type']:
|
||||||
|
matching.append(attr)
|
||||||
|
|
||||||
|
if len(matching) == 0:
|
||||||
|
self.session.create('CustomAttributeConfiguration', data)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
elif len(matching) == 1:
|
||||||
|
attr_update = matching[0]
|
||||||
|
for key in data:
|
||||||
|
if key not in ['is_hierarchical','entity_type', 'object_type_id']:
|
||||||
|
attr_update[key] = data[key]
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise CustAttrException("Is duplicated")
|
||||||
|
|
||||||
|
|
||||||
|
def get_required(self, attr):
|
||||||
|
output = {}
|
||||||
|
for key in self.required_keys:
|
||||||
|
if key not in attr:
|
||||||
|
raise CustAttrException("Key {} is required - please set".format(key))
|
||||||
|
|
||||||
|
if attr['type'].lower() not in self.type_posibilities:
|
||||||
|
raise CustAttrException("Type {} is not valid".format(attr['type']))
|
||||||
|
|
||||||
|
type_name = attr['type'].lower()
|
||||||
|
|
||||||
|
output['key'] = attr['key']
|
||||||
|
output['label'] = attr['label']
|
||||||
|
output['type'] = self.get_type(type_name)
|
||||||
|
|
||||||
|
config = None
|
||||||
|
if type_name == 'number':
|
||||||
|
config = self.get_number_config(attr)
|
||||||
|
elif type_name == 'text':
|
||||||
|
config = self.get_text_config(attr)
|
||||||
|
elif type_name == 'enumerator':
|
||||||
|
config = self.get_enumerator_config(attr)
|
||||||
|
|
||||||
|
if config is not None:
|
||||||
|
output['config'] = config
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_number_config(self, attr):
|
||||||
|
if 'config' in attr and 'isdecimal' in attr['config']:
|
||||||
|
isdecimal = attr['config']['isdecimal']
|
||||||
|
else:
|
||||||
|
isdecimal = False
|
||||||
|
|
||||||
|
config = json.dumps({'isdecimal':isdecimal})
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_text_config(self, attr):
|
||||||
|
if 'config' in attr and 'markdown' in attr['config']:
|
||||||
|
markdown = attr['config']['markdown']
|
||||||
|
else:
|
||||||
|
markdown = False
|
||||||
|
config = json.dumps({'markdown':markdown})
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_enumerator_config(self,attr):
|
||||||
|
if 'config' not in attr:
|
||||||
|
raise CustAttrException("Missing config with data")
|
||||||
|
if 'data' not in attr['config']:
|
||||||
|
raise CustAttrException("Missing data in config")
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for item in attr['config']['data']:
|
||||||
|
item_data = {}
|
||||||
|
for key in item:
|
||||||
|
# TODO key check by regex
|
||||||
|
item_data['menu'] = item[key]
|
||||||
|
item_data['value'] = key
|
||||||
|
data.append(item_data)
|
||||||
|
|
||||||
|
if 'multiSelect' in attr['config']:
|
||||||
|
multiSelect = attr['config']['multiSelect']
|
||||||
|
else:
|
||||||
|
multiSelect = False
|
||||||
|
|
||||||
|
config = json.dumps({
|
||||||
|
'multiSelect':multiSelect,
|
||||||
|
'data': json.dumps(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def get_group(self, attr):
|
||||||
|
if isinstance(attr, str):
|
||||||
|
group_name = attr
|
||||||
|
else:
|
||||||
|
group_name = attr['group'].lower()
|
||||||
|
if group_name in self.groups:
|
||||||
|
return self.groups[group_name]
|
||||||
|
|
||||||
|
query = 'CustomAttributeGroup where name is "{}"'.format(group_name)
|
||||||
|
groups = self.session.query(query).all()
|
||||||
|
|
||||||
|
if len(groups) == 1:
|
||||||
|
group = groups[0]
|
||||||
|
self.groups[group_name] = group
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
elif len(groups) < 1:
|
||||||
|
group = self.session.create('CustomAttributeGroup', {
|
||||||
|
'name': group_name,
|
||||||
|
})
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise CustAttrException("Found more than one group '{}'".format(group_name))
|
||||||
|
|
||||||
|
def get_role_ALL(self):
|
||||||
|
role_name = 'ALL'
|
||||||
|
if role_name in self.security_roles:
|
||||||
|
all_roles = self.security_roles[role_name]
|
||||||
|
else:
|
||||||
|
all_roles = self.session.query('SecurityRole').all()
|
||||||
|
self.security_roles[role_name] = all_roles
|
||||||
|
for role in all_roles:
|
||||||
|
if role['name'] not in self.security_roles:
|
||||||
|
self.security_roles[role['name']] = role
|
||||||
|
return all_roles
|
||||||
|
|
||||||
|
def get_security_role(self, security_roles):
|
||||||
|
roles = []
|
||||||
|
if len(security_roles) == 0 or security_roles[0] == 'ALL':
|
||||||
|
roles = self.get_role_ALL()
|
||||||
|
elif security_roles[0] == 'except':
|
||||||
|
excepts = security_roles[1:]
|
||||||
|
all = self.get_role_ALL()
|
||||||
|
for role in all:
|
||||||
|
if role['name'] not in excepts:
|
||||||
|
roles.append(role)
|
||||||
|
if role['name'] not in self.security_roles:
|
||||||
|
self.security_roles[role['name']] = role
|
||||||
|
else:
|
||||||
|
for role_name in security_roles:
|
||||||
|
if role_name in self.security_roles:
|
||||||
|
roles.append(self.security_roles[role_name])
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = 'SecurityRole where name is "{}"'.format(role_name)
|
||||||
|
role = self.session.query(query).one()
|
||||||
|
self.security_roles[role_name] = role
|
||||||
|
roles.append(role)
|
||||||
|
except Exception as e:
|
||||||
|
raise CustAttrException("Securit role '{}' does not exist".format(role_name))
|
||||||
|
|
||||||
|
return roles
|
||||||
|
|
||||||
|
def get_default(self, attr):
|
||||||
|
type = attr['type']
|
||||||
|
default = attr['default']
|
||||||
|
err_msg = 'Default value is not'
|
||||||
|
if type == 'number':
|
||||||
|
if not isinstance(default, (float, int)):
|
||||||
|
raise CustAttrException('{} integer'.format(err_msg))
|
||||||
|
elif type == 'text':
|
||||||
|
if not isinstance(default, str):
|
||||||
|
raise CustAttrException('{} string'.format(err_msg))
|
||||||
|
elif type == 'boolean':
|
||||||
|
if not isinstance(default, bool):
|
||||||
|
raise CustAttrException('{} boolean'.format(err_msg))
|
||||||
|
elif type == 'enumerator':
|
||||||
|
if not isinstance(default, array):
|
||||||
|
raise CustAttrException('{} array with strings'.format(err_msg))
|
||||||
|
# TODO check if multiSelect is available and if default is one of data menu
|
||||||
|
if not isinstance(default[0], str):
|
||||||
|
raise CustAttrException('{} array of strings'.format(err_msg))
|
||||||
|
elif type == 'date':
|
||||||
|
date_items = default.split(" ")
|
||||||
|
try:
|
||||||
|
if len(date_items) == 1:
|
||||||
|
default = arrow.get(default, 'YY.M.D')
|
||||||
|
elif len(date_items) == 2:
|
||||||
|
default = arrow.get(default, 'YY.M.D H:m:s')
|
||||||
|
else:
|
||||||
|
raise Exception
|
||||||
|
except Exception as e:
|
||||||
|
raise CustAttrException('Date is not in proper format')
|
||||||
|
elif type == 'dynamic enumerator':
|
||||||
|
raise CustAttrException('Dynamic enumerator can\'t have default')
|
||||||
|
|
||||||
|
return default
|
||||||
|
|
||||||
|
def get_optional(self, attr):
|
||||||
|
output = {}
|
||||||
|
if 'group' in attr:
|
||||||
|
output['group'] = self.get_group(attr)
|
||||||
|
if 'default' in attr:
|
||||||
|
output['default'] = self.get_default(attr)
|
||||||
|
|
||||||
|
roles_read = []
|
||||||
|
roles_write = []
|
||||||
|
if 'read_security_roles' in output:
|
||||||
|
roles_read = attr['read_security_roles']
|
||||||
|
if 'read_security_roles' in output:
|
||||||
|
roles_write = attr['write_security_roles']
|
||||||
|
output['read_security_roles'] = self.get_security_role(roles_read)
|
||||||
|
output['write_security_roles'] = self.get_security_role(roles_write)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_type(self, type_name):
|
||||||
|
if type_name in self.types:
|
||||||
|
return self.types[type_name]
|
||||||
|
|
||||||
|
query = 'CustomAttributeType where name is "{}"'.format(type_name)
|
||||||
|
type = self.session.query(query).one()
|
||||||
|
self.types[type_name] = type
|
||||||
|
|
||||||
|
return type
|
||||||
|
|
||||||
|
def get_entity_type(self, attr):
|
||||||
|
if 'is_hierarchical' in attr:
|
||||||
|
if attr['is_hierarchical'] is True:
|
||||||
|
return {'is_hierarchical':True}
|
||||||
|
|
||||||
|
if 'entity_type' not in attr:
|
||||||
|
raise CustAttrException('Missing entity_type')
|
||||||
|
|
||||||
|
if attr['entity_type'].lower() != 'task':
|
||||||
|
return {'entity_type':attr['entity_type']}
|
||||||
|
|
||||||
|
if 'object_type' not in attr:
|
||||||
|
raise CustAttrException('Missing object_type')
|
||||||
|
|
||||||
|
object_type_name = attr['object_type']
|
||||||
|
if object_type_name not in self.object_type_ids:
|
||||||
|
try:
|
||||||
|
query = 'ObjectType where name is "{}"'.format(object_type_name)
|
||||||
|
object_type_id = self.session.query(query).one()['id']
|
||||||
|
except Exception as e:
|
||||||
|
raise CustAttrException('Object type with name "{}" don\'t exist'.format(object_type_name))
|
||||||
|
self.object_type_ids[object_type_name] = object_type_id
|
||||||
|
else:
|
||||||
|
object_type_id = self.object_type_ids[object_type_name]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'entity_type': attr['entity_type'],
|
||||||
|
'object_type_id': object_type_id
|
||||||
|
}
|
||||||
|
|
||||||
|
def register(session, **kw):
|
||||||
|
'''Register plugin. Called when used as an plugin.'''
|
||||||
|
|
||||||
|
# Validate that session is an instance of ftrack_api.Session. If not,
|
||||||
|
# assume that register is being called from an old or incompatible API and
|
||||||
|
# return without doing anything.
|
||||||
|
if not isinstance(session, ftrack_api.session.Session):
|
||||||
|
return
|
||||||
|
|
||||||
|
action_handler = CustomAttributes(session)
|
||||||
|
action_handler.register()
|
||||||
|
|
||||||
|
|
||||||
|
def main(arguments=None):
|
||||||
|
'''Set up logging and register action.'''
|
||||||
|
if arguments is None:
|
||||||
|
arguments = []
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
# Allow setting of logging level from arguments.
|
||||||
|
loggingLevels = {}
|
||||||
|
for level in (
|
||||||
|
logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
|
||||||
|
logging.ERROR, logging.CRITICAL
|
||||||
|
):
|
||||||
|
loggingLevels[logging.getLevelName(level).lower()] = level
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-v', '--verbosity',
|
||||||
|
help='Set the logging output verbosity.',
|
||||||
|
choices=loggingLevels.keys(),
|
||||||
|
default='info'
|
||||||
|
)
|
||||||
|
namespace = parser.parse_args(arguments)
|
||||||
|
|
||||||
|
# Set up basic logging
|
||||||
|
logging.basicConfig(level=loggingLevels[namespace.verbosity])
|
||||||
|
|
||||||
|
session = ftrack_api.Session()
|
||||||
|
register(session)
|
||||||
|
|
||||||
|
# Wait for events
|
||||||
|
logging.info(
|
||||||
|
'Registered actions and listening for events. Use Ctrl-C to abort.'
|
||||||
|
)
|
||||||
|
session.event_hub.wait()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main(sys.argv[1:]))
|
||||||
|
|
@ -309,7 +309,7 @@ def register(session, **kw):
|
||||||
return
|
return
|
||||||
|
|
||||||
action_handler = SyncToAvalon(session)
|
action_handler = SyncToAvalon(session)
|
||||||
action_handler.register()
|
action_handler.register(200)
|
||||||
|
|
||||||
|
|
||||||
def main(arguments=None):
|
def main(arguments=None):
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,17 @@ class TestAction(BaseAction):
|
||||||
|
|
||||||
def discover(self, session, entities, event):
|
def discover(self, session, entities, event):
|
||||||
''' Validation '''
|
''' Validation '''
|
||||||
|
discover = False
|
||||||
|
roleList = ['Pypeclub']
|
||||||
|
userId = event['source']['user']['id']
|
||||||
|
user = session.query('User where id is ' + userId).one()
|
||||||
|
|
||||||
return True
|
for role in user['user_security_roles']:
|
||||||
|
if role['security_role']['name'] in roleList:
|
||||||
|
discover = True
|
||||||
|
break
|
||||||
|
|
||||||
|
return discover
|
||||||
|
|
||||||
|
|
||||||
def launch(self, session, entities, event):
|
def launch(self, session, entities, event):
|
||||||
|
|
@ -70,7 +79,7 @@ def register(session, **kw):
|
||||||
return
|
return
|
||||||
|
|
||||||
action_handler = TestAction(session)
|
action_handler = TestAction(session)
|
||||||
action_handler.register()
|
action_handler.register(10000)
|
||||||
|
|
||||||
|
|
||||||
def main(arguments=None):
|
def main(arguments=None):
|
||||||
|
|
|
||||||
|
|
@ -54,12 +54,12 @@ class AppAction(object):
|
||||||
'''Return current session.'''
|
'''Return current session.'''
|
||||||
return self._session
|
return self._session
|
||||||
|
|
||||||
def register(self):
|
def register(self, priority = 100):
|
||||||
'''Registers the action, subscribing the discover and launch topics.'''
|
'''Registers the action, subscribing the discover and launch topics.'''
|
||||||
self.session.event_hub.subscribe(
|
self.session.event_hub.subscribe(
|
||||||
'topic=ftrack.action.discover and source.user.username={0}'.format(
|
'topic=ftrack.action.discover and source.user.username={0}'.format(
|
||||||
self.session.api_user
|
self.session.api_user
|
||||||
), self._discover
|
), self._discover,priority=priority
|
||||||
)
|
)
|
||||||
|
|
||||||
self.session.event_hub.subscribe(
|
self.session.event_hub.subscribe(
|
||||||
|
|
@ -467,12 +467,15 @@ class BaseAction(object):
|
||||||
def reset_session(self):
|
def reset_session(self):
|
||||||
self.session.reset()
|
self.session.reset()
|
||||||
|
|
||||||
def register(self):
|
def register(self, priority = 100):
|
||||||
'''Registers the action, subscribing the the discover and launch topics.'''
|
'''
|
||||||
|
Registers the action, subscribing the the discover and launch topics.
|
||||||
|
- highest priority event will show last
|
||||||
|
'''
|
||||||
self.session.event_hub.subscribe(
|
self.session.event_hub.subscribe(
|
||||||
'topic=ftrack.action.discover and source.user.username={0}'.format(
|
'topic=ftrack.action.discover and source.user.username={0}'.format(
|
||||||
self.session.api_user
|
self.session.api_user
|
||||||
), self._discover
|
), self._discover, priority=priority
|
||||||
)
|
)
|
||||||
|
|
||||||
self.session.event_hub.subscribe(
|
self.session.event_hub.subscribe(
|
||||||
|
|
@ -632,6 +635,37 @@ class BaseAction(object):
|
||||||
'''
|
'''
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def show_message(self, event, input_message, result = False):
|
||||||
|
"""
|
||||||
|
Shows message to user who triggered event
|
||||||
|
- event - just source of user id
|
||||||
|
- input_message - message that is shown to user
|
||||||
|
- result - changes color of message (based on ftrack settings)
|
||||||
|
- True = Violet
|
||||||
|
- False = Red
|
||||||
|
"""
|
||||||
|
if not isinstance(result, bool):
|
||||||
|
result = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = str(input_message)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = event['source']['user']['id']
|
||||||
|
self.session.event_hub.publish(
|
||||||
|
ftrack_api.event.base.Event(
|
||||||
|
topic='ftrack.action.trigger-user-interface',
|
||||||
|
data=dict(
|
||||||
|
type='message',
|
||||||
|
success=result,
|
||||||
|
message=message
|
||||||
|
),
|
||||||
|
target='applicationId=ftrack.client.web and user.id="{0}"'.format(user_id)
|
||||||
|
),
|
||||||
|
on_error='ignore'
|
||||||
|
)
|
||||||
|
|
||||||
def _handle_result(self, session, result, entities, event):
|
def _handle_result(self, session, result, entities, event):
|
||||||
'''Validate the returned result from the action callback'''
|
'''Validate the returned result from the action callback'''
|
||||||
if isinstance(result, bool):
|
if isinstance(result, bool):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue