support for automatic transition stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 30 Sep 2009 16:06:52 +0200
branchstable
changeset 3528 77a69de16709
parent 3527 efeb16ff93f3
child 3529 87b5086fd6a7
support for automatic transition
entities/test/unittest_wfobjs.py
entities/wfobjs.py
misc/migration/3.5.3_Any.py
schemas/workflow.py
server/hooks.py
--- a/entities/test/unittest_wfobjs.py	Wed Sep 30 16:06:04 2009 +0200
+++ b/entities/test/unittest_wfobjs.py	Wed Sep 30 16:06:52 2009 +0200
@@ -343,6 +343,47 @@
                            ('asleep', 'activated', None, 'workflow changed to "default user workflow"'),])
 
 
+
+class AutoTransitionTC(EnvBasedTC):
+
+    def setup_database(self):
+        self.wf = add_wf(self, 'CWUser')
+        asleep = self.wf.add_state('asleep', initial=True)
+        dead = self.wf.add_state('dead')
+        self.wf.add_transition('rest', asleep, asleep)
+        self.wf.add_transition('sick', asleep, dead, type=u'auto',
+                               conditions=({'expr': u'U surname "toto"',
+                                            'mainvars': u'U'},))
+
+    def test_auto_transition_fired(self):
+        user = self.create_user('member')
+        self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
+                     {'wf': self.wf.eid, 'x': user.eid})
+        self.commit()
+        user.clear_all_caches()
+        self.assertEquals(user.state, 'asleep')
+        self.assertEquals([t.name for t in user.possible_transitions()],
+                          ['rest'])
+        user.fire_transition('rest')
+        self.commit()
+        user.clear_all_caches()
+        self.assertEquals(user.state, 'asleep')
+        self.assertEquals([t.name for t in user.possible_transitions()],
+                          ['rest'])
+        self.assertEquals(parse_hist(user.workflow_history),
+                          [('asleep', 'asleep', 'rest', None)])
+        self.request().user.set_attributes(surname=u'toto') # fulfill condition
+        self.commit()
+        user.fire_transition('rest')
+        self.commit()
+        user.clear_all_caches()
+        self.assertEquals(user.state, 'dead')
+        self.assertEquals(parse_hist(user.workflow_history),
+                          [('asleep', 'asleep', 'rest', None),
+                           ('asleep', 'asleep', 'rest', None),
+                           ('asleep', 'dead', 'sick', None),])
+
+
 from cubicweb.devtools.apptest import RepositoryBasedTC
 
 class WorkflowHooksTC(RepositoryBasedTC):
--- a/entities/wfobjs.py	Wed Sep 30 16:06:04 2009 +0200
+++ b/entities/wfobjs.py	Wed Sep 30 16:06:52 2009 +0200
@@ -223,11 +223,14 @@
             conditions = (conditions,)
         for expr in conditions:
             if isinstance(expr, str):
-                expr = unicode(expr)
+                kwargs = {'expr': unicode(expr)}
+            elif isinstance(expr, dict):
+                kwargs = expr
+            kwargs['x'] = self.eid
+            kwargs.setdefault('mainvars', u'X')
             self.req.execute('INSERT RQLExpression X: X exprtype "ERQLExpression", '
-                             'X expression %(expr)s, T condition X '
-                             'WHERE T eid %(x)s',
-                             {'x': self.eid, 'expr': expr}, 'x')
+                             'X expression %(expr)s, X mainvars %(mainvars)s, '
+                             'T condition X WHERE T eid %(x)s', kwargs, 'x')
         # XXX clear caches?
 
 
@@ -415,16 +418,17 @@
             self.warning("can't find any workflow for %s", self.id)
         return None
 
-    def possible_transitions(self):
+    def possible_transitions(self, type='normal'):
         """generates transition that MAY be fired for the given entity,
         expected to be in this state
         """
         if self.current_state is None or self.current_workflow is None:
             return
         rset = self.req.execute(
-            'Any T,N WHERE S allowed_transition T, S eid %(x)s, '
-            'T name N, T transition_of WF, WF eid %(wfeid)s',
-            {'x': self.current_state.eid,
+            'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, '
+            'T type TT, T type %(type)s, '
+            'T name TN, T transition_of WF, WF eid %(wfeid)s',
+            {'x': self.current_state.eid, 'type': type,
              'wfeid': self.current_workflow.eid}, 'x')
         for tr in rset.entities():
             if tr.may_be_fired(self.eid):
@@ -446,13 +450,14 @@
             kwargs['S'] = tseid
         return self.req.create_entity('TrInfo', *args, **kwargs)
 
-    def fire_transition(self, trname, comment=None, commentformat=None):
+    def fire_transition(self, tr, comment=None, commentformat=None):
         """change the entity's state by firing transition of the given name in
         entity's workflow
         """
         assert self.current_workflow
-        tr = self.current_workflow.transition_by_name(trname)
-        assert tr is not None, 'not a %s transition: %s' % (self.id, trname)
+        if isinstance(tr, basestring):
+            tr = self.current_workflow.transition_by_name(tr)
+        assert tr is not None, 'not a %s transition: %s' % (self.id, tr)
         return self._add_trinfo(comment, commentformat, tr.eid)
 
     def change_state(self, statename, comment=None, commentformat=None, tr=None):
--- a/misc/migration/3.5.3_Any.py	Wed Sep 30 16:06:04 2009 +0200
+++ b/misc/migration/3.5.3_Any.py	Wed Sep 30 16:06:52 2009 +0200
@@ -1,2 +1,4 @@
 sync_schema_props_perms('state_of')
 sync_schema_props_perms('transition_of')
+
+add_attribute('BaseTransition', 'type')
--- a/schemas/workflow.py	Wed Sep 30 16:06:04 2009 +0200
+++ b/schemas/workflow.py	Wed Sep 30 16:06:52 2009 +0200
@@ -68,6 +68,7 @@
 
     name = String(required=True, indexed=True, internationalizable=True,
                   maxsize=256)
+    type = String(vocabulary=(_('normal'), _('auto')), default='normal')
     description = RichString(fulltextindexed=True,
                          description=_('semantic description of this transition'))
     condition = SubjectRelation('RQLExpression', cardinality='*?', composite='subject',
--- a/server/hooks.py	Wed Sep 30 16:06:04 2009 +0200
+++ b/server/hooks.py	Wed Sep 30 16:06:52 2009 +0200
@@ -434,6 +434,18 @@
     session.add_relation(x, 'in_state', newstate)
 
 
+class FireAutotransitionOp(PreCommitOperation):
+    """try to fire auto transition after state changes"""
+
+    def precommit_event(self):
+        session = self.session
+        entity = self.entity
+        autotrs = list(entity.possible_transitions('auto'))
+        if autotrs:
+            assert len(autotrs) == 1
+            entity.fire_transition(autotrs[0])
+
+
 def before_add_trinfo(session, entity):
     """check the transition is allowed, add missing information. Expect that:
     * wf_info_for inlined relation is set
@@ -513,6 +525,8 @@
     nocheck = session.transaction_data.setdefault('skip-security', set())
     nocheck.add((entity.eid, 'from_state', fromstate.eid))
     nocheck.add((entity.eid, 'to_state', deststateeid))
+    FireAutotransitionOp(session, entity=forentity)
+
 
 def after_add_trinfo(session, entity):
     """change related entity state"""