Started working on shots UI & management

This commit is contained in:
2016-09-21 11:07:20 +02:00
parent 486f947a90
commit 010cf23d67
9 changed files with 201 additions and 12 deletions

View File

@@ -5,6 +5,7 @@ from werkzeug.local import LocalProxy
from pillar.extension import PillarExtension from pillar.extension import PillarExtension
import attract.task_manager import attract.task_manager
import attract.shot_manager
EXTENSION_NAME = 'attract' EXTENSION_NAME = 'attract'
@@ -13,6 +14,7 @@ class AttractExtension(PillarExtension):
def __init__(self): def __init__(self):
self._log = logging.getLogger('%s.AttractExtension' % __name__) self._log = logging.getLogger('%s.AttractExtension' % __name__)
self.task_manager = attract.task_manager.TaskManager() self.task_manager = attract.task_manager.TaskManager()
self.shot_manager = attract.shot_manager.ShotManager()
@property @property
def name(self): def name(self):
@@ -52,12 +54,14 @@ class AttractExtension(PillarExtension):
:rtype: list of flask.Blueprint objects. :rtype: list of flask.Blueprint objects.
""" """
from . import modules, tasks from . import modules, tasks, shots
return [ return [
modules.blueprint, modules.blueprint,
tasks.blueprint, tasks.blueprint,
tasks.perproject_blueprint, tasks.perproject_blueprint,
shots.blueprint,
shots.perproject_blueprint,
] ]
@property @property
@@ -78,12 +82,11 @@ class AttractExtension(PillarExtension):
subversion.task_logged.connect(self.task_manager.task_logged_in_svn) subversion.task_logged.connect(self.task_manager.task_logged_in_svn)
def _get_task_manager(): def _get_current_attract():
"""Returns the Attract task manager of the current application.""" """Returns the Attract extension of the current application."""
current_attract = flask.current_app.pillar_extensions[EXTENSION_NAME] return flask.current_app.pillar_extensions[EXTENSION_NAME]
return current_attract.task_manager
current_task_manager = LocalProxy(_get_task_manager) current_attract = LocalProxy(_get_current_attract)
"""Attract Task manager of the current app.""" """Attract extension of the current app."""

View File

