Workflows¶
A workflow is a collection of transitions that transition
between states. Specifically, substanced.workflow
implements
event-driven finite-state machine workflows.
Workflows are used to ease following tasks when content goes through the lifecycle:
- updating security (adding/removing permissions)
- sending emails
- …
States and transitions together with metadata are stored on the
Workflow
. Workflows are stored in
config.registry.workflows
. The only thing that content has from the
workflow machinery is content.__workflow_state__
attribute that stores a
dict of all workflow types and corresponding states assigned. When content is
added to the database (ObjectAdded
event is
emitted), all relevant registered workflows are initialized for it.
Features¶
- Site-wide workflows
- Multiple workflows per object
- Content type specific workflows
- Restrict transitions by permission
- Configurable callbacks when entering state
- Configurable callbacks when executing transition
- Reset workflow to initial state
Adding a workflow¶
Suppose we want to add a simple workflow:
/-----\ <-- to_draft ----- /---------\
|draft| |published|
\-----/ --- to_publish --> \---------/
Using add_workflow()
Pyramid configuration
directive:
>>> workflow = Workflow(initial_state="draft", type="article")
>>> workflow.add_state("draft")
>>> workflow.add_state("published")
>>> workflow.add_transition('to_publish', from_state='draft', to_state='published')
>>> workflow.add_transition('to_draft', from_state='published', to_state='draft')
...
>>> config.add_workflow(workflow, ('News',))
Interaction with the workflow¶
Retrieve a Workflow
instance using
the substanced.workflow.get_workflow()
:
>>> from substanced.workflow import get_workflow
>>> workflow = get_workflow(request, type='article', content_type='News')
Suppose there is a context object at hand, you can
reset()
its workflow to initial state:
>>> workflow.reset(context, request)
You could check it has_state()
and assert
state_of()
context is initial state name
of the workflow:
>>> assert workflow.has_state(context) == True
>>> assert workflow.state_of(context) == workflow.initial_state
List possible transitions from the current state of the workflow
with get_transitions()
:
>>> workflow.get_transitions(context, request)
[{'from_state': 'draft',
'callback': None,
'permission': None,
'name': 'to_publish',
'to_state': 'published'}]
Execute a transition()
:
>>> workflow.transition(context, request, 'to_publish')
List all states of the workflow with
get_states()
:
>>> workflow.get_states(context, request)
[{'name': 'draft',
'title': 'draft',
'initial': True,
'current': False,
'transitions': [{'from_state': 'draft',
'callback': None,
'permission': None,
'name': 'to_publish',
'to_state': 'published'}],
'data': {'callback': None}},
{'name': 'published',
'title': 'published',
'initial': False,
'current': True,
'transitions': [{'from_state': 'published',
'callback': None,
'permission': None,
'name': 'to_draft',
'to_state': 'draft'}],
'data': {'callback': None}}]
Execute a transition_to_state()
:
>>> workflow.transition_to_state(context, request, 'draft')
Using callbacks¶
Typically you will want to define custom actions when transition is executed or when content enters a specific state. Let’s define a transition with a callback:
>>> def cb(context, **kw):
... print "keywords: ", kw
>>> workflow.add_transition('to_publish_with_callback',
... from_state='draft',
... to_state='published',
... callback=cb)
When you execute the transition, callback is called:
>>> workflow.transition(context, request, 'to_publish_with_callback')
keywords: {'workflow': <Workflow ...>, 'transition': {'to_state': 'published', 'from_state': 'draft', ...}, request=<Request ...>}
To know more about callback parameters, read
add_transition()
signature.