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
|
||||
|
||||
import attract.task_manager
|
||||
import attract.shot_manager
|
||||
|
||||
EXTENSION_NAME = 'attract'
|
||||
|
||||
@@ -13,6 +14,7 @@ class AttractExtension(PillarExtension):
|
||||
def __init__(self):
|
||||
self._log = logging.getLogger('%s.AttractExtension' % __name__)
|
||||
self.task_manager = attract.task_manager.TaskManager()
|
||||
self.shot_manager = attract.shot_manager.ShotManager()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -52,12 +54,14 @@ class AttractExtension(PillarExtension):
|
||||
:rtype: list of flask.Blueprint objects.
|
||||
"""
|
||||
|
||||
from . import modules, tasks
|
||||
from . import modules, tasks, shots
|
||||
|
||||
return [
|
||||
modules.blueprint,
|
||||
tasks.blueprint,
|
||||
tasks.perproject_blueprint,
|
||||
shots.blueprint,
|
||||
shots.perproject_blueprint,
|
||||
]
|
||||
|
||||
@property
|
||||
@@ -78,12 +82,11 @@ class AttractExtension(PillarExtension):
|
||||
subversion.task_logged.connect(self.task_manager.task_logged_in_svn)
|
||||
|
||||
|
||||
def _get_task_manager():
|
||||
"""Returns the Attract task manager of the current application."""
|
||||
def _get_current_attract():
|
||||
"""Returns the Attract extension of the current application."""
|
||||
|
||||
current_attract = flask.current_app.pillar_extensions[EXTENSION_NAME]
|
||||
return current_attract.task_manager
|
||||
return flask.current_app.pillar_extensions[EXTENSION_NAME]
|
||||
|
||||
|
||||
current_task_manager = LocalProxy(_get_task_manager)
|
||||
"""Attract Task manager of the current app."""
|
||||
current_attract = LocalProxy(_get_current_attract)
|
||||
"""Attract extension of the current app."""
|
||||
|
@@ -23,6 +23,7 @@ node_type_shot = {
|
||||
'review',
|
||||
'final'
|
||||
],
|
||||
'default': 'todo',
|
||||
},
|
||||
'notes': {
|
||||
'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 . import attrs_extra
|
||||
from .node_types.shot import node_type_shot
|
||||
|
||||
|
||||
@attr.s
|
||||
@@ -31,7 +32,7 @@ class TaskManager(object):
|
||||
"""
|
||||
|
||||
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:
|
||||
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 .node_types.task import node_type_task
|
||||
from . import current_task_manager
|
||||
from . import current_attract
|
||||
|
||||
blueprint = Blueprint('attract.tasks', __name__, url_prefix='/tasks')
|
||||
perproject_blueprint = Blueprint('attract.tasks.perproject', __name__,
|
||||
@@ -21,7 +21,7 @@ def index():
|
||||
return render_template('attract/tasks/index.html')
|
||||
|
||||
|
||||
@perproject_blueprint.route('/')
|
||||
@perproject_blueprint.route('/', endpoint='index')
|
||||
@attract_project_view()
|
||||
def for_project(project):
|
||||
api = pillar_api()
|
||||
@@ -59,7 +59,7 @@ def save(project, task_id):
|
||||
log.info('Saving task %s', task_id)
|
||||
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 })
|
||||
|
||||
@@ -69,7 +69,7 @@ def save(project, task_id):
|
||||
@perproject_blueprint.route('/create/<task_type>', methods=['POST', 'GET'])
|
||||
@attract_project_view()
|
||||
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.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