Started working on shots UI & management
This commit is contained in:
@@ -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."""
|
||||||
|
@@ -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
40
attract/shot_manager.py
Normal 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
75
attract/shots.py
Normal 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
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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',
|
||||||
|
30
src/templates/attract/shots/for_project.jade
Normal file
30
src/templates/attract/shots/for_project.jade
Normal 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 %}
|
23
src/templates/attract/shots/index.jade
Normal file
23
src/templates/attract/shots/index.jade
Normal 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 %}
|
16
src/templates/attract/shots/shot.jade
Normal file
16
src/templates/attract/shots/shot.jade
Normal 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 %}
|
Reference in New Issue
Block a user