Templates & Flask end-points for managing assets.
This commit is contained in:
@@ -64,14 +64,16 @@ class AttractExtension(PillarExtension):
|
||||
|
||||
from . import routes
|
||||
import attract.tasks.routes
|
||||
import attract.shots_and_assets.routes
|
||||
import attract.shots_and_assets.routes_assets
|
||||
import attract.shots_and_assets.routes_shots
|
||||
import attract.subversion.routes
|
||||
|
||||
return [
|
||||
routes.blueprint,
|
||||
attract.tasks.routes.blueprint,
|
||||
attract.tasks.routes.perproject_blueprint,
|
||||
attract.shots_and_assets.routes.perproject_blueprint,
|
||||
attract.shots_and_assets.routes_assets.perproject_blueprint,
|
||||
attract.shots_and_assets.routes_shots.perproject_blueprint,
|
||||
attract.subversion.routes.blueprint,
|
||||
attract.subversion.routes.api_blueprint,
|
||||
]
|
||||
|
97
attract/shots_and_assets/routes_assets.py
Normal file
97
attract/shots_and_assets/routes_assets.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
|
||||
import flask_login
|
||||
from flask import Blueprint, render_template, request
|
||||
import flask
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
|
||||
import pillarsdk
|
||||
import pillar.api.utils
|
||||
from pillar.web.system_util import pillar_api
|
||||
|
||||
from attract.routes import attract_project_view
|
||||
from attract.node_types.asset import node_type_asset, task_types
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
from pillar.web.utils import get_file
|
||||
|
||||
from . import routes_common
|
||||
|
||||
perproject_blueprint = Blueprint('attract.assets.perproject', __name__,
|
||||
url_prefix='/<project_url>/assets')
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/', endpoint='index')
|
||||
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
||||
@attract_project_view(extension_props=True)
|
||||
def for_project(project, attract_props, task_id=None, asset_id=None):
|
||||
assets, tasks_for_assets, task_types_for_template = routes_common.for_project(
|
||||
node_type_asset['name'],
|
||||
task_types,
|
||||
project, attract_props, task_id, asset_id)
|
||||
|
||||
return render_template('attract/assets/for_project.html',
|
||||
assets=assets,
|
||||
tasks_for_assets=tasks_for_assets,
|
||||
task_types=task_types_for_template,
|
||||
open_task_id=task_id,
|
||||
open_asset_id=asset_id,
|
||||
project=project,
|
||||
attract_props=attract_props)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/<asset_id>')
|
||||
@attract_project_view(extension_props=True)
|
||||
def view_asset(project, attract_props, asset_id):
|
||||
if not request.is_xhr:
|
||||
return for_project(project, attract_props, asset_id=asset_id)
|
||||
|
||||
asset, node_type = routes_common.view_node(project, asset_id, node_type_asset['name'])
|
||||
|
||||
return render_template('attract/assets/view_asset_embed.html',
|
||||
asset=asset,
|
||||
project=project,
|
||||
asset_node_type=node_type,
|
||||
attract_props=attract_props)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/<asset_id>', methods=['POST'])
|
||||
@attract_project_view()
|
||||
def save(project, asset_id):
|
||||
log.info('Saving asset %s', asset_id)
|
||||
log.debug('Form data: %s', request.form)
|
||||
|
||||
asset_dict = request.form.to_dict()
|
||||
current_attract.shot_manager.edit_asset(asset_id, **asset_dict)
|
||||
|
||||
# Return the patched node in all its glory.
|
||||
api = pillar_api()
|
||||
asset = pillarsdk.Node.find(asset_id, api=api)
|
||||
return pillar.api.utils.jsonify(asset.to_dict())
|
||||
|
||||
|
||||
@perproject_blueprint.route('/create', methods=['POST'])
|
||||
@attract_project_view()
|
||||
def create_asset(project):
|
||||
asset = current_attract.shot_manager.create_asset(project)
|
||||
|
||||
resp = flask.make_response()
|
||||
resp.headers['Location'] = flask.url_for('.view_asset',
|
||||
project_url=project['url'],
|
||||
asset_id=asset['_id'])
|
||||
resp.status_code = 201
|
||||
return flask.make_response(flask.jsonify({'asset_id': asset['_id']}), 201)
|
||||
|
||||
|
||||
@perproject_blueprint.route('/<asset_id>/activities')
|
||||
@attract_project_view()
|
||||
def activities(project, asset_id):
|
||||
if not request.is_xhr:
|
||||
return flask.redirect(flask.url_for('.view_asset',
|
||||
project_url=project.url,
|
||||
asset_id=asset_id))
|
||||
acts = current_attract.activities_for_node(asset_id)
|
||||
|
||||
# NOTE: this uses the 'shots' template, because it has everything we ever wanted.
|
||||
return flask.render_template('attract/shots/view_activities_embed.html',
|
||||
activities=acts)
|
69
attract/shots_and_assets/routes_common.py
Normal file
69
attract/shots_and_assets/routes_common.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import logging
|
||||
|
||||
import flask
|
||||
import flask_login
|
||||
import werkzeug.exceptions as wz_exceptions
|
||||
|
||||
import pillarsdk
|
||||
from pillar.web.system_util import pillar_api
|
||||
from pillar.web.utils import get_file
|
||||
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def for_project(node_type_name, task_types_for_nt, project, attract_props,
|
||||
task_id=None, shot_or_asset_id=None):
|
||||
"""Common view code for assets and shots /attract/<project_url>/{assets,shots}"""
|
||||
api = pillar_api()
|
||||
|
||||
found = pillarsdk.Node.all({
|
||||
'where': {
|
||||
'project': project['_id'],
|
||||
'node_type': node_type_name,
|
||||
},
|
||||
'sort': [
|
||||
('properties.cut_in_timeline_in_frames', 1),
|
||||
]
|
||||
}, api=api)
|
||||
nodes = found['_items']
|
||||
|
||||
thumb_placeholder = flask.url_for('static_attract', filename='assets/img/placeholder.jpg')
|
||||
for node in nodes:
|
||||
picture = get_file(node.picture, api=api)
|
||||
if picture:
|
||||
node._thumbnail = next((var.link for var in picture.variations
|
||||
if var.size == 't'), thumb_placeholder)
|
||||
else:
|
||||
node._thumbnail = thumb_placeholder
|
||||
|
||||
# The placeholder can be shown quite small, but otherwise the aspect ratio of
|
||||
# the actual thumbnail should be taken into account. Since it's different for
|
||||
# each project, we can't hard-code a proper height.
|
||||
node._thumbnail_height = '30px' if node._thumbnail is thumb_placeholder else 'auto'
|
||||
|
||||
tasks_for_nodes = current_attract.shot_manager.tasks_for_nodes(nodes, task_types_for_nt)
|
||||
|
||||
# Append the task type onto which 'other' tasks are mapped.
|
||||
task_types_for_template = task_types_for_nt + [None]
|
||||
|
||||
return nodes, tasks_for_nodes, task_types_for_template
|
||||
|
||||
|
||||
def view_node(project, node_id, node_type_name):
|
||||
"""Returns the node if the user has access.
|
||||
|
||||
Uses attract.ROLES_REQUIRED_TO_VIEW_ITEMS to check permissions.
|
||||
"""
|
||||
|
||||
# asset list is public, asset details are not.
|
||||
if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
api = pillar_api()
|
||||
|
||||
node = pillarsdk.Node.find(node_id, api=api)
|
||||
node_type = project.get_node_type(node_type_name)
|
||||
|
||||
return node, node_type
|
@@ -10,10 +10,12 @@ import pillar.api.utils
|
||||
from pillar.web.system_util import pillar_api
|
||||
|
||||
from attract.routes import attract_project_view
|
||||
from attract.node_types.shot import node_type_shot
|
||||
from attract.node_types.shot import node_type_shot, task_types
|
||||
from attract import current_attract, ROLES_REQUIRED_TO_VIEW_ITEMS
|
||||
from pillar.web.utils import get_file
|
||||
|
||||
from . import routes_common
|
||||
|
||||
perproject_blueprint = Blueprint('attract.shots.perproject', __name__,
|
||||
url_prefix='/<project_url>/shots')
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -23,40 +25,10 @@ log = logging.getLogger(__name__)
|
||||
@perproject_blueprint.route('/with-task/<task_id>', endpoint='with_task')
|
||||
@attract_project_view(extension_props=True)
|
||||
def for_project(project, attract_props, task_id=None, shot_id=None):
|
||||
api = pillar_api()
|
||||
|
||||
found = pillarsdk.Node.all({
|
||||
'where': {
|
||||
'project': project['_id'],
|
||||
'node_type': node_type_shot['name'],
|
||||
},
|
||||
'sort': [
|
||||
('properties.cut_in_timeline_in_frames', 1),
|
||||
]
|
||||
}, api=api)
|
||||
shots = found['_items']
|
||||
|
||||
thumb_placeholder = flask.url_for('static_attract', filename='assets/img/placeholder.jpg')
|
||||
for shot in shots:
|
||||
picture = get_file(shot.picture, api=api)
|
||||
if picture:
|
||||
shot._thumbnail = next((var.link for var in picture.variations
|
||||
if var.size == 't'), thumb_placeholder)
|
||||
else:
|
||||
shot._thumbnail = thumb_placeholder
|
||||
|
||||
# The placeholder can be shown quite small, but otherwise the aspect ratio of
|
||||
# the actual thumbnail should be taken into account. Since it's different for
|
||||
# each project, we can't hard-code a proper height.
|
||||
shot._thumbnail_height = '30px' if shot._thumbnail is thumb_placeholder else 'auto'
|
||||
|
||||
tasks_for_shots = current_attract.shot_manager.tasks_for_nodes(
|
||||
shots,
|
||||
attract_props.task_types.attract_shot,
|
||||
)
|
||||
|
||||
# Append the task type onto which 'other' tasks are mapped.
|
||||
task_types = attract_props.task_types.attract_shot + [None]
|
||||
shots, tasks_for_shots, task_types_for_template = routes_common.for_project(
|
||||
node_type_shot['name'],
|
||||
task_types,
|
||||
project, attract_props, task_id, shot_id)
|
||||
|
||||
# Some aggregated stats
|
||||
stats = {
|
||||
@@ -69,7 +41,7 @@ def for_project(project, attract_props, task_id=None, shot_id=None):
|
||||
return render_template('attract/shots/for_project.html',
|
||||
shots=shots,
|
||||
tasks_for_shots=tasks_for_shots,
|
||||
task_types=task_types,
|
||||
task_types=task_types_for_template,
|
||||
open_task_id=task_id,
|
||||
open_shot_id=shot_id,
|
||||
project=project,
|
||||
@@ -83,14 +55,7 @@ def view_shot(project, attract_props, shot_id):
|
||||
if not request.is_xhr:
|
||||
return for_project(project, attract_props, shot_id=shot_id)
|
||||
|
||||
# Shot list is public, shot details are not.
|
||||
if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
|
||||
raise wz_exceptions.Forbidden()
|
||||
|
||||
api = pillar_api()
|
||||
|
||||
shot = pillarsdk.Node.find(shot_id, api=api)
|
||||
node_type = project.get_node_type(node_type_shot['name'])
|
||||
shot, node_type = routes_common.view_node(project, shot_id, node_type_shot['name'])
|
||||
|
||||
return render_template('attract/shots/view_shot_embed.html',
|
||||
shot=shot,
|
Reference in New Issue
Block a user