Workflows ========= A workflow is a collection of :term:`transitions` that :term:`transition` between :term:`states`. Specifically, :mod:`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 :class:`~substanced.workflow.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 (:class:`~substanced.event.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 :func:`~substanced.workflow.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 :class:`~substanced.workflow.Workflow` instance using the :func:`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 :meth:`~substanced.workflow.Workflow.reset` its workflow to initial state:: >>> workflow.reset(context, request) You could check it :meth:`~substanced.workflow.Workflow.has_state` and assert :meth:`~substanced.workflow.Workflow.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 :meth:`~substanced.workflow.Workflow.get_transitions`:: >>> workflow.get_transitions(context, request) [{'from_state': 'draft', 'callback': None, 'permission': None, 'name': 'to_publish', 'to_state': 'published'}] Execute a :meth:`~substanced.workflow.Workflow.transition`:: >>> workflow.transition(context, request, 'to_publish') List all states of the workflow with :meth:`~substanced.workflow.Workflow.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 :meth:`~substanced.workflow.Workflow.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': , 'transition': {'to_state': 'published', 'from_state': 'draft', ...}, request=} To know more about callback parameters, read :meth:`~substanced.workflow.Workflow.add_transition` signature.