[mq]: worklfow view
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 13 Apr 2010 13:21:10 +0200
changeset 5224 34e669b6fd95
parent 5223 6abd6e3599f4
child 5232 78c1a531f7b3
[mq]: worklfow view
entities/schemaobjs.py
entities/wfobjs.py
i18n/en.po
i18n/es.po
i18n/fr.po
web/data/cubicweb.css
web/views/cwuser.py
web/views/schema.py
web/views/workflow.py
--- a/entities/schemaobjs.py	Tue Apr 13 12:19:24 2010 +0200
+++ b/entities/schemaobjs.py	Tue Apr 13 13:21:10 2010 +0200
@@ -162,6 +162,9 @@
     fetch_attrs, fetch_order = fetch_config(['exprtype', 'mainvars', 'expression'])
 
     def dc_title(self):
+        return self.expression or u''
+
+    def dc_long_title(self):
         return '%s(%s)' % (self.exprtype, self.expression or u'')
 
     @property
--- a/entities/wfobjs.py	Tue Apr 13 12:19:24 2010 +0200
+++ b/entities/wfobjs.py	Tue Apr 13 13:21:10 2010 +0200
@@ -174,7 +174,7 @@
     fired by the logged user
     """
     __regid__ = 'BaseTransition'
-    fetch_attrs, fetch_order = fetch_config(['name'])
+    fetch_attrs, fetch_order = fetch_config(['name', 'type'])
 
     def __init__(self, *args, **kwargs):
         if self.__regid__ == 'BaseTransition':
--- a/i18n/en.po	Tue Apr 13 12:19:24 2010 +0200
+++ b/i18n/en.po	Tue Apr 13 13:21:10 2010 +0200
@@ -30,6 +30,9 @@
 msgid "  from state %(fromstate)s to state %(tostate)s\n"
 msgstr ""
 
+msgid " :"
+msgstr ""
+
 #, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr ""
@@ -603,6 +606,9 @@
 msgid "The view %s could not be found"
 msgstr ""
 
+msgid "There is no default workflow"
+msgstr ""
+
 msgid "This BaseTransition"
 msgstr "This abstract transition"
 
@@ -1514,9 +1520,6 @@
 msgid "condition"
 msgstr "condition"
 
-msgid "condition:"
-msgstr "condtion:"
-
 msgctxt "RQLExpression"
 msgid "condition_object"
 msgstr "condition of"
@@ -1524,6 +1527,9 @@
 msgid "condition_object"
 msgstr "condition of"
 
+msgid "conditions"
+msgstr ""
+
 msgid "config mode"
 msgstr ""
 
@@ -2385,10 +2391,6 @@
 msgid "granted to groups"
 msgstr ""
 
-#, python-format
-msgid "graphical representation of %s"
-msgstr ""
-
 msgid "graphical representation of the instance'schema"
 msgstr ""
 
@@ -2412,9 +2414,6 @@
 msgid "groups to which the permission is granted"
 msgstr ""
 
-msgid "groups:"
-msgstr ""
-
 msgid "guests"
 msgstr ""
 
@@ -2790,6 +2789,9 @@
 msgid "more actions"
 msgstr ""
 
+msgid "more info about this workflow"
+msgstr ""
+
 msgid "multiple edit"
 msgstr ""
 
@@ -3013,6 +3015,9 @@
 msgid "permission"
 msgstr ""
 
+msgid "permissions"
+msgstr ""
+
 msgid "permissions for entities"
 msgstr ""
 
@@ -3931,6 +3936,12 @@
 msgid "wf_info_for_object"
 msgstr "workflow history"
 
+msgid "wf_tab_info"
+msgstr ""
+
+msgid "wfgraph"
+msgstr ""
+
 msgid ""
 "when multiple addresses are equivalent (such as python-projects@logilab.org "
 "and python-projects@lists.logilab.org), set this to indicate which is the "
--- a/i18n/es.po	Tue Apr 13 12:19:24 2010 +0200
+++ b/i18n/es.po	Tue Apr 13 13:21:10 2010 +0200
@@ -35,6 +35,9 @@
 msgid "  from state %(fromstate)s to state %(tostate)s\n"
 msgstr "  del estado %(fromstate)s hacia el estado %(tostate)s\n"
 
+msgid " :"
+msgstr ""
+
 #, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr ""
@@ -611,6 +614,9 @@
 msgid "The view %s could not be found"
 msgstr "La vista %s no ha podido ser encontrada"
 
+msgid "There is no default workflow"
+msgstr ""
+
 msgid "This BaseTransition"
 msgstr ""
 
@@ -1545,9 +1551,6 @@
 msgid "condition"
 msgstr ""
 
-msgid "condition:"
-msgstr "condición:"
-
 msgctxt "RQLExpression"
 msgid "condition_object"
 msgstr ""
@@ -1555,6 +1558,9 @@
 msgid "condition_object"
 msgstr "condición de"
 
+msgid "conditions"
+msgstr ""
+
 msgid "config mode"
 msgstr ""
 
@@ -2435,10 +2441,6 @@
 msgid "granted to groups"
 msgstr "Otorgado a los grupos"
 
-#, python-format
-msgid "graphical representation of %s"
-msgstr ""
-
 msgid "graphical representation of the instance'schema"
 msgstr ""
 
@@ -2462,9 +2464,6 @@
 msgid "groups to which the permission is granted"
 msgstr "Grupos quienes tienen otorgada esta autorización"
 
-msgid "groups:"
-msgstr "Grupos :"
-
 msgid "guests"
 msgstr "Invitados"
 
@@ -2858,6 +2857,9 @@
 msgid "more actions"
 msgstr "mas acciones"
 
+msgid "more info about this workflow"
+msgstr ""
+
 msgid "multiple edit"
 msgstr "Edicion multiple"
 
@@ -3086,6 +3088,9 @@
 msgid "permission"
 msgstr "Permiso"
 
+msgid "permissions"
+msgstr ""
+
 msgid "permissions for entities"
 msgstr "autorizaciónes para entidades"
 
@@ -4018,6 +4023,12 @@
 msgid "wf_info_for_object"
 msgstr "historial de transiciones"
 
+msgid "wf_tab_info"
+msgstr ""
+
+msgid "wfgraph"
+msgstr ""
+
 msgid ""
 "when multiple addresses are equivalent (such as python-projects@logilab.org "
 "and python-projects@lists.logilab.org), set this to indicate which is the "
@@ -4219,6 +4230,9 @@
 #~ msgid "comment:"
 #~ msgstr "Comentario:"
 
+#~ msgid "condition:"
+#~ msgstr "condición:"
+
 #~ msgid "copy edition"
 #~ msgstr "Edición de una copia"
 
@@ -4314,6 +4328,9 @@
 #~ msgid "groups allowed to update entities of this type"
 #~ msgstr "Grupos autorizados a actualizar entidades de este tipo"
 
+#~ msgid "groups:"
+#~ msgstr "Grupos :"
+
 #~ msgid "home"
 #~ msgstr "Inicio"
 
--- a/i18n/fr.po	Tue Apr 13 12:19:24 2010 +0200
+++ b/i18n/fr.po	Tue Apr 13 13:21:10 2010 +0200
@@ -35,6 +35,9 @@
 msgid "  from state %(fromstate)s to state %(tostate)s\n"
 msgstr "  de l'état %(fromstate)s vers l'état %(tostate)s\n"
 
+msgid " :"
+msgstr ""
+
 #, python-format
 msgid "%(attr)s set to %(newvalue)s"
 msgstr "%(attr)s modifié à %(newvalue)s"
@@ -622,6 +625,9 @@
 msgid "The view %s could not be found"
 msgstr "La vue %s est introuvable"
 
+msgid "There is no default workflow"
+msgstr "Ce type d'entité n'a pas de workflow par défault"
+
 msgid "This BaseTransition"
 msgstr "Cette transition abstraite"
 
@@ -1565,9 +1571,6 @@
 msgid "condition"
 msgstr "condition"
 
-msgid "condition:"
-msgstr "condition :"
-
 msgctxt "RQLExpression"
 msgid "condition_object"
 msgstr "condition de"
@@ -1575,6 +1578,9 @@
 msgid "condition_object"
 msgstr "condition de"
 
+msgid "conditions"
+msgstr "conditions"
+
 msgid "config mode"
 msgstr "mode de configuration"
 
@@ -2374,7 +2380,7 @@
 msgstr "suivez ce lien pour plus d'information sur ce %s"
 
 msgid "follow this link if javascript is deactivated"
-msgstr ""
+msgstr "suivez ce lien si javascript est désactivé"
 
 msgid "for_user"
 msgstr "pour l'utilisateur"
@@ -2471,10 +2477,6 @@
 msgid "granted to groups"
 msgstr "accordée aux groupes"
 
-#, python-format
-msgid "graphical representation of %s"
-msgstr "représentation graphique de %s"
-
 msgid "graphical representation of the instance'schema"
 msgstr "représentation graphique du schéma de l'instance"
 
@@ -2499,9 +2501,6 @@
 msgid "groups to which the permission is granted"
 msgstr "groupes auquels cette permission est donnée"
 
-msgid "groups:"
-msgstr "groupes :"
-
 msgid "guests"
 msgstr "invités"
 
@@ -2896,6 +2895,9 @@
 msgid "more actions"
 msgstr "plus d'actions"
 
+msgid "more info about this workflow"
+msgstr "plus d'information sur ce workflow"
+
 msgid "multiple edit"
 msgstr "édition multiple"
 
@@ -3122,6 +3124,9 @@
 msgid "permission"
 msgstr "permission"
 
+msgid "permissions"
+msgstr "permissions"
+
 msgid "permissions for entities"
 msgstr "permissions pour les entités"
 
@@ -4062,6 +4067,12 @@
 msgid "wf_info_for_object"
 msgstr "historique des transitions"
 
+msgid "wf_tab_info"
+msgstr "description"
+
+msgid "wfgraph"
+msgstr "image du workflow"
+
 msgid ""
 "when multiple addresses are equivalent (such as python-projects@logilab.org "
 "and python-projects@lists.logilab.org), set this to indicate which is the "
--- a/web/data/cubicweb.css	Tue Apr 13 12:19:24 2010 +0200
+++ b/web/data/cubicweb.css	Tue Apr 13 13:21:10 2010 +0200
@@ -63,7 +63,7 @@
   text-decoration: underline;
 }
 
-a img {
+a img, img {
   border: none;
   text-align: center;
 }
--- a/web/views/cwuser.py	Tue Apr 13 12:19:24 2010 +0200
+++ b/web/views/cwuser.py	Tue Apr 13 13:21:10 2010 +0200
@@ -71,3 +71,12 @@
         if emailaddr:
             self.w(u'<foaf:mbox>%s</foaf:mbox>\n' % xml_escape(emailaddr))
         self.w(u'</foaf:Person>\n')
+
+class CWGroupInContextView(EntityView):
+    __regid__ = 'incontext'
+    __select__ = implements('CWGroup')
+    def cell_call(self, row, col):
+        self._cw.add_css('cubicweb.acl.css')
+        entity = self.cw_rset.complete_entity(row, col)
+        self.w(u'<a href="%s" class="%s">%s</a>' % (
+            entity.absolute_url(), entity.name, entity.printable_value('name')))
--- a/web/views/schema.py	Tue Apr 13 12:19:24 2010 +0200
+++ b/web/views/schema.py	Tue Apr 13 13:21:10 2010 +0200
@@ -313,18 +313,25 @@
         entity = self.cw_rset.get_entity(row, col)
         if entity.default_workflow:
             wf = entity.default_workflow[0]
-            self.w(u'<h1>%s (%s)</h1>' % (wf.name, self._cw._('default')))
-            self.wf_image(wf)
+            if len(entity.reverse_workflow_of) > 1:
+                self.w(u'<h1>%s (%s)</h1>'
+                       % (wf.name, self._cw._('default_workflow')))
+            self.display_workflow(wf)
+            defaultwfeid = wf.eid
+        else:
+            self.w(u'<div class="error">%s</div>'
+                   % self._cw._('There is no default workflow'))
+            defaultwfeid = None
         for altwf in entity.reverse_workflow_of:
-            if altwf.eid == wf.eid:
+            if altwf.eid == defaultwfeid:
                 continue
             self.w(u'<h1>%s</h1>' % altwf.name)
-            self.wf_image(altwf)
+            self.display_workflow(altwf)
 
-    def wf_image(self, wf):
-        self.w(u'<img src="%s" alt="%s"/>' % (
-            xml_escape(wf.absolute_url(vid='wfgraph')),
-            xml_escape(self._cw._('graphical representation of %s') % wf.name)))
+    def display_workflow(self, wf):
+        self.w(wf.view('wfgraph'))
+        self.w('<a href="%s">%s</a>' % (
+            wf.absolute_url(), self._cw._('more info about this workflow')))
 
 
 # CWRType ######################################################################
--- a/web/views/workflow.py	Tue Apr 13 12:19:24 2010 +0200
+++ b/web/views/workflow.py	Tue Apr 13 13:21:10 2010 +0200
@@ -11,6 +11,9 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
+import tempfile
+import os
+
 from logilab.mtconverter import xml_escape
 from logilab.common.graph import escape, GraphGenerator, DotBackend
 
@@ -24,6 +27,7 @@
 from cubicweb.web import uicfg, stdmsgs, action, component, form, action
 from cubicweb.web import formfields as ff, formwidgets as fwdgs
 from cubicweb.web.views import TmpFileViewMixin, forms, primary, autoform
+from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
 
 _pvs = uicfg.primaryview_section
 _pvs.tag_subject_of(('Workflow', 'initial_state', '*'), 'hidden')
@@ -187,6 +191,7 @@
 _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)
@@ -198,14 +203,10 @@
 _abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
 _abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
 
-class WorkflowPrimaryView(primary.PrimaryView):
+class WorkflowPrimaryView(TabbedPrimaryView):
     __select__ = implements('Workflow')
-
-    def render_entity_attributes(self, entity):
-        self.w(entity.view('reledit', rtype='description'))
-        self.w(u'<img src="%s" alt="%s"/>' % (
-            xml_escape(entity.absolute_url(vid='wfgraph')),
-            xml_escape(self._cw._('graphical workflow for %s') % entity.name)))
+    tabs = [  _('wf_tab_info'), _('wfgraph'),]
+    default_tab = 'wf_tab_info'
 
 
 class CellView(view.EntityView):
@@ -225,6 +226,59 @@
         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() & implements('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('editable-table', rset, 'null',
+                   cellvids={ 1: 'trfromstates', 2: 'outofcontext', 3:'trsecurity',},
+                   headers = (_('Transition'),  _('from_state'),
+                              _('to_state'), _('permissions'), _('type') ),
+                   )
+
+
+class TransitionSecurityTextView(view.EntityView):
+    __regid__ = 'trsecurity'
+    __select__ = implements('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(view.EntityView):
+    __regid__ = 'trfromstates'
+    __select__ = implements('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 ################################################
 
@@ -284,24 +338,18 @@
     def node_properties(self, stateortransition):
         """return default DOT drawing options for a state or transition"""
         props = {'label': stateortransition.printable_value('name'),
-                 'fontname': 'Courier'}
+                 'fontname': 'Courier', 'fontsize':10,
+                 'href': stateortransition.absolute_url(),
+                 }
         if hasattr(stateortransition, 'state_of'):
             props['shape'] = 'box'
             props['style'] = 'filled'
             if stateortransition.reverse_initial_state:
-                props['color'] = '#88CC88'
+                props['fillcolor'] = '#88CC88'
         else:
             props['shape'] = 'ellipse'
             descr = []
             tr = stateortransition
-            if tr.require_group:
-                descr.append('%s %s'% (
-                    self._('groups:'),
-                    ','.join(g.printable_value('name') for g in tr.require_group)))
-            if tr.condition:
-                descr.append('%s %s'% (
-                    self._('condition:'),
-                    ' | '.join(e.expression for e in tr.condition)))
             if descr:
                 props['label'] += escape('\n'.join(descr))
         return props
@@ -331,17 +379,39 @@
                 yield transition.eid, outgoingstate.eid, transition
 
 
+class WorkflowGraphView(view.EntityView):
+    __regid__ = 'wfgraph'
+    __select__ = EntityView.__select__ & one_line_rset() & implements('Workflow')
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.get_entity(row, col)
+        visitor = WorkflowVisitor(entity)
+        prophdlr = WorkflowDotPropsHandler(self._cw)
+        wfname = 'workflow%s' % str(entity.eid)
+        generator = GraphGenerator(DotBackend(wfname, None,
+                                              ratio='compress', size='30,10'))
+        # map file
+        pmap, mapfile = tempfile.mkstemp(".map", wfname)
+        os.close(pmap)
+        # image file
+        fd, tmpfile = tempfile.mkstemp('.png')
+        os.close(fd)
+        generator.generate(visitor, prophdlr, tmpfile, mapfile)
+        self.w(u'<img src="%s" alt="%s" usemap="#%s" />' % (
+            xml_escape(entity.absolute_url(vid='wfimage', tmpfile=tmpfile)),
+            xml_escape(self._cw._('graphical workflow for %s') % entity.name),
+            wfname))
+        stream = open(mapfile, 'r').read()
+        stream = stream.decode(self._cw.encoding)
+        self.w(stream)
+        os.unlink(mapfile)
+
 class WorkflowImageView(TmpFileViewMixin, view.EntityView):
-    __regid__ = 'wfgraph'
+    __regid__ = 'wfimage'
     __select__ = implements('Workflow')
     content_type = 'image/png'
 
-    def _generate(self, tmpfile):
-        """display schema information for an entity"""
-        entity = self.cw_rset.get_entity(self.cw_row, self.cw_col)
-        visitor = WorkflowVisitor(entity)
-        prophdlr = WorkflowDotPropsHandler(self._cw)
-        generator = GraphGenerator(DotBackend('workflow', 'LR',
-                                              ratio='compress', size='30,12'))
-        return generator.generate(visitor, prophdlr, tmpfile)
-
+    def cell_call(self, row=0, col=0):
+        tmpfile = self._cw.form.get('tmpfile', None)
+        self.w(open(tmpfile, 'rb').read())
+        os.unlink(tmpfile)