diff --git a/attract/node_types/__init__.py b/attract/node_types/__init__.py index 9ee89ef..9476035 100644 --- a/attract/node_types/__init__.py +++ b/attract/node_types/__init__.py @@ -2,6 +2,6 @@ from .act import node_type_act from .scene import node_type_scene from .shot import node_type_shot from .task import node_type_task +from .asset import node_type_asset -NODE_TYPES = (node_type_act, node_type_scene, node_type_shot, node_type_task) - +NODE_TYPES = (node_type_act, node_type_scene, node_type_shot, node_type_task, node_type_asset) diff --git a/attract/node_types/asset.py b/attract/node_types/asset.py new file mode 100644 index 0000000..f15edef --- /dev/null +++ b/attract/node_types/asset.py @@ -0,0 +1,26 @@ +node_type_asset = { + 'name': 'attract_asset', + 'description': 'Attract Asset Node Type, for planning asset creation', + 'dyn_schema': { + 'status': { + 'type': 'string', + 'allowed': [ + 'on_hold', + 'todo', + 'in_progress', + 'review', + 'final' + ], + 'default': 'todo', + 'required_after_creation': True, + }, + 'notes': { + 'type': 'string', + 'maxlength': 256, + }, + }, + 'form_schema': {}, + 'parent': ['attract_scene', 'attract_shot'] +} + +task_types = ['modeling', 'rigging', 'material'] diff --git a/attract/shots_and_assets/__init__.py b/attract/shots_and_assets/__init__.py index 1ccd266..6da624a 100644 --- a/attract/shots_and_assets/__init__.py +++ b/attract/shots_and_assets/__init__.py @@ -20,7 +20,7 @@ from pillar import attrs_extra from attract.node_types import node_type_shot, node_type_task # From patch operation name to fields that operation may edit. -VALID_PATCH_FIELDS = { +VALID_SHOT_PATCH_FIELDS = { u'from-blender': { u'name', u'picture', @@ -37,7 +37,7 @@ VALID_PATCH_FIELDS = { }, } -VALID_PATCH_OPERATIONS = { +VALID_SHOT_PATCH_OPERATIONS = { u'from-blender', u'from-web', u'unlink', u'relink', } @@ -222,7 +222,7 @@ def patch_shot(node_id, patch): raise wz_exceptions.NotFound('Node %s not found' % node_id) op = patch['op'] - if op in VALID_PATCH_FIELDS: + if op in VALID_SHOT_PATCH_FIELDS: # Set the fields for key, value in patch['$set'].items(): node_setattr(node, key, value) @@ -253,15 +253,15 @@ def assert_is_valid_patch(patch): except KeyError: raise wz_exceptions.BadRequest("PATCH should have a key 'op' indicating the operation.") - if op not in VALID_PATCH_OPERATIONS: - valid_ops = u', '.join(sorted(VALID_PATCH_OPERATIONS)) + if op not in VALID_SHOT_PATCH_OPERATIONS: + valid_ops = u', '.join(sorted(VALID_SHOT_PATCH_OPERATIONS)) raise wz_exceptions.BadRequest(u'Operation should be one of %s' % valid_ops) - if op not in VALID_PATCH_FIELDS: + if op not in VALID_SHOT_PATCH_FIELDS: # Valid operation, and we don't have to check the fields. return - allowed_fields = VALID_PATCH_FIELDS[op] + allowed_fields = VALID_SHOT_PATCH_FIELDS[op] try: fields = set(patch['$set'].keys()) except KeyError: diff --git a/attract/shots_and_assets/eve_hooks.py b/attract/shots_and_assets/eve_hooks.py index 5013faf..b91fcc1 100644 --- a/attract/shots_and_assets/eve_hooks.py +++ b/attract/shots_and_assets/eve_hooks.py @@ -1,9 +1,15 @@ # -*- encoding: utf-8 -*- +"""Hooks for shots & assets. + +These two node types have very similar use, which is why they are covered by the same code. +""" + import itertools import logging from attract.node_types.shot import node_type_shot, human_readable_properties +from attract.node_types.asset import node_type_asset from pillar.api.nodes import only_for_node_type_decorator import pillar.api.activities import pillar.api.utils.authentication @@ -12,9 +18,16 @@ import pillar.web.jinja log = logging.getLogger(__name__) only_for_shot = only_for_node_type_decorator(node_type_shot['name']) +for_shot_asset = only_for_node_type_decorator(node_type_shot['name'], + node_type_asset['name']) + +typenames = { + node_type_shot['name']: 'shot', + node_type_asset['name']: 'asset', +} -def register_shot_activity(shot, descr): +def register_shot_asset_activity(shot, descr): user_id = pillar.api.utils.authentication.current_user_id() pillar.api.activities.register_activity( user_id, @@ -26,20 +39,22 @@ def register_shot_activity(shot, descr): ) -@only_for_shot -def activity_after_replacing_shot(shot, original): +@for_shot_asset +def activity_after_replacing_shot_asset(shot_or_asset, original): """ Note: this is also used on PATCH, since our custom shot PATCH handler performs a PUT-internal to run the patched node through Eve for validation. """ + typename = typenames[shot_or_asset['node_type']] + # Compare to original, and either mention the things that changed, # or (if they are equal) don't log an activity at all. - changes = list(itertools.islice(pillar.api.utils.doc_diff(shot, original), 2)) + changes = list(itertools.islice(pillar.api.utils.doc_diff(shot_or_asset, original), 2)) if not changes: - log.info('Not registering replacement of shot %s, as it is identical ' - 'in non-private fields.', shot['_id']) + log.info('Not registering replacement of %s %s, as it is identical ' + 'in non-private fields.', typename, shot_or_asset['_id']) return if len(changes) == 1: @@ -52,31 +67,33 @@ def activity_after_replacing_shot(shot, original): # Some key- and value-specific overrides if val_shot is pillar.api.utils.DoesNotExist: - descr = 'removed "%s" from shot "%s"' % (human_key, shot['name']) + descr = 'removed "%s" from shot "%s"' % (human_key, shot_or_asset['name']) if key == 'picture': - descr = 'changed the thumbnail of shot "%s"' % shot['name'] + descr = 'changed the thumbnail of %s "%s"' % (typename, shot_or_asset['name']) elif key == 'properties.status': val_shot = pillar.web.jinja.format_undertitle(val_shot) elif isinstance(val_shot, basestring) and len(val_shot) > 80: val_shot = val_shot[:80] + u'…' if descr is None: - descr = 'changed "%s" to "%s" in shot "%s"' %\ - (human_key, val_shot, shot['name']) + descr = 'changed "%s" to "%s" in %s "%s"' % \ + (human_key, val_shot, typename, shot_or_asset['name']) else: - descr = 'edited shot "%s"' % shot['name'] + descr = 'edited %s "%s"' % (typename, shot_or_asset['name']) - register_shot_activity(shot, descr) + register_shot_asset_activity(shot_or_asset, descr) -@only_for_shot -def activity_after_creating_shot(shot): - register_shot_activity(shot, 'created a new shot "%s"' % shot['name']) +@for_shot_asset +def activity_after_creating_shot_asset(shot_or_asset): + typename = typenames[shot_or_asset['node_type']] + register_shot_asset_activity(shot_or_asset, 'created a new %s "%s"' % ( + typename, shot_or_asset['name'])) -def activity_after_creating_shots(nodes): +def activity_after_creating_shots_assets(nodes): for node in nodes: - activity_after_creating_shot(node) + activity_after_creating_shot_asset(node) @only_for_shot @@ -90,14 +107,16 @@ def nodes_set_default_used_in_edit(nodes): set_default_used_in_edit(node) -@only_for_shot -def activity_after_deleting_shot(shot): - register_shot_activity(shot, 'deleted shot "%s"' % shot['name']) +@for_shot_asset +def activity_after_deleting_shot_asset(shot_or_asset): + typename = typenames[shot_or_asset['node_type']] + register_shot_asset_activity(shot_or_asset, 'deleted %s "%s"' % ( + typename, shot_or_asset['name'])) def setup_app(app): - app.on_replaced_nodes += activity_after_replacing_shot + app.on_replaced_nodes += activity_after_replacing_shot_asset app.on_insert_nodes += nodes_set_default_used_in_edit - app.on_inserted_nodes += activity_after_creating_shots - app.on_deleted_item_nodes += activity_after_deleting_shot - app.on_deleted_resource_nodes += activity_after_deleting_shot + app.on_inserted_nodes += activity_after_creating_shots_assets + app.on_deleted_item_nodes += activity_after_deleting_shot_asset + app.on_deleted_resource_nodes += activity_after_deleting_shot_asset