Source code for publiforge.views.task

# $Id$
# pylint: disable = C0322
"""Task view callables."""

from os import sep
from time import time
from sqlalchemy import func, desc, and_, or_
from colander import Mapping, SchemaNode, Length, OneOf, String, Date

from pyramid.view import view_config
from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound

from .selection import Selection
from ..lib.i18n import _
from ..lib.utils import MIME_TYPES, has_permission, normalize_spaces, age
from ..lib.utils import rst2xhtml
from ..lib.viewutils import get_action, get_selection, file_upload
from ..lib.viewutils import file_details, current_storage, current_project
from ..lib.viewutils import selection2container
from ..lib.packutils import pack2task, pack_upload_content, pack_download
from ..lib.packutils import operator_labels, operator_label
from ..lib.form import Form
from ..lib.widget import TabSet
from ..lib.paging import PAGE_SIZES, Paging
from ..models import LABEL_LEN, DESCRIPTION_LEN, DBSession, close_dbsession
from ..models.projects import Project
from ..models.roles import RoleUser
from ..models.packs import FILE_TYPE_MARKS, Pack, PackEvent
from ..models.tasks import LINK_TYPES, Task, TaskProcessing, TaskLink


TASK_SETTINGS_TABS = (_('Description'), _('Processings'), _('Connections'))


# =============================================================================
[docs]class TaskView(object): """Class to manage tasks.""" # ------------------------------------------------------------------------- def __init__(self, request): """Constructor method.""" request.add_finished_callback(close_dbsession) self._request = request # -------------------------------------------------------------------------
[docs] @view_config(route_name='task_index', renderer='../Templates/tsk_index.pt') @view_config( route_name='task_index_task', renderer='../Templates/tsk_index.pt') @view_config( route_name='task_index_task_pack', renderer='../Templates/tsk_index.pt') @view_config(route_name='task_index', renderer='json', xhr=True) @view_config(route_name='task_index_task', renderer='json', xhr=True) @view_config(route_name='task_index_task_pack', renderer='json', xhr=True) def index(self): """List tasks to do.""" # Current task and its packs project = current_project(self._request) if project['entries'] not in ('all', 'tasks'): raise HTTPForbidden() current_task = self._current_task( project['project_id'], project['perm'] == 'leader' and self._request.params.get('f_operator')) # Form schema = SchemaNode(Mapping()) schema.add(SchemaNode(String(), name='target', missing=None)) schema.add(SchemaNode(String(), name='note', missing=None)) schema.add(SchemaNode(String(), name='message', missing=None)) defaults = Paging.params(self._request, 'task_packs', '-updated') defaults['processing_id'] = project['processing_id'] defaults['f_operator'] = current_task['operator_id'] defaults['target'] = current_task['target'] form = Form(self._request, schema=schema, defaults=defaults) # List of operators operators = project['perm'] == 'leader' and [ (k[0], k[2]) for k in Project.team_query(project['project_id'])] # Action storage = None action, items = self._index_action( project, current_task, operators, form) if self._request.is_xhr: return if action[0:4] == 'dnl!': return pack_download( self._request, project['project_id'], items[0]) elif action[0:4] == 'bld!': if len(items) == 1: pack = DBSession.query(Pack).filter_by( project_id=project['project_id'], pack_id=int(items[0])).first() self._request.session['project']['pack_id'] = pack.pack_id self._request.breadcrumbs.add( _('Tasks'), root_chunks=3, anchor='p%d' % pack.pack_id) return HTTPFound(self._request.route_path( 'build_launch', project_id=project['project_id'], processing_id=project['processing_id'], pack_ids='_'.join(items))) elif action[0:4] == 'upl?': storage = current_storage( self._request, action[4:].split('/')[0])[0] # List of tasks tasks = DBSession.query( Task.task_id, Task.label, Task.deadline, func.count(Pack.task_id))\ .join(Pack).filter(Task.project_id == project['project_id'])\ .order_by(Task.label) if current_task['operator_roles']: tasks = tasks.filter(or_( and_(Pack.operator_type == 'user', Pack.operator_id == current_task['operator_id']), and_(Pack.operator_type == 'role', Pack.operator_id.in_(current_task['operator_roles'])))) else: tasks = tasks.filter( Pack.operator_type == 'user', Pack.operator_id == current_task['operator_id']) tasks = tasks.group_by(Task.task_id, Task.label, Task.deadline).all() # List of packs and builds packs = self._paging_packs(current_task, defaults) builds = packs and self._builds( project['project_id'], [k.pack_id for k in packs]) or {} # Breadcrumb trail self._request.breadcrumbs.add( _('Tasks'), 3, root_chunks=3, anchor=self._request.session['project']['pack_id'] is not None and 'p%d' % self._request.session['project']['pack_id'] or None) return { 'age': age, 'sep': sep, 'project': project, 'form': form, 'action': action, 'operators': operators, 'storage': storage, 'tasks': tasks, 'current_task': current_task, 'packs': packs, 'builds': builds, 'FILE_TYPE_MARKS': FILE_TYPE_MARKS, 'MIME_TYPES': MIME_TYPES, 'PAGE_SIZES': PAGE_SIZES, 'rst2xhtml': rst2xhtml, 'i_packeditor': project['perm'] in ('leader', 'packmaker', 'packeditor')}
# -------------------------------------------------------------------------
[docs] @view_config(route_name='task_view', renderer='../Templates/tsk_view.pt') def view(self): """Display task settings.""" # Permission project = current_project(self._request) task = DBSession.query(Task).filter_by( task_id=int(self._request.matchdict.get('task_id'))).first() if task is None: raise HTTPNotFound() tab_set = TabSet(self._request, TASK_SETTINGS_TABS) operator = operator_label( self._request, project, task.operator_type, task.operator_id) i_editor = project['perm'] == 'leader' \ or has_permission(self._request, 'prj_editor') self._request.breadcrumbs.add( _('Task settings'), replace=self._request.route_path( 'task_edit', project_id=task.project_id, task_id=task.task_id)) return { 'tab_set': tab_set, 'project': project, 'task': task, 'operator': operator, 'i_editor': i_editor, 'LINK_TYPES': LINK_TYPES}
# -------------------------------------------------------------------------
[docs] @view_config(route_name='task_create', renderer='../Templates/tsk_edit.pt') def create(self): """Create a task.""" # Authorization project = current_project(self._request) if project['perm'] != 'leader' \ and not has_permission(self._request, 'prj_editor'): raise HTTPForbidden() # Environment form, tab_set, operators = self._settings_form(project) # Action action = get_action(self._request)[0] if action == 'sav!' and form.validate(): task = self._create(project['project_id'], form.values) if task is not None: self._request.breadcrumbs.pop() del self._request.session['project'] del self._request.session['menu'] return HTTPFound(self._request.route_path( 'task_edit', project_id=task.project_id, task_id=task.task_id)) self._request.breadcrumbs.add(_('Task creation')) return { 'form': form, 'tab_set': tab_set, 'project': project, 'operators': operators, 'task': None, 'action': None}
# -------------------------------------------------------------------------
[docs] @view_config(route_name='task_edit', renderer='../Templates/tsk_edit.pt') def edit(self): """Edit a task.""" # Authorization project = current_project(self._request) if project['perm'] != 'leader' \ and not has_permission(self._request, 'prj_editor'): raise HTTPForbidden() # Environment task = DBSession.query(Task).filter_by( project_id=project['project_id'], task_id=int(self._request.matchdict.get('task_id'))).first() if task is None: raise HTTPNotFound() form, tab_set, operators = self._settings_form(project, task) # Action action = self._edit_action(task) view_path = self._request.route_path( 'task_view', project_id=task.project_id, task_id=task.task_id) if action == 'sav!' and form.validate(task) \ and self._save(task, form.values): del self._request.session['project'] return HTTPFound(view_path) if form.has_error(): self._request.session.flash(_('Correct errors.'), 'alert') # Breadcrumbs self._request.breadcrumbs.add(_('Task settings'), replace=view_path) return { 'form': form, 'tab_set': tab_set, 'project': project, 'operators': operators, 'task': task, 'action': action, 'LINK_TYPES': LINK_TYPES}
# ------------------------------------------------------------------------- def _index_action(self, project, current_task, operators, form): """Process action for index view. :param project: (dictionary) Current project. :param current_task: (dictionary) Current task. :param operators: (list) Names of operators. :param form: (:class:`~..lib.form.Form` instance) Current form. :return: (tuple) A tuple such as ``(action, items)`` """ action, items = get_action(self._request) i_packeditor = \ project['perm'] in ('leader', 'packmaker', 'pack_editor') if not self._request.is_xhr and not form.validate(): action = '%s?%s' % (action[0:3], action[4:]) if action[0:3] == 'not': pack = DBSession.query(Pack).filter_by( project_id=project['project_id'], pack_id=int(items[0]))\ .first() self._request.session['project']['pack_id'] = pack.pack_id if action[0:4] == 'not!': pack.note = form.values['note'] \ and form.values['note'].strip() or None DBSession.commit() action = '' form.values['note'] = pack.note elif action[0:4] == 'sel!': Selection(self._request).add((action[4:],)) action = '' elif not i_packeditor and action[0:4] == 'get!': self._request.session.flash(_('Not authorized!'), 'alert') action = '' if action[0:4] == 'get!': pack = DBSession.query(Pack).filter_by( project_id=project['project_id'], pack_id=int(action[4:]))\ .first() if pack.operator_type == 'user': selection2container( self._request, 'pck', pack, 'file', get_selection(self._request)) action = '' elif not i_packeditor and action[0:3] == 'ipk': self._request.session.flash(_('Not authorized!'), 'alert') action = '' elif action[0:3] == 'ipk': pack = DBSession.query(Pack).filter_by( project_id=project['project_id'], pack_id=int(items[0]))\ .first() self._request.session['project']['pack_id'] = pack.pack_id if action[0:4] == 'ipk!': message = form.values['message'] \ or project['task_labels'][current_task['task_id']] pack_upload_content( self._request, project['project_id'], message, label=pack.label) action = '' self._request.session.flash(_('Pack has been updated.')) elif action[0:4] == 'upl!': message = form.values['message'] \ or project['task_labels'][current_task['task_id']] file_upload(self._request, action[4:], message) self._request.registry['fbuild'].forget_results( project['project_id'], self._request.session['user_id'], pack_id=current_task['pack_id']) action = '' elif action[0:4] in ('nxt!', 'tak!'): action, items = self._index_action_task( project, current_task, operators, form, action, items) return action, items # ------------------------------------------------------------------------- def _index_action_task( self, project, current_task, operators, form, action, items): """Process action implying task for index view. :param project: (dictionary) Current project. :param current_task: (dictionary) Current task. :param operators: (list) Names of operators. :param form: (:class:`~..lib.form.Form` instance) Current form. :param action: (string) Current action. :param items: (list) List of task IDs to process. :return: (tuple) A tuple such as ``(action, items)`` """ # pylint: disable = too-many-arguments for pack_id in items: pack = DBSession.query(Pack).filter_by( project_id=project['project_id'], pack_id=int(pack_id)).first() if action[0:4] == 'nxt!' and pack.operator_type == 'user': pack2task( self._request, pack, form.values['target'][0:4], int(form.values['target'][4:])) elif action[0:4] == 'nxt!': self._request.session.flash(_('First accept tasks.'), 'alert') break elif action[0:4] == 'tak!' and pack.operator_type == 'role': pack.operator_type = 'user' pack.operator_id = current_task['operator_id'] name = operators and dict(operators)[pack.operator_id] \ or self._request.session['name'] DBSession.add(PackEvent( project['project_id'], pack.pack_id, current_task['task_id'], project['task_labels'][current_task['task_id']], pack.operator_type, pack.operator_id, name)) DBSession.commit() if len(items) == 1: self._request.session['project']['pack_id'] = pack.pack_id form.forget('#') action = '' return action, items # ------------------------------------------------------------------------- def _edit_action(self, task): """Return current action for edit view. :param task: (:class:`~..models.tasks.Task` instance, optional) Current task object. :return: (string) Current action. """ action, items = get_action(self._request) # Processings if action[0:4] == 'apr!': done = [k.processing_id for k in task.processings] for item in items: item = int(item) if item not in done: task.processings.append(TaskProcessing( task.project_id, task.task_id, item)) DBSession.commit() elif action[0:4] == 'rpr!': DBSession.query(TaskProcessing).filter_by( project_id=task.project_id, task_id=task.task_id, processing_id=int(action[4:])).delete() DBSession.commit() # Links if action[0:4] == 'alk!': done = [k.target_id for k in task.links] for item in items: item = int(item) if item not in done and item != task.task_id: task.links.append(TaskLink( task.project_id, task.task_id, item)) DBSession.commit() elif action[0:4] == 'rlk!': DBSession.query(TaskLink).filter_by( project_id=task.project_id, task_id=task.task_id, target_id=int(action[4:])).delete() DBSession.commit() return action # ------------------------------------------------------------------------- def _settings_form(self, project, task=None): """Return a task settings form. :param project: (dictionary) Current project dictionary. :param task: (:class:`~..models.tasks.Task` instance, optional) Current task object. :return: (tuple) A tuple such as ``(form, tab_set, operators)`` """ operators = operator_labels(project, True) schema = SchemaNode(Mapping()) schema.add(SchemaNode( String(), name='label', validator=Length(min=2, max=LABEL_LEN))) schema.add(SchemaNode( String(), name='description', missing='', validator=Length(max=DESCRIPTION_LEN))) schema.add(SchemaNode( String(), name='operator', validator=OneOf([k[0] for k in operators]))) schema.add(SchemaNode(Date(), name='deadline', missing=None)) defaults = {} if task is not None: defaults['operator'] = '%s%s' % ( task.operator_type, str(task.operator_id)) for link in task.links: schema.add(SchemaNode( String(), name='lnk_%d' % link.target_id, validator=OneOf(LINK_TYPES.keys()))) return ( Form(self._request, schema=schema, defaults=defaults, obj=task), TabSet(self._request, TASK_SETTINGS_TABS), operators) # ------------------------------------------------------------------------- def _create(self, project_id, values): """Create a record in ``project_task`` table. :param project_id: (string) Project ID. :param values: (dictionary) Values to record. :return: (:class:`~..models.tasks.Task` instance) """ # Check unicity label = normalize_spaces(values['label'], LABEL_LEN) task = DBSession.query(Task).filter_by( project_id=project_id, label=label).first() if task is not None: self._request.session.flash( _('This task already exists.'), 'alert') return # Create task operator_type = values['operator'][0:4] task = Task( project_id, label, values['description'], values['deadline'], operator_type, operator_type != 'auto' and int(values['operator'][4:]) or None) DBSession.add(task) DBSession.commit() return task # ------------------------------------------------------------------------- @classmethod def _save(cls, task, values): """Save task settings. :param task: (:class:`~..models.tasks.Task` instance) Task to update. :param values: (dictionary) Form values. :return: (boolean) ``True`` if succeeds. """ # Operator task.operator_type = values['operator'][0:4] task.operator_id = task.operator_type != 'auto' \ and int(values['operator'][4:]) or None # Links for link in task.links: link.link_type = values['lnk_%d' % link.target_id] DBSession.commit() return True # ------------------------------------------------------------------------- def _current_task(self, project_id, operator_id=None): """Initialize, if necessary, ``request.session['task']`` and return it as current task dictionary. :param project_id: (string) Project ID. :param operator_id: (string, default=current user ID) Operator ID. :return: (dictionary) The dictionary has the following keys: ``project_id``, ``task_id``, ``operator_id``, ``operator_roles``, ``description``, ``new_task``, ``processing_ids``, ``targets``, ``target``, ``pack_id``, ``pack_label`` and ``files``. """ # Task dictionary if 'task' not in self._request.session \ or self._request.session['task']['project_id'] != project_id: self._request.session['task'] = { 'project_id': project_id, 'task_id': None, 'operator_id': None, 'pack_id': None, 'target': None} task_dict = self._request.session['task'] # Task task_id = self._request.matchdict.get('task_id') task_id = task_id is not None and int(task_id) or None task_dict['new_task'] = task_id is not None \ and task_id != task_dict['task_id'] if task_id is not None or self._request.GET.get('close_task'): task_dict['task_id'] = task_id if task_dict['task_id'] is not None and task_dict['new_task']: record = DBSession.query(Task).filter_by( project_id=project_id, task_id=task_dict['task_id']).first() if record is None: raise HTTPNotFound(comment=_('This task does not exist!')) task_dict['description'] = record.description task_dict['processing_ids'] = [ k.processing_id for k in record.processings] task_dict['targets'] = [ '%s%d' % (k.link_type[0:4], k.target_id) for k in record.links] if task_dict['task_id'] is None: task_dict['description'] = None task_dict['processing_ids'] = [] task_dict['targets'] = [] if self._request.params.get('target'): task_dict['target'] = self._request.params.get('target') # Operator operator_id = operator_id and int(operator_id) \ or task_dict['operator_id'] or self._request.session['user_id'] if task_dict['operator_id'] != operator_id: task_dict['operator_id'] = operator_id task_dict['operator_roles'] = tuple([ k[0] for k in DBSession.query(RoleUser.role_id).filter( RoleUser.project_id == project_id, RoleUser.user_id == operator_id)]) # Pack with files pack_id = self._request.matchdict.get('pack_id') pack_id = pack_id is not None and int(pack_id) or None new_pack = pack_id is not None \ and pack_id != task_dict['pack_id'] if pack_id is not None or self._request.GET.get('close_pack'): task_dict['pack_id'] = pack_id if task_dict['pack_id'] is not None and new_pack: record = DBSession.query(Pack).filter_by( project_id=project_id, pack_id=task_dict['pack_id']).first() task_dict['pack_label'] = record and record.label task_dict['files'] = file_details(self._request, record) self._request.session['project']['pack_id'] = pack_id if task_dict['pack_id'] is None: task_dict['pack_label'] = None task_dict['files'] = [] return task_dict # ------------------------------------------------------------------------- def _paging_packs(self, task, filters): """Return a :class:`~..lib.widget.Paging` object filled with packs of project ``project_id`` currently used by task ``current_task['task_id']``. :param task: (dictionary) Current task dictionary. :param filters: (dictionary) Filters for pack list. :return: (:class:`~..lib.widget.Paging` instance) """ if task['task_id'] is None: return None # Query query = DBSession.query(Pack).filter_by( project_id=task['project_id'], task_id=task['task_id']) if task['operator_roles']: query = query.filter(or_( and_(Pack.operator_type == 'user', Pack.operator_id == task['operator_id']), and_(Pack.operator_type == 'role', Pack.operator_id.in_(task['operator_roles'])))) else: query = query.filter_by( operator_type='user', operator_id=task['operator_id']) if 'f_label' in filters: query = query.filter( Pack.label.ilike('%%%s%%' % filters['f_label'])) # Order by oby = getattr(Pack, filters['sort'][1:]) query = query.order_by(desc(oby) if filters['sort'][0] == '-' else oby) return Paging(self._request, 'task_packs', query) # ------------------------------------------------------------------------- def _builds(self, project_id, pack_ids): """Return error messages of builds on packs ``packs``. :param project_id: (string) Project ID. :param pack_ids: (list) Visible pack IDs. :return: (dictionary) A dictionary such as ``{pack_id: (build_id, message),...}``. """ builds = {} expire = time() - 5 * int(self._request.registry.settings.get( 'storage.report_ttl', 120)) for build in self._request.registry['fbuild'].build_list( project_id, self._request.session['user_id']): if build['pack_id'] not in pack_ids or build['pack_id'] in builds \ or build['status'] == 'end': continue result = self._request.registry['fbuild'].result(build['build_id']) if result.get('message') and result['log'][-1][0] > expire: builds[build['pack_id']] = ( build['build_id'], result['message']) return builds