Skip to content

Commit

Permalink
Merged r108441 from multiple-workflows branch
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentfretin committed Jan 27, 2010
1 parent 12ff7e4 commit cbb4674
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 30 deletions.
6 changes: 5 additions & 1 deletion CHANGES.txt
Expand Up @@ -4,7 +4,11 @@ hurry.workflow changes
0.11 (unreleased)
=================

- Nothing changed yet.
* Do IAnnotations(self.context) only once in WorkflowState.

* An IWorkflowVersions implementation is now optional.

* Added multiple workflows support.


0.10 (2009-11-19)
Expand Down
60 changes: 31 additions & 29 deletions src/hurry/workflow/workflow.py
Expand Up @@ -92,50 +92,54 @@ def getTransitionById(self, transition_id):

class WorkflowState(object):
implements(IWorkflowState)
state_key = "hurry.workflow.state"
id_key = "hurry.workflow.id"

def __init__(self, context):
# XXX okay, I'm tired of it not being able to set annotations, so
# we'll do this. Ugh.
from zope.security.proxy import removeSecurityProxy
self.context = removeSecurityProxy(context)
self._annotations = IAnnotations(self.context)

def initialize(self):
wf_versions = component.getUtility(IWorkflowVersions)
self.setId(wf_versions.createVersionId())
wf_versions = component.queryUtility(IWorkflowVersions)
if wf_versions is not None:
self.setId(wf_versions.createVersionId())

def setState(self, state):
if state != self.getState():
IAnnotations(self.context)[
'hurry.workflow.state'] = state
self._annotations[self.state_key] = state

def setId(self, id):
# XXX catalog should be informed (or should it?)
IAnnotations(self.context)['hurry.workflow.id'] = id
self._annotations[self.id_key] = id

def getState(self):
try:
return IAnnotations(self.context)['hurry.workflow.state']
except KeyError:
return None
return self._annotations.get(self.state_key, None)

def getId(self):
try:
return IAnnotations(self.context)['hurry.workflow.id']
except KeyError:
return None
return self._annotations.get(self.id_key, None)

class WorkflowInfo(object):
implements(IWorkflowInfo)
name = u''

def __init__(self, context):
self.context = context

self.wf = component.getUtility(IWorkflow, name=self.name)

def info(self, obj):
return component.getAdapter(obj, IWorkflowInfo, name=self.name)

def state(self, obj):
return component.getAdapter(obj, IWorkflowState, name=self.name)

