#221: Use adaption for workflow history and status
- Contents
- Proposed by
- Laurence Rowe
- Proposal type
- Architecture
- State
- being-discussed
Definitions
Motivation
Currently it is very difficult to use alternate mechanisms for storing workflow history. For example collective.tin needs a content mixin class defining a workflow_history property in order to store workflow history in a database.
This proposal is inspired by Kapil's work in cmfblackbird and my experience building collective.tin.
Assumptions
Proposal
Providing workflow history storage via adaptation is a pretty trivial task which can be made fully backward compatible. We simply need to replace the content of the WorkflowTool.getHistoryOf , setStatusOf methods with a lookup via adaptation:
def getHistoryOf(self, wf_id, ob):
""" Get the history of an object for a given workflow.
"""
return queryMultiAdapter((ob, wf_id), IWorkflowHistory, default=())
def setStatusOf(self, wf_id, ob, status):
""" Append a record to the workflow history of a given workflow.
"""
wfs = getMultiAdapter((ob, wf_id), IWorkflowStatus)
wfs.set(status)
def getStatusOf(self, wf_id, ob):
""" Get the last element of a workflow history for a given workflow.
"""
wfs = queryMultiAdapter((ob, wf_id), IWorkflowStatus, default=None)
if wfs:
return wfs.get()
return None
Multi-adapters on the content object and workflow id are used. This allows for content type developers and integrators to customise the storage of workflow status and optionally history for either all workflows or individual workflows (by registering a multiadapter for (IContentish, 'workflow_id'). The default adapters are simply:
class DefaultWorkflowStatus(object):
implements(IWorkflowStatus)
adapts(IContentish, basestring)
def __init__(self, context, wf_id):
self.context = aq_base(context)
self.wf_id = wf_id
def get(self):
history = getattr(self.context, 'workflow_history', {})
return history.get(self.wf_id, None)
def set(self, status):
history = getattr(self.context, 'workflow_history', None)
if history is None:
history = self.context.workflow_history = PersistentMapping()
wfh = list(history.get(self.wf_id, ()))
wfh.append(status)
history[self.wf_id] = tuple(wfh)
@implementer(IWorkflowHistory)
@adapter(IContentish, basestring)
def default_workflow_history(context, wf_id):
history = getattr(aq_base(context), 'workflow_history', {})
return history.get(self.wf_id, ())
Interfaces are:
class IWorkflowStatus(Interface):
def get():
"""Return the workflow status"""
def set(status):
"""Update the workflow status to `status`"""
class IWorkflowHistory(Interface):
"""A sequence of workflow status dictionaries"""
Ideally this would be done in CMFCore. However, it's certainly feasible to do it inside of the CMFPlone workflow tool if no major release of CMF is imminent. The changes could easily be merged back into the core as needed.
Implementation
Deliverables
- The interfaces for IWorkflowHistory and IWorkflowStatus would need to be created.
- The getWorkflowHistory, setStatusOf and getStatusOf methods would need to be rewritten/overridden, and a test would need to be written to demonstrate the adaptation based mechanism.
- The default adapters would need to be created
- Some documentation on how to provide a custom workflow history and status storage should be provided.
Risks
There should be no risk as the WorkflowTool interface and default history storage remain consistent. The only risk is that our workflow tool will deviate further from the CMF version, hopefully we can get this code applied in CMFCore sooner rather than later.
Progress log
Participants
Framework vote
Framework team vote
Framework team vote
another implementation.. also backwards compatible
+1
that blackbird link is pretty old.. i separated out the functionality and put in into an egg.. here's the guts
http://svn.objectrealms.net/view/public/browser/ore.adaptedworkflow/trunk/src/ore/adaptedworkflow
i agree it should probably go in the cmf
Framework team vote