@@ -23,6 +23,7 @@ node_type_shot = {
'review', 'review',
'final' 'final'
], ],
'default': 'todo',
}, },
'notes': { 'notes': {
'type': 'string', 'type': 'string',

40
attract/shot_manager.py Normal file
View File

@@ -0,0 +1,40 @@
"""Shot management."""
import attr
import flask_login
import pillarsdk
from pillar.web.system_util import pillar_api
from . import attrs_extra
from .node_types.shot import node_type_shot
@attr.s
class ShotManager(object):
_log = attrs_extra.log('%s.ShotManager' % __name__)
def create_shot(self, project):
"""Creates a new shot, owned by the current user.
:rtype: pillarsdk.Node
"""
api = pillar_api()
node_type = project.get_node_type(node_type_shot['name'])
if not node_type:
raise ValueError('Project %s not set up for Attract' % project._id)
node_props = dict(
name='New shot',
project=project['_id'],
user=flask_login.current_user.objectid,
node_type=node_type['name'],
properties={
'status': node_type['dyn_schema']['status']['default'],
},
)
shot = pillarsdk.Node(node_props)
shot.create(api=api)
return shot

75
attract/shots.py Normal file
View File

@@ -0,0 +1,75 @@
import logging
from flask import Blueprint, render_template, request
import flask
import pillarsdk
from pillar.web.system_util import pillar_api
from .modules import attract_project_view
from .node_types.shot import node_type_shot
from . import current_attract
blueprint = Blueprint('attract.shots', __name__, url_prefix='/shots')
perproject_blueprint = Blueprint('attract.shots.perproject', __name__,
url_prefix='/<project_url>/shots')
log = logging.getLogger(__name__)
@blueprint.route('/')
def index():
api = pillar_api()
# Find projects that are set up for Attract.
projects = pillarsdk.Project.all({
'where': {
'extension_props.attract': {'$exists': 1},
'node_types.name': node_type_shot['name'],
}}, api=api)
return render_template('attract/shots/index.html',
projects=projects['_items'])
@perproject_blueprint.route('/', endpoint='index')
@attract_project_view(extension_props=True)
def for_project(project, attract_props):
api = pillar_api()
shots = pillarsdk.Node.all({
'where': {
'project': project['_id'],
'node_type': node_type_shot['name'],
}}, api=api)
return render_template('attract/shots/for_project.html',
shots=shots['_items'],
project=project,
attract_props=attract_props)
@perproject_blueprint.route('/<shot_id>')
@attract_project_view(extension_props=True)
def view_shot(project, attract_props, shot_id):
api = pillar_api()
shot = pillarsdk.Node.find(shot_id, api=api)
return render_template('attract/shots/shot.html',
shot=shot,
project=project,
attract_props=attract_props)
# TODO: remove GET method once Pablo has made a proper button to call this URL with a POST.
@perproject_blueprint.route('/create', methods=['POST', 'GET'])
@attract_project_view()
def create_shot(project):
shot = current_attract.shot_manager.create_shot(project)
resp = flask.make_response()
resp.headers['Location'] = flask.url_for('.view_shot',
project_url=project['url'],
shot_id=shot['_id'])
resp.status_code = 201
return resp

View File

@@ -7,6 +7,7 @@ import pillarsdk
from pillar.web.system_util import pillar_api from pillar.web.system_util import pillar_api
from . import attrs_extra from . import attrs_extra
from .node_types.shot import node_type_shot
@attr.s @attr.s
@@ -31,7 +32,7 @@ class TaskManager(object):
""" """
api = pillar_api() api = pillar_api()
node_type = project.get_node_type('attract_task') node_type = project.get_node_type(node_type_shot['name'])
if not node_type: if not node_type:
raise ValueError('Project %s not set up for Attract' % project._id) raise ValueError('Project %s not set up for Attract' % project._id)

View File

@@ -8,7 +8,7 @@ from pillar.web.system_util import pillar_api
from .modules import attract_project_view from .modules import attract_project_view
from .node_types.task import node_type_task from .node_types.task import node_type_task
from . import current_task_manager from . import current_attract
blueprint = Blueprint('attract.tasks', __name__, url_prefix='/tasks') blueprint = Blueprint('attract.tasks', __name__, url_prefix='/tasks')
perproject_blueprint = Blueprint('attract.tasks.perproject', __name__, perproject_blueprint = Blueprint('attract.tasks.perproject', __name__,
@@ -21,7 +21,7 @@ def index():
return render_template('attract/tasks/index.html') return render_template('attract/tasks/index.html')
@perproject_blueprint.route('/') @perproject_blueprint.route('/', endpoint='index')
@attract_project_view() @attract_project_view()
def for_project(project): def for_project(project):
api = pillar_api() api = pillar_api()
@@ -59,7 +59,7 @@ def save(project, task_id):
log.info('Saving task %s', task_id) log.info('Saving task %s', task_id)
log.debug('Form data: %s', request.form) log.debug('Form data: %s', request.form)
task = current_task_manager.edit_task(task_id, **request.form.to_dict()) task = current_attract.task_manager.edit_task(task_id, **request.form.to_dict())
return flask.jsonify({'task_id': task_id, 'etag': task._etag, 'time': task._updated }) return flask.jsonify({'task_id': task_id, 'etag': task._etag, 'time': task._updated })
@@ -69,7 +69,7 @@ def save(project, task_id):
@perproject_blueprint.route('/create/<task_type>', methods=['POST', 'GET']) @perproject_blueprint.route('/create/<task_type>', methods=['POST', 'GET'])
@attract_project_view() @attract_project_view()
def create_task(project, task_type=None): def create_task(project, task_type=None):
task = current_task_manager.create_task(project, task_type=task_type) task = current_attract.task_manager.create_task(project, task_type=task_type)
resp = flask.make_response() resp = flask.make_response()
resp.headers['Location'] = flask.url_for('attract.tasks.view_embed_task', resp.headers['Location'] = flask.url_for('attract.tasks.view_embed_task',

View File

@@ -0,0 +1,30 @@
| {% extends 'attract/layout.html' %}
| {% block page_title %}Shots{% endblock %}
| {% block body %}
#page-container
#page-header
.page-title
| Shots for project {{ project.name }}
#page-content
.page-triplet-container.homepage
.row
.col-md-8
table
thead
tr
td Shot name
| {% for task_type in attract_props.task_types.attract_shot %}
td {{ task_type}}
| {% endfor %}
tbody
| {% for shot in shots %}
tr
td {{ shot.name }}
| {% for task_type in attract_props.task_types.attract_shot %}
td tasks of type {{ task_type }}
| {% endfor %}
| {% endfor %}
.col-md-4
.well Task details here
| {% endblock %}

View File

@@ -0,0 +1,23 @@
| {% extends 'attract/layout.html' %}
| {% block page_title %}Shots{% endblock %}
| {% block body %}
#page-container
#page-header
.page-title
| Attract - Shots
#page-content
.page-triplet-container.homepage
.row
.col-md-4
h2 Attract projects
ul
| {% for project in projects %}
li
a(href="{{ url_for('attract.shots.perproject.index', project_url=project.url) }}") {{ project.name }}
| {% endfor %}
.col-md-4
h2 project 2
.col-md-4
h2 project 3
| {% endblock %}

View File

@@ -0,0 +1,16 @@
| {% extends 'attract/layout.html' %}
| {% block page_title %}Shot {{ shot.name }}{% endblock %}
| {% block body %}
#page-container
#page-header
.page-title
| Shot {{ shot.name }}
#page-content
.page-triplet-container.homepage
.row
.col-md-8
p this is a shot.
.col-md-4
.well Task details here
| {% endblock %}