web/views/workflow.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
--- a/web/views/workflow.py	Mon Jan 04 18:40:30 2016 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,452 +0,0 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""workflow views:
-
-* IWorkflowable views and forms
-* workflow entities views (State, Transition, TrInfo)
-"""
-
-__docformat__ = "restructuredtext en"
-from cubicweb import _
-
-import os
-from warnings import warn
-
-from six import add_metaclass
-
-from logilab.mtconverter import xml_escape
-from logilab.common.graph import escape
-from logilab.common.deprecation import class_deprecated
-
-from cubicweb import Unauthorized
-from cubicweb.predicates import (has_related_entities, one_line_rset,
-                                relation_possible, match_form_params,
-                                score_entity, is_instance, adaptable)
-from cubicweb.view import EntityView
-from cubicweb.schema import display_name
-from cubicweb.web import stdmsgs, action, component, form, action
-from cubicweb.web import formfields as ff, formwidgets as fwdgs
-from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import uicfg, forms, primary, ibreadcrumbs
-from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
-from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler
-
-_pvs = uicfg.primaryview_section
-_pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
-_pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
-_pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
-_pvs.tag_object_of(('*', 'wf_info_for', '*'), 'hidden')
-for rtype in ('in_state', 'by_transition', 'from_state', 'to_state'):
-    _pvs.tag_subject_of(('*', rtype, '*'), 'hidden')
-    _pvs.tag_object_of(('*', rtype, '*'), 'hidden')
-_pvs.tag_object_of(('*', 'wf_info_for', '*'), 'hidden')
-
-_abaa = uicfg.actionbox_appearsin_addmenu
-_abaa.tag_subject_of(('BaseTransition', 'condition', 'RQLExpression'), False)
-_abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)
-_abaa.tag_object_of(('SubWorkflowExitPoint', 'destination_state', 'State'),
-                    False)
-_abaa.tag_subject_of(('*', 'wf_info_for', '*'), False)
-_abaa.tag_object_of(('*', 'wf_info_for', '*'), False)
-
-_abaa.tag_object_of(('*', 'state_of', 'CWEType'), True)
-_abaa.tag_object_of(('*', 'transition_of', 'CWEType'), True)
-_abaa.tag_subject_of(('Transition', 'destination_state', '*'), True)
-_abaa.tag_object_of(('*', 'allowed_transition', 'Transition'), True)
-_abaa.tag_object_of(('*', 'destination_state', 'State'), True)
-_abaa.tag_subject_of(('State', 'allowed_transition', '*'), True)
-_abaa.tag_object_of(('State', 'state_of', 'Workflow'), True)
-_abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
-_abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
-
-_afs = uicfg.autoform_section
-_affk = uicfg.autoform_field_kwargs
-
-# IWorkflowable views #########################################################
-
-class ChangeStateForm(forms.CompositeEntityForm):
-    # set dom id to ensure there is no conflict with edition form (see
-    # session_key() implementation)
-    __regid__ = domid = 'changestate'
-
-    form_renderer_id = 'base' # don't want EntityFormRenderer
-    form_buttons = [fwdgs.SubmitButton(),
-                    fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
-
-
-class ChangeStateFormView(form.FormViewMixIn, EntityView):
-    __regid__ = 'statuschange'
-    title = _('status change')
-    __select__ = (one_line_rset()
-                  & match_form_params('treid')
-                  & adaptable('IWorkflowable'))
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.get_entity(row, col)
-        transition = self._cw.entity_from_eid(self._cw.form['treid'])
-        form = self.get_form(entity, transition)
-        self.w(u'<h4>%s %s</h4>\n' % (self._cw._(transition.name),
-                                      entity.view('oneline')))
-        msg = self._cw._('status will change from %(st1)s to %(st2)s') % {
-            'st1': entity.cw_adapt_to('IWorkflowable').printable_state,
-            'st2': self._cw._(transition.destination(entity).name)}
-        self.w(u'<p>%s</p>\n' % msg)
-        form.render(w=self.w)
-
-    def redirectpath(self, entity):
-        return entity.rest_path()
-
-    def get_form(self, entity, transition, **kwargs):
-        # XXX used to specify both rset/row/col and entity in case implements
-        # selector (and not is_instance) is used on custom form
-        form = self._cw.vreg['forms'].select(
-            'changestate', self._cw, entity=entity, transition=transition,
-            redirect_path=self.redirectpath(entity), **kwargs)
-        trinfo = self._cw.vreg['etypes'].etype_class('TrInfo')(self._cw)
-        trinfo.eid = next(self._cw.varmaker)
-        subform = self._cw.vreg['forms'].select('edition', self._cw, entity=trinfo,
-                                                mainform=False)
-        subform.field_by_name('wf_info_for', 'subject').value = entity.eid
-        trfield = subform.field_by_name('by_transition', 'subject')
-        trfield.widget = fwdgs.HiddenInput()
-        trfield.value = transition.eid
-        form.add_subform(subform)
-        return form
-
-
-class WFHistoryView(EntityView):
-    __regid__ = 'wfhistory'
-    __select__ = relation_possible('wf_info_for', role='object') & \
-                 score_entity(lambda x: x.cw_adapt_to('IWorkflowable').workflow_history)
-
-    title = _('Workflow history')
-
-    def cell_call(self, row, col, view=None, title=title):
-        _ = self._cw._
-        eid = self.cw_rset[row][col]
-        sel = 'Any FS,TS,C,D'
-        rql = ' ORDERBY D DESC WHERE WF wf_info_for X,'\
-              'WF from_state FS, WF to_state TS, WF comment C,'\
-              'WF creation_date D'
-        if self._cw.vreg.schema.eschema('CWUser').has_perm(self._cw, 'read'):
-            sel += ',U,WF'
-            rql += ', WF owned_by U?'
-            headers = (_('from_state'), _('to_state'), _('comment'), _('date'),
-                       _('CWUser'))
-        else:
-            sel += ',WF'
-            headers = (_('from_state'), _('to_state'), _('comment'), _('date'))
-        rql = '%s %s, X eid %%(x)s' % (sel, rql)
-        try:
-            rset = self._cw.execute(rql, {'x': eid})
-        except Unauthorized:
-            return
-        if rset:
-            if title:
-                self.w(u'<h2>%s</h2>\n' % _(title))
-            self.wview('table', rset, headers=headers,
-                       cellvids={2: 'editable-final'})
-
-
-class WFHistoryVComponent(component.EntityCtxComponent):
-    """display the workflow history for entities supporting it"""
-    __regid__ = 'wfhistory'
-    __select__ = component.EntityCtxComponent.__select__ & WFHistoryView.__select__
-    context = 'navcontentbottom'
-    title = _('Workflow history')
-
-    def render_body(self, w):
-        self.entity.view('wfhistory', w=w, title=None)
-
-
-class InContextWithStateView(EntityView):
-    """display incontext view for an entity as well as its current state"""
-    __regid__ = 'incontext-state'
-    __select__ = adaptable('IWorkflowable')
-    def entity_call(self, entity):
-        iwf = entity.cw_adapt_to('IWorkflowable')
-        self.w(u'%s [%s]' % (entity.view('incontext'), iwf.printable_state))
-
-
-# workflow actions #############################################################
-
-class WorkflowActions(action.Action):
-    """fill 'workflow' sub-menu of the actions box"""
-    __regid__ = 'workflow'
-    __select__ = (action.Action.__select__ & one_line_rset() &
-                  relation_possible('in_state'))
-
-    submenu = _('workflow')
-    order = 10
-
-    def fill_menu(self, box, menu):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        menu.label = u'%s: %s' % (self._cw._('state'),
-                                  entity.cw_adapt_to('IWorkflowable').printable_state)
-        menu.append_anyway = True
-        super(WorkflowActions, self).fill_menu(box, menu)
-
-    def actual_actions(self):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        iworkflowable = entity.cw_adapt_to('IWorkflowable')
-        hastr = False
-        for tr in iworkflowable.possible_transitions():
-            url = entity.absolute_url(vid='statuschange', treid=tr.eid)
-            yield self.build_action(self._cw._(tr.name), url)
-            hastr = True
-        # don't propose to see wf if user can't pass any transition
-        if hastr:
-            wfurl = iworkflowable.current_workflow.absolute_url()
-            yield self.build_action(self._cw._('view workflow'), wfurl)
-        if iworkflowable.workflow_history:
-            wfurl = entity.absolute_url(vid='wfhistory')
-            yield self.build_action(self._cw._('view history'), wfurl)
-
-
-# workflow entity types views ##################################################
-
-_pvs = uicfg.primaryview_section
-_pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
-_pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
-_pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
-_pvs.tag_object_of(('*', 'default_workflow', 'Workflow'), 'hidden')
-
-_abaa = uicfg.actionbox_appearsin_addmenu
-_abaa.tag_subject_of(('BaseTransition', 'condition', 'RQLExpression'), False)
-_abaa.tag_subject_of(('State', 'allowed_transition', 'BaseTransition'), False)
-_abaa.tag_object_of(('SubWorkflowExitPoint', 'destination_state', 'State'),
-                    False)
-_abaa.tag_object_of(('State', 'state_of', 'Workflow'), True)
-_abaa.tag_object_of(('BaseTransition', 'transition_of', 'Workflow'), False)
-_abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
-_abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
-
-class WorkflowPrimaryView(TabbedPrimaryView):
-    __select__ = is_instance('Workflow')
-    tabs = [  _('wf_tab_info'), _('wfgraph'),]
-    default_tab = 'wf_tab_info'
-
-
-class StateInContextView(EntityView):
-    """convenience trick, State's incontext view should not be clickable"""
-    __regid__ = 'incontext'
-    __select__ = is_instance('State')
-
-    def cell_call(self, row, col):
-        self.w(xml_escape(self._cw.view('textincontext', self.cw_rset,
-                                        row=row, col=col)))
-
-class WorkflowTabTextView(PrimaryTab):
-    __regid__ = 'wf_tab_info'
-    __select__ = PrimaryTab.__select__ & one_line_rset() & is_instance('Workflow')
-
-    def render_entity_attributes(self, entity):
-        _ = self._cw._
-        self.w(u'<div>%s</div>' % (entity.printable_value('description')))
-        self.w(u'<span>%s%s</span>' % (_("workflow_of").capitalize(), _(" :")))
-        html = []
-        for e in  entity.workflow_of:
-            view = e.view('outofcontext')
-            if entity.eid == e.default_workflow[0].eid:
-                view += u' <span>[%s]</span>' % _('default_workflow')
-            html.append(view)
-        self.w(', '.join(v for v in html))
-        self.w(u'<h2>%s</h2>' % _("Transition_plural"))
-        rset = self._cw.execute(
-            'Any T,T,DS,T,TT ORDERBY TN WHERE T transition_of WF, WF eid %(x)s,'
-            'T type TT, T name TN, T destination_state DS?', {'x': entity.eid})
-        self.wview('table', rset, 'null',
-                   cellvids={ 1: 'trfromstates', 2: 'outofcontext', 3:'trsecurity',},
-                   headers = (_('Transition'),  _('from_state'),
-                              _('to_state'), _('permissions'), _('type') ),
-                   )
-
-
-class TransitionSecurityTextView(EntityView):
-    __regid__ = 'trsecurity'
-    __select__ = is_instance('Transition')
-
-    def cell_call(self, row, col):
-        _ = self._cw._
-        entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
-        if entity.require_group:
-            self.w(u'<div>%s%s %s</div>' %
-                   (_('groups'), _(" :"),
-                    u', '.join((g.view('incontext') for g
-                               in entity.require_group))))
-        if entity.condition:
-            self.w(u'<div>%s%s %s</div>' %
-                   ( _('conditions'), _(" :"),
-                     u'<br/>'.join((e.dc_title() for e
-                                in entity.condition))))
-
-class TransitionAllowedTextView(EntityView):
-    __regid__ = 'trfromstates'
-    __select__ = is_instance('Transition')
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
-        self.w(u', '.join((e.view('outofcontext') for e
-                           in entity.reverse_allowed_transition)))
-
-
-# workflow entity types edition ################################################
-
-def _wf_items_for_relation(req, wfeid, wfrelation, field):
-    wf = req.entity_from_eid(wfeid)
-    rschema = req.vreg.schema[field.name]
-    param = 'toeid' if field.role == 'subject' else 'fromeid'
-    return sorted((e.view('combobox'), unicode(e.eid))
-                  for e in getattr(wf, 'reverse_%s' % wfrelation)
-                  if rschema.has_perm(req, 'add', **{param: e.eid}))
-
-# TrInfo
-_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden')
-_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden')
-_afs.tag_attribute(('TrInfo', 'tr_count'), 'main', 'hidden')
-
-# BaseTransition
-# XXX * allowed_transition BaseTransition
-# XXX BaseTransition destination_state *
-
-def transition_states_vocabulary(form, field):
-    entity = form.edited_entity
-    if entity.has_eid():
-        wfeid = entity.transition_of[0].eid
-    else:
-        eids = form.linked_to.get(('transition_of', 'subject'))
-        if not eids:
-            return []
-        wfeid = eids[0]
-    return _wf_items_for_relation(form._cw, wfeid, 'state_of', field)
-
-_afs.tag_subject_of(('*', 'destination_state', '*'), 'main', 'attributes')
-_affk.tag_subject_of(('*', 'destination_state', '*'),
-                     {'choices': transition_states_vocabulary})
-_afs.tag_object_of(('*', 'allowed_transition', '*'), 'main', 'attributes')
-_affk.tag_object_of(('*', 'allowed_transition', '*'),
-                     {'choices': transition_states_vocabulary})
-
-# State
-
-def state_transitions_vocabulary(form, field):
-    entity = form.edited_entity
-    if entity.has_eid():
-        wfeid = entity.state_of[0].eid
-    else :
-        eids = form.linked_to.get(('state_of', 'subject'))
-        if not eids:
-            return []
-        wfeid = eids[0]
-    return _wf_items_for_relation(form._cw, wfeid, 'transition_of', field)
-
-_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'main', 'attributes')
-_affk.tag_subject_of(('State', 'allowed_transition', '*'),
-                     {'choices': state_transitions_vocabulary})
-
-
-# adaptaters ###################################################################
-
-class WorkflowIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
-    __select__ = is_instance('Workflow')
-    # XXX what if workflow of multiple types?
-    def parent_entity(self):
-        return self.entity.workflow_of and self.entity.workflow_of[0] or None
-
-class WorkflowItemIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
-    __select__ = is_instance('BaseTransition', 'State')
-    def parent_entity(self):
-        return self.entity.workflow
-
-class TransitionItemIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
-    __select__ = is_instance('SubWorkflowExitPoint')
-    def parent_entity(self):
-        return self.entity.reverse_subworkflow_exit[0]
-
-class TrInfoIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
-    __select__ = is_instance('TrInfo')
-    def parent_entity(self):
-        return self.entity.for_entity
-
-
-# workflow images ##############################################################
-
-class WorkflowDotPropsHandler(DotPropsHandler):
-
-    def node_properties(self, stateortransition):
-        """return default DOT drawing options for a state or transition"""
-        props = super(WorkflowDotPropsHandler, self).node_properties(stateortransition)
-        if hasattr(stateortransition, 'state_of'):
-            props['shape'] = 'box'
-            props['style'] = 'filled'
-            if stateortransition.reverse_initial_state:
-                props['fillcolor'] = '#88CC88'
-        else:
-            props['shape'] = 'ellipse'
-        return props
-
-
-class WorkflowVisitor(object):
-    def __init__(self, entity):
-        self.entity = entity
-
-    def nodes(self):
-        for state in self.entity.reverse_state_of:
-            state.complete()
-            yield state.eid, state
-        for transition in self.entity.reverse_transition_of:
-            transition.complete()
-            yield transition.eid, transition
-
-    def edges(self):
-        for transition in self.entity.reverse_transition_of:
-            for incomingstate in transition.reverse_allowed_transition:
-                yield incomingstate.eid, transition.eid, transition
-            for outgoingstate in transition.potential_destinations():
-                yield transition.eid, outgoingstate.eid, transition
-
-class WorkflowGraphView(DotGraphView):
-    __regid__ = 'wfgraph'
-    __select__ = EntityView.__select__ & one_line_rset() & is_instance('Workflow')
-
-    def build_visitor(self, entity):
-        return WorkflowVisitor(entity)
-
-    def build_dotpropshandler(self):
-        return WorkflowDotPropsHandler(self._cw)
-
-
-@add_metaclass(class_deprecated)
-class TmpPngView(TmpFileViewMixin, EntityView):
-    __deprecation_warning__ = '[3.18] %(cls)s is deprecated'
-    __regid__ = 'tmppng'
-    __select__ = match_form_params('tmpfile')
-    content_type = 'image/png'
-    binary = True
-
-    def cell_call(self, row=0, col=0):
-        key = self._cw.form['tmpfile']
-        if key not in self._cw.session.data:
-            # the temp file is gone and there's nothing
-            # we can do about it
-            # we should probably write it to some well
-            # behaved place and serve it
-            return
-        tmpfile = self._cw.session.data.pop(key)
-        self.w(open(tmpfile, 'rb').read())
-        os.unlink(tmpfile)