--- 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"""