def fireTransition(self, transition_id, comment=None, side_effect=None,
check_security=True):
state = IWorkflowState(self.context)
wf = component.getUtility(IWorkflow)
state = self.state(self.context)
# this raises InvalidTransitionError if id is invalid for current state
transition = wf.getTransition(state.getState(), transition_id)
transition = self.wf.getTransition(state.getState(), transition_id)
# check whether we may execute this workflow transition
try:
interaction = getInteraction()
Expand All @@ -158,10 +162,10 @@ def fireTransition(self, transition_id, comment=None, side_effect=None,
result = transition.action(self, self.context)
if result is not None:
if transition.source is None:
IWorkflowState(result).initialize()
self.state(result).initialize()
# stamp it with version
state = IWorkflowState(result)
state.setId(IWorkflowState(self.context).getId())
state = self.state(result)
state.setId(self.state(self.context).getId())
# execute any side effect:
if side_effect is not None:
side_effect(result)
Expand All @@ -171,7 +175,7 @@ def fireTransition(self, transition_id, comment=None, side_effect=None,
transition, comment)
else:
if transition.source is None:
IWorkflowState(self.context).initialize()
self.state(self.context).initialize()
# execute any side effect
if side_effect is not None:
side_effect(self.context)
Expand Down Expand Up @@ -200,12 +204,12 @@ def fireTransitionToward(self, state, comment=None, side_effect=None,
comment, side_effect, check_security)

def fireTransitionForVersions(self, state, transition_id):
id = IWorkflowState(self.context).getId()
id = self.state(self.context).getId()
wf_versions = component.getUtility(IWorkflowVersions)
for version in wf_versions.getVersions(state, id):
if version is self.context:
continue
IWorkflowInfo(version).fireTransition(transition_id)
self.info(version).fireTransition(transition_id)

def fireAutomatic(self):
for transition_id in self.getAutomaticTransitionIds():
Expand All @@ -222,7 +226,7 @@ def fireAutomatic(self):

def hasVersion(self, state):
wf_versions = component.getUtility(IWorkflowVersions)
id = IWorkflowState(self.context).getId()
id = self.state(self.context).getId()
return wf_versions.hasVersion(state, id)

def getManualTransitionIds(self):
Expand All @@ -245,10 +249,9 @@ def getFireableTransitionIds(self):
return self.getManualTransitionIds() + self.getSystemTransitionIds()

def getFireableTransitionIdsToward(self, state):
wf = component.getUtility(IWorkflow)
result = []
for transition_id in self.getFireableTransitionIds():
transition = wf.getTransitionById(transition_id)
transition = self.wf.getTransitionById(transition_id)
if transition.destination == state:
result.append(transition_id)
return result
Expand All @@ -263,9 +266,8 @@ def hasAutomaticTransitions(self):

def _getTransitions(self, trigger):
# retrieve all possible transitions from workflow utility
wf = component.getUtility(IWorkflow)
transitions = wf.getTransitions(
IWorkflowState(self.context).getState())
transitions = self.wf.getTransitions(
self.state(self.context).getState())
# now filter these transitions to retrieve all possible
# transitions in this context, and return their ids
return [transition for transition in transitions if
Expand Down
79 changes: 79 additions & 0 deletions src/hurry/workflow/workflow.txt
Expand Up @@ -189,6 +189,85 @@ And we have another event that was fired::
'b'
>>> events[-1].destination is None
True


Multiple workflows
------------------

We have previously registered a workflow as a unnamed utility.
You can also register a workflow as a named utility to provide
several workflows for a site.

Let's create a, invoice document::

>>> class IInvoiceDocument(IDocument):
... title = Attribute('Title')

>>> class InvoiceDocument(object):
... implements(IInvoiceDocument)
... def __init__(self, title, amount):
... self.title = title
... self.amount = amount

We define our workflow::

>>> invoice_init = workflow.Transition(
... transition_id='init_invoice',
... title='Invoice Received',
... source=None,
... destination='received')
>>>
>>> invoice_paid = workflow.Transition(
... transition_id='invoice_paid',
... title='Invoice Paid',
... source='received',
... destination='paid')

>>> invoice_wf = workflow.Workflow( [ invoice_init, invoice_paid ] )

We register a new workflow utility, WorkflowState and WorkflowInfo adapters, all
named "invoice"::

>>> from hurry.workflow import workflow
>>> from zope.annotation import interfaces as annotation_interfaces
>>> component.provideUtility(invoice_wf, interfaces.IWorkflow, name="invoice")
>>> class InvoiceWorkflowInfo(workflow.WorkflowInfo):
... name="invoice"
>>> component.provideAdapter(
... InvoiceWorkflowInfo,
... (annotation_interfaces.IAnnotatable,),
... interfaces.IWorkflowInfo,
... name="invoice")
>>> class InvoiceWorkflowState(workflow.WorkflowState):
... state_key = "invoice.state"
... id_key = "invoice.id"
>>> component.provideAdapter(
... InvoiceWorkflowState,
... (annotation_interfaces.IAnnotatable,),
... interfaces.IWorkflowState,
... name="invoice")

Now we can utilize the workflow::

>>> invoice = InvoiceDocument('abc', 22)

>>> info = component.getAdapter(invoice, interfaces.IWorkflowInfo, name="invoice")
>>> info.fireTransition('init_invoice')
>>> state = component.getAdapter(invoice, interfaces.IWorkflowState, name="invoice")
>>> state.getState()
'received'
>>> info.fireTransition('invoice_paid')
>>> state.getState()
'paid'

Of course, this document always have the default unnamed workflow::

>>> info = interfaces.IWorkflowInfo(invoice)
>>> info.fireTransition('to_a')
>>> state = interfaces.IWorkflowState(invoice)
>>> state.getState()
'a'


Multi-version workflow
----------------------
Expand Down

0 comments on commit cbb4674

Please sign in to comment.