The data model I already have does things a bit differently, but I think it accomplishes the same thing. I am in the process of writing all of the support methods.
This is my current data model: # workflow table db.define_table('workflow', Field('name', length=50), Field('is_template', 'boolean', default=False), Field('created_by', db.auth_user, default=current_user), Field('created_on', 'datetime', default=self.request.now), Field('table_name', length=128), Field('row_id', 'integer'), Field('order_id', 'integer', default=self.ORDER_ID_STOP, comment='Current position of the workflow'), Field('priority', 'integer', requires=IS_INT_IN_RANGE(1, 9), default=self.DEFAULT_PRIORITY) ) # allow users / groups to "monitor" select workflows without needing to be a part of the workflow # if workflow is template, this list is copied to the new workflow db.define_table('workflow_monitor', Field('workflow_id', db.workflow), Field('user_id', db.auth_user), Field('group_id', db.auth_group), Field('viewed', 'boolean', default=False) # once the monitor looks at it, mark as viewed until another change happens ) # comments can be attached to workflows so that users can voice questions and concerns db.define_table('workflow_comment', Field('workflow_id', db.workflow), Field('user_id', db.auth_user, default=current_user), Field('event_date', 'datetime', default=self.request.now), Field('is_active', 'boolean', default=True, comment='Is the comment waiting to be addressed'), Field('comment', length=512, comment='The question, comment, or concern'), Field('response', length=512, comment='Response to the question, comment, or concern') ) # high-level list of goals for workflows. Users mark items as completed as they complete the goals # if workflow is template, the checklist is copied to new workflow db.define_table('workflow_checklist', Field('workflow_id', db.workflow), Field('name', length=50), Field('order_id', 'integer', comment='Ordering position of the item'), Field('is_active', 'boolean', default=True, comment='Is the item waiting to be addressed'), Field('completed_by', db.auth_user), Field('completed_at', 'datetime'), Field('completed_note', length=512) ) # workflow step table db.define_table('workflow_step', Field('workflow_id', db.workflow), Field('order_id', 'integer', comment='Ordering position of the step'), Field('name', length=50), Field('user_id', db.auth_user), Field('group_id', db.auth_group), Field('hours', 'decimal(10,2)', requires=IS_DECIMAL_IN_RANGE(0), default=0, comment='Optional time limit in hours'), Field('due_date', 'datetime', comment='Optional due date'), Field('note', length=512, comment='Note on current state of the step') # represents a stateful note (i.e. kickback reason). Can change at any time. ) # audit tables db.define_table('workflow_event', Field('workflow_id', 'integer'), Field('table_name', length=128), # i.e. document, folder, etc Field('row_id', 'integer'), # i.e. the ID of the document Field('user_id', db.auth_user, default=current_user), Field('event_date', 'datetime', default=self.request.now), Field('action', length=10, default='update'), # could be: create, update, delete Field('field_name', length=128), # i.e order_id, name, hours, etc Field('value_before', length=128), # None if create or delete Field('value_after', length=128) # None if delete ) db.define_table('workflow_step_event', Field('workflow_id', 'integer'), Field('step_id', 'integer'), # the ID of the workflow_step modified (used to match up fields in batch changes) Field('user_id', db.auth_user, default=current_user), Field('event_date', 'datetime', default=self.request.now), Field('action', length=10, default='update'), # could be: create, update, delete Field('field_name', length=128), # i.e order_id, name, hours, etc Field('value_before', length=128), # None if create or delete Field('value_after', length=128) # None if delete ) As you can see, this model has quite a few of the features we discussed: workflows, templates, monitors, comments, checklists, and auditing. It also has triggers that applications can register (i.e. before_step_complete, after_step_complete, etc) so applications can interact with the workflow engine when an event is triggered. Workflows are copied from templates so that changes can be made to templates and workflows without one affecting the other. This has worked very well in my previous implementation. Tying monitors to templates wouldn't work well like this. Attaching monitors to workflows themselves also offers the advantage of giving the monitors a read/unread indicator and when a workflow has been changed (i.e step completed), the indicator turns back to "unread" so that way they keep up with workflows and not have to remember if a workflow has changed since the last time they looked at it. My thinking on approvers is that they need to be part of the workflow. If the workflow cannot progress until it has been approved, then it should be a step in the workflow. If the approver finds something wrong, they can reject their step (with notes as to why) and send it back to anyone in the chain. Making approvals part of the workflow also has the advantage of optionally setting a time limit for the approver to make a decision. I haven't made any attempts to add authorization functionality yet. I'm thinking about using the web2py authorization system for this. You would set up groups, add users to groups, and add workflow permissions to the groups. For example: # allow group_id to create workflows on-the-fly auth.add_permission(group_id, 'create', 'workflow', 0) # allow group_id to load templates auth.add_permission(group_id, 'load_template', 'workflow', 0) # allow group_id to create templates auth.add_permission(group_id, 'create_template', 'workflow', 0) # give comment permission to group_id for workflow_id auth.add_permission(group_id, 'comment', 'workflow', workflow_id) # allow group_id to step-complete or reject steps for workflow_id auth.add_permission(group_id, 'flow', 'workflow', workflow_id) # allow group_id to modify workflow steps for workflow_id auth.add_permission(group_id, 'update', 'workflow', workflow_id) I would probably have the engine create a default set of groups and permissions on first-run. For example, maybe groups called "Workflow Monitor", "Workflow Participant", 'Workflow Creator", "Workflow Template Creator". Each workflow would have permissions set for it that can be predefined in templates (which are then copied to workflows when created).