Commit 8423ba1a authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

support for automatic transition

--HG--
branch : stable
parent efeb16ff93f3
......@@ -343,6 +343,47 @@ class CustomWorkflowTC(EnvBasedTC):
('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):
......
......@@ -223,11 +223,14 @@ class BaseTransition(AnyEntity):
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 @@ class WorkflowableMixIn(object):
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 @@ class WorkflowableMixIn(object):
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):
......
sync_schema_props_perms('state_of')
sync_schema_props_perms('transition_of')
add_attribute('BaseTransition', 'type')
......@@ -68,6 +68,7 @@ class BaseTransition(EntityType):
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',
......
......@@ -434,6 +434,18 @@ def _change_state(session, x, oldstate, newstate):
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 @@ def before_add_trinfo(session, entity):
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"""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment