Added user-specific task list.

This commit is contained in:
2016-10-05 10:30:10 +02:00
parent 60c13615bf
commit 75e6b39069
7 changed files with 110 additions and 15 deletions

View File

@@ -7,8 +7,8 @@ from attract.node_types.task import node_type_task
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def fetch_task_parent_info(node): def fetch_task_extra_info(node):
"""Extends the node with some parent info. """Extends the node with some info about its parent and project.
This allows us to link to the shot the task is attached to. This allows us to link to the shot the task is attached to.
However, such a link requires at least knowing the parent node type, However, such a link requires at least knowing the parent node type,
@@ -18,11 +18,18 @@ def fetch_task_parent_info(node):
if node.get('node_type') != node_type_task['name']: if node.get('node_type') != node_type_task['name']:
return return
fetch_task_parent_info(node)
fetch_task_project_info(node)
def fetch_task_parent_info(node):
"""Store node parent info in node['_parent_info']."""
parent_id = node.get('parent') parent_id = node.get('parent')
if not parent_id: if not parent_id:
return return
nodes_coll = current_app.data.driver.db['nodes'] nodes_coll = current_app.db()['nodes']
parent = nodes_coll.find_one({'_id': parent_id}, parent = nodes_coll.find_one({'_id': parent_id},
projection={'node_type': 1, projection={'node_type': 1,
'name': 1}) 'name': 1})
@@ -35,11 +42,32 @@ def fetch_task_parent_info(node):
node['_parent_info'] = parent node['_parent_info'] = parent
def fetch_task_project_info(node):
"""Store node project info in node['_project_info']."""
project_id = node.get('project')
if not project_id:
log.warning('Task node %s has no project!', node['_id'])
return
proj_coll = current_app.db()['projects']
project = proj_coll.find_one({'_id': project_id},
projection={'name': 1,
'url': 1})
if project is None:
log.warning("Task node %s has project %s, but the project doesn't exist.",
node['_id'], project_id)
return
project.pop('_id') # always there, but also already included in the node.
node['_project_info'] = project
def fetch_tasks_parent_info(nodes): def fetch_tasks_parent_info(nodes):
for node in nodes['_items']: for node in nodes['_items']:
fetch_task_parent_info(node) fetch_task_extra_info(node)
def setup_app(app): def setup_app(app):
app.on_fetched_item_nodes += fetch_task_parent_info app.on_fetched_item_nodes += fetch_task_extra_info
app.on_fetched_resource_nodes += fetch_tasks_parent_info app.on_fetched_resource_nodes += fetch_tasks_parent_info

View File

@@ -2,6 +2,7 @@ import logging
from flask import Blueprint, render_template, request from flask import Blueprint, render_template, request
import flask import flask
import flask_login
import pillarsdk import pillarsdk
from pillar.web.system_util import pillar_api from pillar.web.system_util import pillar_api
@@ -19,7 +20,23 @@ log = logging.getLogger(__name__)
@blueprint.route('/') @blueprint.route('/')
def index(): def index():
return render_template('attract/tasks/index.html') user = flask_login.current_user
if not user.is_authenticated:
return render_template('attract/tasks/index.html')
api = pillar_api()
# TODO: also include tasks assigned to any of the user's groups.
tasks = pillarsdk.Node.all({
'where': {
'properties.assigned_to.users': user.objectid,
'node_type': node_type_task['name'],
}
}, api=api)
return render_template('attract/tasks/for_user.html',
tasks=tasks['_items'],
task_count=tasks['_meta']['total'])
@blueprint.route('/<task_id>', methods=['DELETE']) @blueprint.route('/<task_id>', methods=['DELETE'])
@@ -86,7 +103,6 @@ def save(project, task_id):
@perproject_blueprint.route('/create', methods=['POST']) @perproject_blueprint.route('/create', methods=['POST'])
@attract_project_view() @attract_project_view()
def create_task(project): def create_task(project):
task_type = request.form['task_type'] task_type = request.form['task_type']
parent = request.form.get('parent', None) parent = request.form.get('parent', None)

View File

