.. :copyright: Copyright (c) 2017 ftrack .. _example/manage_custom_attribute_configuration: ************************** Managing custom attributes ************************** From the API it is not only possible to :ref:`read and update custom attributes for entities `, but also managing custom attribute configurations. Existing custom attribute configurations can be queried as :: # Print all existing custom attribute configurations. print(session.query('CustomAttributeConfiguration').all()) Use :meth:`Session.create` to create a new custom attribute configuration:: # Get the custom attribute type. custom_attribute_type = session.query( 'CustomAttributeType where name is "text"' ).one() # Create a custom attribute configuration. session.create('CustomAttributeConfiguration', { 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Asset version text attribute', 'key': 'asset_version_text_attribute', 'default': 'bar', 'config': json.dumps({'markdown': False}) }) # Persist it to the ftrack instance. session.commit() .. tip:: The example above does not add security roles. This can be done either from System Settings in the ftrack web application, or by following the :ref:`example/manage_custom_attribute_configuration/security_roles` example. Global or project specific ========================== A custom attribute can be global or project specific depending on the `project_id` attribute:: # Create a custom attribute configuration. session.create('CustomAttributeConfiguration', { # Set the `project_id` and the custom attribute will only be available # on `my_project`. 'project_id': my_project['id'], 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Asset version text attribute', 'key': 'asset_version_text_attribute', 'default': 'bar', 'config': json.dumps({'markdown': False}) }) session.commit() A project specific custom attribute can be changed to a global:: custom_attribute_configuration['project_id'] = None session.commit() Changing a global custom attribute configuration to a project specific is not allowed. Entity types ============ Custom attribute configuration entity types are using a legacy notation. A configuration can have one of the following as `entity_type`: :task: Represents TypedContext (Folder, Shot, Sequence, Task, etc.) custom attribute configurations. When setting this as entity_type the object_type_id must be set as well. Creating a text custom attribute for Folder:: custom_attribute_type = session.query( 'CustomAttributeType where name is "text"' ).one() object_type = session.query('ObjectType where name is "Folder"').one() session.create('CustomAttributeConfiguration', { 'entity_type': 'task', 'object_type_id': object_type['id'], 'type': custom_attribute_type, 'label': 'Foo', 'key': 'foo', 'default': 'bar', 'config': json.dumps({'markdown': False}) }) session.commit() Can be associated with a `project_id`. :show: Represents Projects custom attribute configurations. Can be associated with a `project_id`. :assetversion: Represents AssetVersion custom attribute configurations. Can be associated with a `project_id`. :user: Represents User custom attribute configurations. Must be `global` and cannot be associated with a `project_id`. :list: Represents List custom attribute configurations. Can be associated with a `project_id`. :asset: Represents Asset custom attribute configurations. .. note:: Asset custom attributes have limited support in the ftrack web interface. Can be associated with a `project_id`. It is not possible to change type after a custom attribute configuration has been created. Custom attribute configuration types ==================================== Custom attributes can be of different data types depending on what type is set in the configuration. Some types requires an extra json encoded config to be set: :text: A sting type custom attribute. The `default` value must be either :py:class:`str` or :py:class:`unicode`. Can be either presented as raw text or markdown formatted in applicaitons which support it. This is configured through a markwdown key:: # Get the custom attribute type. custom_attribute_type = session.query( 'CustomAttributeType where name is "text"' ).one() # Create a custom attribute configuration. session.create('CustomAttributeConfiguration', { 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Asset version text attribute', 'key': 'asset_version_text_attribute', 'default': 'bar', 'config': json.dumps({'markdown': False}) }) # Persist it to the ftrack instance. session.commit() :boolean: A boolean type custom attribute. The `default` value must be a :py:class:`bool`. No config is required. :date: A date type custom attribute. The `default` value must be an :term:`arrow` date - e.g. arrow.Arrow(2017, 2, 8). No config is required. :enumerator: An enumerator type custom attribute. The `default` value must be a list with either :py:class:`str` or :py:class:`unicode`. The enumerator can either be single or multi select. The config must a json dump of a dictionary containing `multiSelect` and `data`. Where `multiSelect` is True or False and data is a list of options. Each option should be a dictionary containing `value` and `menu`, where `menu` is meant to be used as label in a user interface. Create a custom attribute enumerator:: custom_attribute_type = session.query( 'CustomAttributeType where name is "enumerator"' ).first() session.create('CustomAttributeConfiguration', { 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Enumerator attribute', 'key': 'enumerator_attribute', 'default': ['bar'], 'config': json.dumps({ 'multiSelect': True, 'data': json.dumps([ {'menu': 'Foo', 'value': 'foo'}, {'menu': 'Bar', 'value': 'bar'} ]) }) }) session.commit() :dynamic enumerator: An enumerator type where available options are fetched from remote. Created in the same way as enumerator but without `data`. :number: A number custom attribute can be either decimal or integer for presentation. This can be configured through the `isdecimal` config option:: custom_attribute_type = session.query( 'CustomAttributeType where name is "number"' ).first() session.create('CustomAttributeConfiguration', { 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Number attribute', 'key': 'number_attribute', 'default': 42, 'config': json.dumps({ 'isdecimal': True }) }) session.commit() Links ===== .. _example/manage_custom_attribute_configuration/links: A link between entities in ftrack can be created as a custom attribute using the CustomAttributeLinkConfiguration schema. Links are different from regular custom attributes and they can be used differently in queries. Read more about how to use them :ref:`here`. Links do not have a type or default value like regular CustomAttributeConfiguration objects, instead they have the following attributes: :one_to_one: Boolean used to specify if the link can represent multiple relations or just a single relation. :entity_type_to: Represents the entity this link can point to. Valid values are show, task, asset_version, list, user, group, asset and component. :object_type_id_to: When setting entity_type_to to "task" object_type_id_to must also be specified. Below is an example of how to create a custom attribute link between a TypedContext object such as Shot and AssetVersion:: security_role = session.query('SecurityRole').first() task_object_id = session.query('ObjectType where name is "task"').one()['id'] session.create('CustomAttributeLinkConfiguration', { 'entity_type': 'task', 'object_type_id': task_object_id, 'entity_type_to': 'asset_version', 'label': 'Delivered version', 'key': 'delivered_version', 'config': '{}', 'one_to_one': True, 'write_security_roles': [security_role], 'read_security_roles': [security_role] }) An other example creating a custom attribute link between a Sequence and a Shot specifying object_type for both sides.:: security_role = session.query('SecurityRole').first() shot_object_id = session.query('ObjectType where name is "shot"').one()['id'] sequence_object_id = session.query('ObjectType where name is "sequence"').one()['id'] session.create('CustomAttributeLinkConfiguration', { 'entity_type': 'task', 'object_type_id': sequence_object, 'entity_type_to': 'task', 'object_type_id_to': shot_object_id, 'label': 'Master shot', 'key': 'master_shot', 'config': '{}', 'one_to_one': True, 'write_security_roles': [security_role], 'read_security_roles': [security_role] }) Changing default ================ It is possible to update the `default` value of a custom attribute configuration. This will not change the value of any existing custom attributes:: # Change the default value of custom attributes. This will only affect # newly created entities. custom_attribute_configuration['default'] = 43 session.commit() .. _example/manage_custom_attribute_configuration/security_roles: Security roles ============== By default new custom attribute configurations and the entity values are not readable or writable by any security role. This can be configured through the `read_security_roles` and `write_security_roles` attributes:: # Pick random security role. security_role = session.query('SecurityRole').first() custom_attribute_type = session.query( 'CustomAttributeType where name is "date"' ).first() session.create('CustomAttributeConfiguration', { 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Date attribute', 'key': 'date_attribute', 'default': arrow.Arrow(2017, 2, 8), 'write_security_roles': [security_role], 'read_security_roles': [security_role] }) session.commit() .. note:: Setting the correct security role is important and must be changed to whatever security role is appropriate for your configuration and intended purpose. Custom attribute groups ======================= A custom attribute configuration can be categorized using a `CustomAttributeGroup`:: group = session.query('CustomAttributeGroup').first() security_role = session.query('SecurityRole').first() custom_attribute_type = session.query( 'CustomAttributeType where name is "enumerator"' ).first() session.create('CustomAttributeConfiguration', { 'entity_type': 'assetversion', 'type': custom_attribute_type, 'label': 'Enumerator attribute', 'key': 'enumerator_attribute', 'default': ['bar'], 'config': json.dumps({ 'multiSelect': True, 'data': json.dumps([ {'menu': 'Foo', 'value': 'foo'}, {'menu': 'Bar', 'value': 'bar'} ]) }), 'group': group, 'write_security_roles': [security_role], 'read_security_roles': [security_role] }) session.commit() .. seealso:: :ref:`example/custom_attribute`