From 4ad33731bd295134f0966bf08857118f5ec805a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 31 Aug 2016 11:32:17 +0200 Subject: [PATCH] Added CLI module to set up project for attract. --- attract_server/__init__.py | 3 + attract_server/cli.py | 105 ++++++++++++++++++++++++ attract_server/node_types/__init__.py | 7 ++ attract_server/node_types/act.py | 5 ++ attract_server/node_types/scene.py | 5 ++ attract_server/node_types/shot.py | 41 ++++++++++ attract_server/node_types/task.py | 113 ++++++++++++++++++++++++++ 7 files changed, 279 insertions(+) create mode 100644 attract_server/cli.py create mode 100644 attract_server/node_types/__init__.py create mode 100644 attract_server/node_types/act.py create mode 100644 attract_server/node_types/scene.py create mode 100644 attract_server/node_types/shot.py create mode 100644 attract_server/node_types/task.py diff --git a/attract_server/__init__.py b/attract_server/__init__.py index 6b521c9..c947e4d 100644 --- a/attract_server/__init__.py +++ b/attract_server/__init__.py @@ -17,6 +17,9 @@ class AttractExtension(PillarExtension): :rtype: dict """ + # Just so that it registers the management commands. + from . import cli + return {} def eve_settings(self): diff --git a/attract_server/cli.py b/attract_server/cli.py new file mode 100644 index 0000000..4135eab --- /dev/null +++ b/attract_server/cli.py @@ -0,0 +1,105 @@ +"""Commandline interface for Attract.""" + +from __future__ import print_function, division + +import copy +import logging + +from bson import ObjectId +from eve.methods.put import put_internal +from flask import current_app +from pillar.cli import manager + +log = logging.getLogger(__name__) + + +def _get_project(project_url): + """Find a project in the database, or SystemExit()s. + + :param project_url: UUID of the project + :type: str + :return: the project + :rtype: dict + """ + + projects_collection = current_app.data.driver.db['projects'] + + # Find the project in the database. + project = projects_collection.find_one({'url': project_url}) + if not project: + log.error('Project %s does not exist.', project_url) + raise SystemExit() + + return project + + +def _update_project(project): + """Updates a project in the database, or SystemExit()s. + + :param project: the project data, should be the entire project document + :type: dict + :return: the project + :rtype: dict + """ + + from pillar.api.utils import remove_private_keys + from pillar.api.utils import authentication + + authentication.force_cli_user() + + project_id = ObjectId(project['_id']) + project = remove_private_keys(project) + result, _, _, status_code = put_internal('projects', project, _id=project_id) + + if status_code != 200: + log.error("Can't update project %s, issues: %s", project_id, result) + raise SystemExit() + + +@manager.command +@manager.option('-r', '--replace', dest='replace', action='store_true', default=False) +def setup_for_attract(project_url, replace=False): + """Adds Attract node types to the project. + + Use --replace to replace pre-existing Attract node types + (by default already existing Attract node types are skipped). + """ + + from .node_types import NODE_TYPES + + # Copy permissions from the project, then give everyone with PUT + # access also DELETE access. + project = _get_project(project_url) + permissions = copy.deepcopy(project['permissions']) + + for perms in permissions.values(): + for perm in perms: + methods = set(perm['methods']) + if 'PUT' not in perm['methods']: + continue + methods.add('DELETE') + perm['methods'] = list(methods) + + # Make a copy of the node types when setting the permissions, as + # we don't want to mutate the global node type objects. + node_types = (dict(permissions=permissions, **nt) for nt in NODE_TYPES) + + # Add the missing node types. + for node_type in node_types: + found = [nt for nt in project['node_types'] + if nt['name'] == node_type['name']] + if found: + assert len(found) == 1, 'node type name should be unique (found %ix)' % len(found) + + # TODO: validate that the node type contains all the properties Attract needs. + if replace: + log.info('Replacing existing node type %s', node_type['name']) + project['node_types'].remove(found[0]) + else: + continue + + project['node_types'].append(node_type) + + _update_project(project) + + log.info('Project %s was updated for Attract.', project_url) diff --git a/attract_server/node_types/__init__.py b/attract_server/node_types/__init__.py new file mode 100644 index 0000000..9ee89ef --- /dev/null +++ b/attract_server/node_types/__init__.py @@ -0,0 +1,7 @@ +from .act import node_type_act +from .scene import node_type_scene +from .shot import node_type_shot +from .task import node_type_task + +NODE_TYPES = (node_type_act, node_type_scene, node_type_shot, node_type_task) + diff --git a/attract_server/node_types/act.py b/attract_server/node_types/act.py new file mode 100644 index 0000000..d434602 --- /dev/null +++ b/attract_server/node_types/act.py @@ -0,0 +1,5 @@ +node_type_act = { + 'name': 'act', + 'description': 'Act node type', + 'parent': [] +} diff --git a/attract_server/node_types/scene.py b/attract_server/node_types/scene.py new file mode 100644 index 0000000..9977875 --- /dev/null +++ b/attract_server/node_types/scene.py @@ -0,0 +1,5 @@ +node_type_scene = { + 'name': 'scene', + 'description': 'Scene node type', + 'parent': ['act'], +} diff --git a/attract_server/node_types/shot.py b/attract_server/node_types/shot.py new file mode 100644 index 0000000..3f0cdbf --- /dev/null +++ b/attract_server/node_types/shot.py @@ -0,0 +1,41 @@ +node_type_shot = { + 'name': 'shot', + 'description': 'Shot Node Type, for shots', + 'dyn_schema': { + # TODO: document what this URL is used for. + 'url': { + 'type': 'string', + }, + # TODO: document what cut_in means. + 'cut_in': { + 'type': 'integer' + }, + # TODO: document what cut_out means. + 'cut_out': { + 'type': 'integer' + }, + 'status': { + 'type': 'string', + 'allowed': [ + 'on_hold', + 'todo', + 'in_progress', + 'review', + 'final' + ], + }, + 'notes': { + 'type': 'string', + 'maxlength': 256, + }, + 'shot_group': { + 'type': 'string', + #'data_relation': { + # 'resource': 'nodes', + # 'field': '_id', + #}, + }, + }, + 'form_schema': {}, + 'parent': ['scene'] +} diff --git a/attract_server/node_types/task.py b/attract_server/node_types/task.py new file mode 100644 index 0000000..dc07756 --- /dev/null +++ b/attract_server/node_types/task.py @@ -0,0 +1,113 @@ +node_type_task = { + 'name': 'task', + 'description': 'Task Node Type, for tasks', + 'dyn_schema': { + 'status': { + 'type': 'string', + 'allowed': [ + 'todo', + 'in_progress', + 'on_hold', + 'approved', + 'cbb', + 'final', + 'review' + ], + 'required': True, + }, + 'filepath': { + 'type': 'string', + }, + 'revision': { + 'type': 'integer', + }, + 'owners': { + 'type': 'dict', + 'schema': { + 'users': { + 'type': 'list', + 'schema': { + 'type': 'objectid', + } + }, + 'groups': { + 'type': 'list', + 'schema': { + 'type': 'objectid', + } + } + } + }, + 'time': { + 'type': 'dict', + 'schema': { + # TODO: include indication of _what_ start. + # At least make distinction between "planned" and "actual". + 'start': { + 'type': 'datetime' + }, + # TODO: include units! + # TODO: include indication of _what_ duration. + # At least make distinction between "planned" and "actual". + 'duration': { + 'type': 'integer' + }, + 'chunks': { + 'type': 'list', + 'schema': { + 'type': 'dict', + # TODO: same notes as above + 'schema': { + 'start': { + 'type': 'datetime', + }, + 'duration': { + 'type': 'integer', + } + } + } + }, + } + }, + 'is_conflicting' : { + 'type': 'boolean' + }, + 'is_processing' : { + 'type': 'boolean' + }, + 'is_open' : { + 'type': 'boolean' + } + + }, + 'form_schema': { + 'status': {}, + 'filepath': {}, + 'revision': {}, + 'owners': { + 'schema': { + 'users':{ + 'items': [('User', 'first_name')], + }, + 'groups': {} + } + }, + 'time': { + 'schema': { + 'start': {}, + 'duration': {}, + 'chunks': { + 'visible': False, + 'schema': { + 'start': {}, + 'duration': {} + } + } + } + }, + 'is_conflicting': {}, + 'is_open': {}, + 'is_processing': {}, + }, + 'parent': ['shot'] +}