@@ -15,15 +15,17 @@ function _remove_task_from_list(task_id) {
/** /**
* Open an item such as tasks/shots in the #item-details div * Open an item such as tasks/shots in the #item-details div
*/ */
function item_open(item_id, item_type, pushState) function item_open(item_id, item_type, pushState, project_url)
{ {
if (item_id === undefined || item_type === undefined) { if (item_id === undefined || item_type === undefined) {
throw new ReferenceError("item_open(" + item_id + ", " + item_type + ") called."); throw new ReferenceError("item_open(" + item_id + ", " + item_type + ") called.");
} }
var project_url = ProjectUtils.projectUrl();
if (typeof project_url === 'undefined') { if (typeof project_url === 'undefined') {
throw new ReferenceError("ProjectUtils.projectUrl() undefined"); project_url = ProjectUtils.projectUrl();
if (typeof project_url === 'undefined') {
throw new ReferenceError("ProjectUtils.projectUrl() undefined");
}
} }
if ($(window).scrollTop() > 0) { if ($(window).scrollTop() > 0) {
@@ -76,9 +78,10 @@ function item_open(item_id, item_type, pushState)
); );
} }
function task_open(task_id) // Fine if project_url is undefined, but that requires ProjectUtils.projectUrl().
function task_open(task_id, project_url)
{ {
item_open(task_id, 'task'); item_open(task_id, 'task', true, project_url);
if (ProjectUtils.context() == 'shot'){ if (ProjectUtils.context() == 'shot'){
$('[id^="shot-"]').removeClass('active'); $('[id^="shot-"]').removeClass('active');
@@ -360,6 +363,7 @@ $(function() {
$("a.task-link[data-task-id]").click(function(e) { $("a.task-link[data-task-id]").click(function(e) {
e.preventDefault(); e.preventDefault();
var task_id = e.delegateTarget.dataset.taskId; var task_id = e.delegateTarget.dataset.taskId;
task_open(task_id); var project_url = e.delegateTarget.dataset.projectUrl; // fine if undefined
task_open(task_id, project_url);
}); });
}); });

View File

@@ -6,6 +6,8 @@ $color-background-nav: hsl(hue($color-background), 20%, 25%)
$color-background-nav-light: hsl(hue($color-background), 20%, 35%) $color-background-nav-light: hsl(hue($color-background), 20%, 35%)
$color-background-nav-dark: hsl(hue($color-background), 20%, 15%) $color-background-nav-dark: hsl(hue($color-background), 20%, 15%)
$color-background-active: #dafff5 // background colour for active items.
$font-body: 'Roboto' $font-body: 'Roboto'
$font-headings: 'Lato' $font-headings: 'Lato'
$font-size: 14px $font-size: 14px

View File

@@ -79,7 +79,7 @@
border-color: $color-background-dark border-color: $color-background-dark
border-right-color: $color-primary border-right-color: $color-primary
text-decoration: none text-decoration: none
background-color: rgba($color-background, .5) background-color: $color-background-active
.status-indicator .status-indicator
transform: scale(1.1) transform: scale(1.1)

View File

@@ -36,7 +36,7 @@
| {% block footer_scripts %} | {% block footer_scripts %}
script. script.
{% if open_task_id %} {% if open_task_id %}
$(function() { task_open('{{ open_task_id }}'); }); $(function() { item_open('{{ open_task_id }}', 'task', false); });
{% endif %} {% endif %}
script(src="{{ url_for('static_attract', filename='assets/js/vendor/clipboard.min.js')}}") script(src="{{ url_for('static_attract', filename='assets/js/vendor/clipboard.min.js')}}")

View File

@@ -0,0 +1,45 @@
| {% extends 'attract/layout.html' %}
| {% block bodyattrs %}{{ super() }} data-context='task'{% endblock %}
| {% block page_title %}Tasks for you{% endblock %}
| {% block body %}
#col_main
.col_header.task-list-header
| Your tasks ({{ task_count }})
#task-list.col-list
| {% for task in tasks %}
//- NOTE: this is tightly linked to the JS in tasks.js, function task_add()
a.col-list-item.task-list-item(
class="status-{{ task.properties.status }} task-link",
title="In project '{{ task._project_info.name }}'",
href="{{ url_for('attract.tasks.perproject.view_task', project_url=task._project_info.url, task_id=task._id) }}")
span.status-indicator
span.name {{ task.name }}
span.type {{ task.properties.task_type }}
| {% endfor %}
.col-splitter
#col_right
.col_header
span.header_text
#status-bar
#item-details
.item-details-empty
| Select a Task
| {% endblock %}
| {% block footer_scripts %}
script.
{% if open_task_id %}
$(function() { task_open('{{ open_task_id }}'); });
{% endif %}
script(src="{{ url_for('static_attract', filename='assets/js/vendor/clipboard.min.js')}}")
script(src="{{ url_for('static_attract', filename='assets/js/vendor/jquery-resizable.min.js')}}")
script.
$("#col_main").resizable({
handleSelector: ".col-splitter",
resizeHeight: false
});
| {% endblock %}