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') |