web/views/workflow.py
branchstable
changeset 6093 9001a74fcc82
parent 5877 0c7b7b76a84f
child 6103 072f23f6bf83
equal deleted inserted replaced
6091:560df423149a 6093:9001a74fcc82
    22 """
    22 """
    23 
    23 
    24 __docformat__ = "restructuredtext en"
    24 __docformat__ = "restructuredtext en"
    25 _ = unicode
    25 _ = unicode
    26 
    26 
    27 import tempfile
       
    28 import os
    27 import os
    29 
    28 
    30 from logilab.mtconverter import xml_escape
    29 from logilab.mtconverter import xml_escape
    31 from logilab.common.graph import escape, GraphGenerator, DotBackend
    30 from logilab.common.graph import escape
    32 
    31 
    33 from cubicweb import Unauthorized, view
    32 from cubicweb import Unauthorized, view
    34 from cubicweb.selectors import (has_related_entities, one_line_rset,
    33 from cubicweb.selectors import (has_related_entities, one_line_rset,
    35                                 relation_possible, match_form_params,
    34                                 relation_possible, match_form_params,
    36                                 score_entity, is_instance, adaptable)
    35                                 score_entity, is_instance, adaptable)
    37 from cubicweb.utils import make_uid
       
    38 from cubicweb.view import EntityView
    36 from cubicweb.view import EntityView
    39 from cubicweb.schema import display_name
    37 from cubicweb.schema import display_name
    40 from cubicweb.web import uicfg, stdmsgs, action, component, form, action
    38 from cubicweb.web import uicfg, stdmsgs, action, component, form, action
    41 from cubicweb.web import formfields as ff, formwidgets as fwdgs
    39 from cubicweb.web import formfields as ff, formwidgets as fwdgs
    42 from cubicweb.web.views import TmpFileViewMixin
    40 from cubicweb.web.views import TmpFileViewMixin
    43 from cubicweb.web.views import forms, primary, autoform, ibreadcrumbs
    41 from cubicweb.web.views import forms, primary, autoform, ibreadcrumbs
    44 from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
    42 from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
       
    43 from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler
    45 
    44 
    46 _pvs = uicfg.primaryview_section
    45 _pvs = uicfg.primaryview_section
    47 _pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
    46 _pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
    48 _pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
    47 _pvs.tag_object_of(('*', 'state_of', 'Workflow'), 'hidden')
    49 _pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
    48 _pvs.tag_object_of(('*', 'transition_of', 'Workflow'), 'hidden')
   372         return self.entity.for_entity
   371         return self.entity.for_entity
   373 
   372 
   374 
   373 
   375 # workflow images ##############################################################
   374 # workflow images ##############################################################
   376 
   375 
   377 class WorkflowDotPropsHandler(object):
   376 class WorkflowDotPropsHandler(DotPropsHandler):
   378     def __init__(self, req):
       
   379         self._ = req._
       
   380 
   377 
   381     def node_properties(self, stateortransition):
   378     def node_properties(self, stateortransition):
   382         """return default DOT drawing options for a state or transition"""
   379         """return default DOT drawing options for a state or transition"""
   383         props = {'label': stateortransition.printable_value('name'),
   380         props = super(WorkflowDotPropsHandler, self).node_properties(stateortransition)
   384                  'fontname': 'Courier', 'fontsize':10,
       
   385                  'href': stateortransition.absolute_url(),
       
   386                  }
       
   387         if hasattr(stateortransition, 'state_of'):
   381         if hasattr(stateortransition, 'state_of'):
   388             props['shape'] = 'box'
   382             props['shape'] = 'box'
   389             props['style'] = 'filled'
   383             props['style'] = 'filled'
   390             if stateortransition.reverse_initial_state:
   384             if stateortransition.reverse_initial_state:
   391                 props['fillcolor'] = '#88CC88'
   385                 props['fillcolor'] = '#88CC88'
   395             tr = stateortransition
   389             tr = stateortransition
   396             if descr:
   390             if descr:
   397                 props['label'] += escape('\n'.join(descr))
   391                 props['label'] += escape('\n'.join(descr))
   398         return props
   392         return props
   399 
   393 
   400     def edge_properties(self, transition, fromstate, tostate):
       
   401         return {'label': '', 'dir': 'forward',
       
   402                 'color': 'black', 'style': 'filled'}
       
   403 
       
   404 
   394 
   405 class WorkflowVisitor:
   395 class WorkflowVisitor:
   406     def __init__(self, entity):
   396     def __init__(self, entity):
   407         self.entity = entity
   397         self.entity = entity
   408 
   398 
   419             for incomingstate in transition.reverse_allowed_transition:
   409             for incomingstate in transition.reverse_allowed_transition:
   420                 yield incomingstate.eid, transition.eid, transition
   410                 yield incomingstate.eid, transition.eid, transition
   421             for outgoingstate in transition.potential_destinations():
   411             for outgoingstate in transition.potential_destinations():
   422                 yield transition.eid, outgoingstate.eid, transition
   412                 yield transition.eid, outgoingstate.eid, transition
   423 
   413 
   424 
   414 class WorkflowGraphView(DotGraphView):
   425 class WorkflowGraphView(view.EntityView):
       
   426     __regid__ = 'wfgraph'
   415     __regid__ = 'wfgraph'
   427     __select__ = EntityView.__select__ & one_line_rset() & is_instance('Workflow')
   416     __select__ = EntityView.__select__ & one_line_rset() & is_instance('Workflow')
   428 
   417 
   429     def cell_call(self, row, col):
   418     def build_visitor(self, entity):
   430         entity = self.cw_rset.get_entity(row, col)
   419         return WorkflowVisitor(entity)
   431         visitor = WorkflowVisitor(entity)
   420 
   432         prophdlr = WorkflowDotPropsHandler(self._cw)
   421     def build_dotpropshandler(self):
   433         wfname = 'workflow%s' % str(entity.eid)
   422         return WorkflowPropsHandler(self._cw)
   434         generator = GraphGenerator(DotBackend(wfname, None,
       
   435                                               ratio='compress', size='30,10'))
       
   436         # map file
       
   437         pmap, mapfile = tempfile.mkstemp(".map", wfname)
       
   438         os.close(pmap)
       
   439         # image file
       
   440         fd, tmpfile = tempfile.mkstemp('.png')
       
   441         os.close(fd)
       
   442         generator.generate(visitor, prophdlr, tmpfile, mapfile)
       
   443         filekeyid = make_uid()
       
   444         self._cw.session.data[filekeyid] = tmpfile
       
   445         self.w(u'<img src="%s" alt="%s" usemap="#%s" />' % (
       
   446             xml_escape(entity.absolute_url(vid='tmppng', tmpfile=filekeyid)),
       
   447             xml_escape(self._cw._('graphical workflow for %s') % entity.name),
       
   448             wfname))
       
   449         stream = open(mapfile, 'r').read()
       
   450         stream = stream.decode(self._cw.encoding)
       
   451         self.w(stream)
       
   452         os.unlink(mapfile)
       
   453 
   423 
   454 
   424 
   455 class TmpPngView(TmpFileViewMixin, view.EntityView):
   425 class TmpPngView(TmpFileViewMixin, view.EntityView):
   456     __regid__ = 'tmppng'
   426     __regid__ = 'tmppng'
   457     __select__ = match_form_params('tmpfile')
   427     __select__ = match_form_params('tmpfile')