Commit 9aec6396 authored by Hanno Schlichting's avatar Hanno Schlichting

Forgot trunk

parents
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" A convenient base class for representing a container as a management tab.
$Id$
"""
from Acquisition import aq_base, aq_inner, aq_parent
from OFS.Folder import Folder
_marker = [] # Create a new marker object.
class ContainerTab(Folder):
def __init__(self, id):
self.id = id
self._mapping = {}
def getId(self):
return self.id
def manage_options(self):
parent = aq_parent(aq_inner(self))
res = []
options = parent.manage_options
if callable(options):
options = options()
for item in options:
item = item.copy()
item['action'] = '../' + item['action']
res.append(item)
return res
def manage_workspace(self, RESPONSE):
'''
Redirects to the primary option.
'''
RESPONSE.redirect(self.absolute_url() + '/manage_main')
def _checkId(self, id, allow_dup=0):
if not allow_dup:
if self._mapping.has_key(id):
raise 'Bad Request', 'The id "%s" is already in use.' % id
return Folder._checkId(self, id, allow_dup)
def _getOb(self, name, default=_marker):
mapping = self._mapping
if mapping.has_key(name):
res = mapping[name]
if hasattr(res, '__of__'):
res = res.__of__(self)
return res
else:
if default is _marker:
raise KeyError, name
return default
def __getattr__(self, name):
ob = self._mapping.get(name, None)
if ob is not None:
return ob
raise AttributeError, name
def _setOb(self, name, value):
mapping = self._mapping
mapping[name] = aq_base(value)
self._mapping = mapping # Trigger persistence.
def _delOb(self, name):
mapping = self._mapping
del mapping[name]
self._mapping = mapping # Trigger persistence.
def get(self, name, default=None):
if self._mapping.has_key(name):
return self[name]
else:
return default
def has_key(self, key):
return self._mapping.has_key(key)
def objectIds(self, spec=None):
# spec is not important for now...
return self._mapping.keys()
def keys(self):
return self._mapping.keys()
def items(self):
return map(lambda id, self=self: (id, self._getOb(id)),
self._mapping.keys())
def values(self):
return map(lambda id, self=self: self._getOb(id),
self._mapping.keys())
def manage_renameObjects(self, ids=[], new_ids=[], REQUEST=None):
"""Rename several sub-objects"""
if len(ids) != len(new_ids):
raise 'Bad Request', 'Please rename each listed object.'
for i in range(len(ids)):
if ids[i] != new_ids[i]:
self.manage_renameObject(ids[i], new_ids[i])
if REQUEST is not None:
return self.manage_main(REQUEST)
return None
This diff is collapsed.
Zope >= 2.10.4
five.localsitemanager >= 0.2
CMFCore
GenericSetup
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Expressions in a web-configurable workflow.
$Id$
"""
from warnings import warn
import Globals
from Globals import Persistent
from Acquisition import aq_inner, aq_parent
from AccessControl import getSecurityManager, ClassSecurityInfo
from DateTime import DateTime
from Products.CMFCore.WorkflowCore import ObjectDeleted, ObjectMoved
from Products.CMFCore.Expression import Expression
from Products.CMFCore.interfaces import ISiteRoot
from Products.PageTemplates.Expressions import getEngine
from Products.PageTemplates.Expressions import SecureModuleImporter
# We don't import SafeMapping from Products.PageTemplates.TALES
# because it's deprecated in Zope 2.10
from MultiMapping import MultiMapping
class SafeMapping(MultiMapping):
"""Mapping with security declarations and limited method exposure.
Since it subclasses MultiMapping, this class can be used to wrap
one or more mapping objects. Restricted Python code will not be
able to mutate the SafeMapping or the wrapped mappings, but will be
able to read any value.
"""
__allow_access_to_unprotected_subobjects__ = 1
push = pop = None
_push = MultiMapping.push
_pop = MultiMapping.pop
class StateChangeInfo:
'''
Provides information for expressions and scripts.
'''
_date = None
ObjectDeleted = ObjectDeleted
ObjectMoved = ObjectMoved
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
def __init__(self, object, workflow, status=None, transition=None,
old_state=None, new_state=None, kwargs=None):
if kwargs is None:
kwargs = {}
else:
# Don't allow mutation
kwargs = SafeMapping(kwargs)
if status is None:
tool = aq_parent(aq_inner(workflow))
status = tool.getStatusOf(workflow.id, object)
if status is None:
status = {}
if status:
# Don't allow mutation
status = SafeMapping(status)
self.object = object
self.workflow = workflow
self.old_state = old_state
self.new_state = new_state
self.transition = transition
self.status = status
self.kwargs = kwargs
def __getitem__(self, name):
if name[:1] != '_' and hasattr(self, name):
return getattr(self, name)
raise KeyError, name
def getHistory(self):
wf = self.workflow
tool = aq_parent(aq_inner(wf))
wf_id = wf.id
h = tool.getHistoryOf(wf_id, self.object)
if h:
return map(lambda dict: dict.copy(), h) # Don't allow mutation
else:
return ()
def getPortal(self):
ob = aq_inner(self.object)
while ob is not None:
if ISiteRoot.providedBy(ob):
return ob
if getattr(ob, '_isPortalRoot', None) is not None:
# BBB
warn("The '_isPortalRoot' marker attribute for site "
"roots is deprecated and will be removed in "
"CMF 2.3; please mark the root object with "
"'ISiteRoot' instead.",
DeprecationWarning, stacklevel=2)
return ob
ob = aq_parent(ob)
return None
def getDateTime(self):
date = self._date
if not date:
date = self._date = DateTime()
return date
Globals.InitializeClass(StateChangeInfo)
def createExprContext(sci):
'''
An expression context provides names for TALES expressions.
'''
ob = sci.object
wf = sci.workflow
container = aq_parent(aq_inner(ob))
data = {
'here': ob,
'object': ob,
'container': container,
'folder': container,
'nothing': None,
'root': ob.getPhysicalRoot(),
'request': getattr( ob, 'REQUEST', None ),
'modules': SecureModuleImporter,
'user': getSecurityManager().getUser(),
'state_change': sci,
'transition': sci.transition,
'status': sci.status,
'kwargs': sci.kwargs,
'workflow': wf,
'scripts': wf.scripts,
}
return getEngine().getContext(data)
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" External method used for unit tests - do not remove
$Id: test_method.py 37115 2005-07-07 15:50:07Z jens $
"""
def test(self):
return None
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Guard conditions in a web-configurable workflow.
$Id$
"""
from cgi import escape
from Globals import DTMLFile
from Globals import InitializeClass
from Globals import Persistent
from AccessControl import ClassSecurityInfo
from Acquisition import Explicit
from Acquisition import aq_base
from Products.CMFCore.utils import _checkPermission
from Expression import Expression
from Expression import StateChangeInfo
from Expression import createExprContext
from permissions import ManagePortal
from utils import _dtmldir
class Guard (Persistent, Explicit):
permissions = ()
roles = ()
groups = ()
expr = None
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
guardForm = DTMLFile('guard', _dtmldir)
def check(self, sm, wf_def, ob, **kw):
"""Checks conditions in this guard.
"""
u_roles = None
if wf_def.manager_bypass:
# Possibly bypass.
u_roles = sm.getUser().getRolesInContext(ob)
if 'Manager' in u_roles:
return 1
if self.permissions:
for p in self.permissions:
if _checkPermission(p, ob):
break
else:
return 0
if self.roles:
# Require at least one of the given roles.
if u_roles is None:
u_roles = sm.getUser().getRolesInContext(ob)
for role in self.roles:
if role in u_roles:
break
else:
return 0
if self.groups:
# Require at least one of the specified groups.
u = sm.getUser()
b = aq_base( u )
if hasattr( b, 'getGroupsInContext' ):
u_groups = u.getGroupsInContext( ob )
elif hasattr( b, 'getGroups' ):
u_groups = u.getGroups()
else:
u_groups = ()
for group in self.groups:
if group in u_groups:
break
else:
return 0
expr = self.expr
if expr is not None:
econtext = createExprContext(
StateChangeInfo(ob, wf_def, kwargs=kw))
res = expr(econtext)
if not res:
return 0
return 1
security.declareProtected(ManagePortal, 'getSummary')
def getSummary(self):
# Perhaps ought to be in DTML.
res = []
if self.permissions:
res.append('Requires permission:')
res.append(formatNameUnion(self.permissions))
if self.roles:
if res:
res.append('<br/>')
res.append('Requires role:')
res.append(formatNameUnion(self.roles))
if self.groups:
if res:
res.append('<br/>')
res.append('Requires group:')
res.append(formatNameUnion(self.groups))
if self.expr is not None:
if res:
res.append('<br/>')
res.append('Requires expr:')
res.append('<code>' + escape(self.expr.text) + '</code>')
return ' '.join(res)
def changeFromProperties(self, props):
'''
Returns 1 if changes were specified.
'''
if props is None:
return 0
res = 0
s = props.get('guard_permissions', None)
if s:
res = 1
p = [ permission.strip() for permission in s.split(';') ]
self.permissions = tuple(p)
s = props.get('guard_roles', None)
if s:
res = 1
r = [ role.strip() for role in s.split(';') ]
self.roles = tuple(r)
s = props.get('guard_groups', None)
if s:
res = 1
g = [ group.strip() for group in s.split(';') ]
self.groups = tuple(g)
s = props.get('guard_expr', None)
if s:
res = 1
self.expr = Expression(s)
return res
security.declareProtected(ManagePortal, 'getPermissionsText')
def getPermissionsText(self):
if not self.permissions:
return ''
return '; '.join(self.permissions)
security.declareProtected(ManagePortal, 'getRolesText')
def getRolesText(self):
if not self.roles:
return ''
return '; '.join(self.roles)
security.declareProtected(ManagePortal, 'getGroupsText')
def getGroupsText(self):
if not self.groups:
return ''
return '; '.join(self.groups)
security.declareProtected(ManagePortal, 'getExprText')
def getExprText(self):
if not self.expr:
return ''
return str(self.expr.text)
InitializeClass(Guard)
def formatNameUnion(names):
escaped = ['<code>' + escape(name) + '</code>' for name in names]
if len(escaped) == 2:
return ' or '.join(escaped)
elif len(escaped) > 2:
escaped[-1] = ' or ' + escaped[-1]
return '; '.join(escaped)
There is some documentation in the 'doc' directory.
I encourage you to experiment with what's available and invite you to
ask questions about the tool on zope-cmf@zope.org. Future development
depends entirely on people's needs, so speak up!
Shane Hathaway
Zope Corporation
shane@zope.com
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Scripts in a web-configurable workflow.
$Id$
"""
from OFS.Folder import Folder
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from ContainerTab import ContainerTab
from permissions import ManagePortal
class Scripts (ContainerTab):
"""A container for workflow scripts"""
meta_type = 'Workflow Scripts'
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
def manage_main(self, client=None, REQUEST=None, **kw):
'''
'''
kw['management_view'] = 'Scripts'
m = Folder.manage_main.__of__(self)
return m(self, client, REQUEST, **kw)
InitializeClass(Scripts)
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Transitions in a web-configurable workflow.
$Id$
"""
from OFS.SimpleItem import SimpleItem
from Globals import DTMLFile
from Globals import PersistentMapping
from Globals import InitializeClass
from Acquisition import aq_inner
from Acquisition import aq_parent
from AccessControl import ClassSecurityInfo
from ContainerTab import ContainerTab
from Guard import Guard
from permissions import ManagePortal
from utils import _dtmldir
from Expression import Expression
TRIGGER_AUTOMATIC = 0
TRIGGER_USER_ACTION = 1
class TransitionDefinition (SimpleItem):
"""Transition definition"""
meta_type = 'Workflow Transition'
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
title = ''
description = ''
new_state_id = ''
trigger_type = TRIGGER_USER_ACTION
guard = None
actbox_name = ''
actbox_url = ''
actbox_category = 'workflow'
var_exprs = None # A mapping.
script_name = None # Executed before transition
after_script_name = None # Executed after transition
manage_options = (
{'label': 'Properties', 'action': 'manage_properties'},
{'label': 'Variables', 'action': 'manage_variables'},
)
def __init__(self, id):
self.id = id
def getId(self):
return self.id
def getGuardSummary(self):
res = None
if self.guard is not None:
res = self.guard.getSummary()
return res
def getGuard(self):
if self.guard is not None:
return self.guard
else:
return Guard().__of__(self) # Create a temporary guard.
def getVarExprText(self, id):
if not self.var_exprs:
return ''
else:
expr = self.var_exprs.get(id, None)
if expr is not None:
return expr.text
else:
return ''
def getWorkflow(self):
return aq_parent(aq_inner(aq_parent(aq_inner(self))))
def getAvailableStateIds(self):
return self.getWorkflow().states.keys()
def getAvailableScriptIds(self):
return self.getWorkflow().scripts.keys()
def getAvailableVarIds(self):
return self.getWorkflow().variables.keys()
_properties_form = DTMLFile('transition_properties', _dtmldir)
def manage_properties(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._properties_form(REQUEST,
management_view='Properties',
manage_tabs_message=manage_tabs_message,
)
def setProperties(self, title, new_state_id,
trigger_type=TRIGGER_USER_ACTION, script_name='',
after_script_name='',
actbox_name='', actbox_url='',
actbox_category='workflow',
props=None, REQUEST=None, description=''):
'''
'''
self.title = str(title)
self.description = str(description)
self.new_state_id = str(new_state_id)
self.trigger_type = int(trigger_type)
self.script_name = str(script_name)
self.after_script_name = str(after_script_name)
g = Guard()
if g.changeFromProperties(props or REQUEST):
self.guard = g
else:
self.guard = None
self.actbox_name = str(actbox_name)
self.actbox_url = str(actbox_url)
self.actbox_category = str(actbox_category)
if REQUEST is not None:
return self.manage_properties(REQUEST, 'Properties changed.')
_variables_form = DTMLFile('transition_variables', _dtmldir)
def manage_variables(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._variables_form(REQUEST,
management_view='Variables',
manage_tabs_message=manage_tabs_message,
)
def getVariableExprs(self):
''' get variable exprs for management UI
'''
ve = self.var_exprs
if ve is None:
return []
else:
ret = []
for key in ve.keys():
ret.append((key,self.getVarExprText(key)))
return ret
def getWorkflowVariables(self):
''' get all variables that are available form
workflow and not handled yet.
'''
wf_vars = self.getAvailableVarIds()
if self.var_exprs is None:
return wf_vars
ret = []
for vid in wf_vars:
if not self.var_exprs.has_key(vid):
ret.append(vid)
return ret
def addVariable(self, id, text, REQUEST=None):
'''
Add a variable expression.
'''
if self.var_exprs is None:
self.var_exprs = PersistentMapping()
expr = None
if text:
expr = Expression(str(text))
self.var_exprs[id] = expr
if REQUEST is not None:
return self.manage_variables(REQUEST, 'Variable added.')
def deleteVariables(self,ids=[],REQUEST=None):
''' delete a WorkflowVariable from State
'''
ve = self.var_exprs
for id in ids:
if ve.has_key(id):
del ve[id]
if REQUEST is not None:
return self.manage_variables(REQUEST, 'Variables deleted.')
def setVariables(self, ids=[], REQUEST=None):
''' set values for Variables set by this state
'''
if self.var_exprs is None:
self.var_exprs = PersistentMapping()
ve = self.var_exprs
if REQUEST is not None:
for id in ve.keys():
fname = 'varexpr_%s' % id
val = REQUEST[fname]
expr = None
if val:
expr = Expression(str(REQUEST[fname]))
ve[id] = expr
return self.manage_variables(REQUEST, 'Variables changed.')
InitializeClass(TransitionDefinition)
class Transitions (ContainerTab):
"""A container for transition definitions"""
meta_type = 'Workflow Transitions'
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
all_meta_types = ({'name':TransitionDefinition.meta_type,
'action':'addTransition',
},)
_manage_transitions = DTMLFile('transitions', _dtmldir)
def manage_main(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._manage_transitions(
REQUEST,
management_view='Transitions',
manage_tabs_message=manage_tabs_message,
)
def addTransition(self, id, REQUEST=None):
'''
'''
tdef = TransitionDefinition(id)
self._setObject(id, tdef)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Transition added.')
def deleteTransitions(self, ids, REQUEST=None):
'''
'''
for id in ids:
self._delObject(id)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Transition(s) removed.')
InitializeClass(Transitions)
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Variables in a web-configurable workflow.
$Id$
"""
from AccessControl import ClassSecurityInfo
from Acquisition import aq_inner
from Acquisition import aq_parent
from Globals import DTMLFile
from Globals import InitializeClass
from OFS.SimpleItem import SimpleItem
from ContainerTab import ContainerTab
from Expression import Expression
from Guard import Guard
from permissions import ManagePortal
from utils import _dtmldir
class VariableDefinition(SimpleItem):
"""Variable definition"""
meta_type = 'Workflow Variable'
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
description = ''
for_catalog = 1
for_status = 1
default_value = ''
default_expr = None # Overrides default_value if set
info_guard = None
update_always = 1
manage_options = (
{'label': 'Properties', 'action': 'manage_properties'},
)
def __init__(self, id):
self.id = id
def getDefaultExprText(self):
if not self.default_expr:
return ''
else:
return self.default_expr.text
def getInfoGuard(self):
if self.info_guard is not None:
return self.info_guard
else:
return Guard().__of__(self) # Create a temporary guard.
def getInfoGuardSummary(self):
res = None
if self.info_guard is not None:
res = self.info_guard.getSummary()
return res
_properties_form = DTMLFile('variable_properties', _dtmldir)
def manage_properties(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._properties_form(REQUEST,
management_view='Properties',
manage_tabs_message=manage_tabs_message,
)
def setProperties(self, description,
default_value='', default_expr='',
for_catalog=0, for_status=0,
update_always=0,
props=None, REQUEST=None):
'''
'''
self.description = str(description)
self.default_value = str(default_value)
if default_expr:
self.default_expr = Expression(default_expr)
else:
self.default_expr = None
g = Guard()
if g.changeFromProperties(props or REQUEST):
self.info_guard = g
else:
self.info_guard = None
self.for_catalog = bool(for_catalog)
self.for_status = bool(for_status)
self.update_always = bool(update_always)
if REQUEST is not None:
return self.manage_properties(REQUEST, 'Properties changed.')
InitializeClass(VariableDefinition)
class Variables(ContainerTab):
"""A container for variable definitions"""
meta_type = 'Workflow Variables'
all_meta_types = ({'name':VariableDefinition.meta_type,
'action':'addVariable',
},)
_manage_variables = DTMLFile('variables', _dtmldir)
def manage_main(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._manage_variables(
REQUEST,
management_view='Variables',
manage_tabs_message=manage_tabs_message,
)
def addVariable(self, id, REQUEST=None):
'''
'''
vdef = VariableDefinition(id)
self._setObject(id, vdef)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Variable added.')
def deleteVariables(self, ids, REQUEST=None):
'''
'''
for id in ids:
self._delObject(id)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Variable(s) removed.')
def _checkId(self, id, allow_dup=0):
wf_def = aq_parent(aq_inner(self))
if id == wf_def.state_var:
raise 'Bad Request', '"%s" is used for keeping state.' % id
return ContainerTab._checkId(self, id, allow_dup)
def getStateVar(self):
wf_def = aq_parent(aq_inner(self))
return wf_def.state_var
def setStateVar(self, id, REQUEST=None):
'''
'''
wf_def = aq_parent(aq_inner(self))
if id != wf_def.state_var:
self._checkId(id)
wf_def.state_var = str(id)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Set state variable.')
InitializeClass(Variables)
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Web-configurable workflow UI.
$Id$
"""
import os
from Globals import DTMLFile
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.requestmethod import postonly
from Acquisition import aq_get
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from permissions import ManagePortal
from Guard import Guard
from utils import _dtmldir
try:
#
# XXX: 2004/04/28 This factoring *has* to go; if necessary,
# this module could have a hook function, which the dependent
# module could replace.
#
# If base_cms exists, include the roles it defines.
from Products.base_cms.permissions import getDefaultRolePermissionMap
except ImportError:
def getDefaultRolePermissionMap():
return {}
class WorkflowUIMixin:
'''
'''
security = ClassSecurityInfo()
security.declareProtected(ManagePortal, 'manage_properties')
manage_properties = DTMLFile('workflow_properties', _dtmldir)
manage_groups = PageTemplateFile('workflow_groups.pt', _dtmldir)
security.declareProtected(ManagePortal, 'setProperties')
@postonly
def setProperties(self, title, manager_bypass=0, props=None,
REQUEST=None, description=''):
"""Sets basic properties.
"""
self.title = str(title)
self.description = str(description)
self.manager_bypass = manager_bypass and 1 or 0
g = Guard()
if g.changeFromProperties(props or REQUEST):
self.creation_guard = g
else:
self.creation_guard = None
if REQUEST is not None:
return self.manage_properties(
REQUEST, manage_tabs_message='Properties changed.')
_permissions_form = DTMLFile('workflow_permissions', _dtmldir)
security.declareProtected(ManagePortal, 'manage_permissions')
def manage_permissions(self, REQUEST, manage_tabs_message=None):
"""Displays the form for choosing which permissions to manage.
"""
return self._permissions_form(REQUEST,
management_view='Permissions',
manage_tabs_message=manage_tabs_message,
)
security.declareProtected(ManagePortal, 'addManagedPermission')
@postonly
def addManagedPermission(self, p, REQUEST=None):
"""Adds to the list of permissions to manage.
"""
if p in self.permissions:
raise ValueError, 'Already a managed permission: ' + p
if REQUEST is not None and p not in self.getPossiblePermissions():
raise ValueError, 'Not a valid permission name:' + p
self.permissions = self.permissions + (p,)
if REQUEST is not None:
return self.manage_permissions(
REQUEST, manage_tabs_message='Permission added.')
security.declareProtected(ManagePortal, 'delManagedPermissions')
@postonly
def delManagedPermissions(self, ps, REQUEST=None):
"""Removes from the list of permissions to manage.
"""
if ps:
l = list(self.permissions)
for p in ps:
l.remove(p)
self.permissions = tuple(l)
if REQUEST is not None:
return self.manage_permissions(
REQUEST, manage_tabs_message='Permission(s) removed.')
security.declareProtected(ManagePortal, 'getPossiblePermissions')
def getPossiblePermissions(self):
"""Returns the list of all permissions that can be managed.
"""
# possible_permissions is in AccessControl.Role.RoleManager.
return list(self.possible_permissions())
security.declareProtected(ManagePortal, 'getGroups')
def getGroups(self):
"""Returns the names of groups managed by this workflow.
"""
return tuple(self.groups)
security.declareProtected(ManagePortal, 'getAvailableGroups')
def getAvailableGroups(self):
"""Returns a list of available group names.
"""
gf = aq_get( self, '__allow_groups__', None, 1 )
if gf is None:
return ()
try:
groups = gf.searchGroups()
except AttributeError:
return ()
else:
return [g['id'] for g in groups]
security.declareProtected(ManagePortal, 'addGroup')
@postonly
def addGroup(self, group, RESPONSE=None, REQUEST=None):
"""Adds a group by name.
"""
if group not in self.getAvailableGroups():
raise ValueError(group)
self.groups = self.groups + (group,)
if RESPONSE is not None:
RESPONSE.redirect(
"%s/manage_groups?manage_tabs_message=Added+group."
% self.absolute_url())
security.declareProtected(ManagePortal, 'delGroups')
@postonly
def delGroups(self, groups, RESPONSE=None, REQUEST=None):
"""Removes groups by name.
"""
self.groups = tuple([g for g in self.groups if g not in groups])
if RESPONSE is not None:
RESPONSE.redirect(
"%s/manage_groups?manage_tabs_message=Groups+removed."
% self.absolute_url())
security.declareProtected(ManagePortal, 'getAvailableRoles')
def getAvailableRoles(self):
"""Returns the acquired roles mixed with base_cms roles.
"""
roles = list(self.valid_roles())
for role in getDefaultRolePermissionMap().keys():
if role not in roles:
roles.append(role)
roles.sort()
return roles
security.declareProtected(ManagePortal, 'getRoles')
def getRoles(self):
"""Returns the list of roles managed by this workflow.
"""
roles = self.roles
if roles is not None:
return roles
roles = getDefaultRolePermissionMap().keys()
if roles:
# Map the base_cms roles by default.
roles.sort()
return roles
return self.valid_roles()
security.declareProtected(ManagePortal, 'setRoles')
@postonly
def setRoles(self, roles, RESPONSE=None, REQUEST=None):
"""Changes the list of roles mapped to groups by this workflow.
"""
avail = self.getAvailableRoles()
for role in roles:
if role not in avail:
raise ValueError(role)
self.roles = tuple(roles)
if RESPONSE is not None:
RESPONSE.redirect(
"%s/manage_groups?manage_tabs_message=Roles+changed."
% self.absolute_url())
security.declareProtected(ManagePortal, 'getGuard')
def getGuard(self):
"""Returns the initiation guard.
If no init guard has been created, returns a temporary object.
"""
if self.creation_guard is not None:
return self.creation_guard
else:
return Guard().__of__(self) # Create a temporary guard.
security.declarePublic('guardExprDocs')
def guardExprDocs(self):
"""Returns documentation on guard expressions.
"""
here = os.path.dirname(__file__)
fn = os.path.join(here, 'doc', 'expressions.stx')
f = open(fn, 'rt')
try:
text = f.read()
finally:
f.close()
from DocumentTemplate.DT_Var import structured_text
return structured_text(text)
InitializeClass(WorkflowUIMixin)
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Worklists in a web-configurable workflow.
$Id$
"""
from AccessControl import ClassSecurityInfo
from Acquisition import aq_inner
from Acquisition import aq_parent
from Globals import DTMLFile
from Globals import InitializeClass
from Globals import PersistentMapping
from OFS.SimpleItem import SimpleItem
from ContainerTab import ContainerTab
from Guard import Guard
from permissions import ManagePortal
from utils import _dtmldir
class WorklistDefinition(SimpleItem):
"""Worklist definiton"""
meta_type = 'Worklist'
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
description = ''
var_matches = None # Compared with catalog when set.
actbox_name = ''
actbox_url = ''
actbox_category = 'global'
guard = None
manage_options = (
{'label': 'Properties', 'action': 'manage_properties'},
)
def __init__(self, id):
self.id = id
def getGuard(self):
if self.guard is not None:
return self.guard
else:
return Guard().__of__(self) # Create a temporary guard.
def getGuardSummary(self):
res = None
if self.guard is not None:
res = self.guard.getSummary()
return res
def getWorkflow(self):
return aq_parent(aq_inner(aq_parent(aq_inner(self))))
def getAvailableCatalogVars(self):
res = []
res.append(self.getWorkflow().state_var)
for id, vdef in self.getWorkflow().variables.items():
if vdef.for_catalog:
res.append(id)
res.sort()
return res
def getVarMatchKeys(self):
if self.var_matches:
return self.var_matches.keys()
else:
return []
def getVarMatch(self, id):
if self.var_matches:
matches = self.var_matches.get(id, ())
if not isinstance(matches, tuple):
# Old version, convert it.
matches = (matches,)
self.var_matches[id] = matches
return matches
else:
return ()
def getVarMatchText(self, id):
values = self.getVarMatch(id)
return '; '.join(values)
_properties_form = DTMLFile('worklist_properties', _dtmldir)
def manage_properties(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._properties_form(REQUEST,
management_view='Properties',
manage_tabs_message=manage_tabs_message,
)
def setProperties(self, description,
actbox_name='', actbox_url='', actbox_category='global',
props=None, REQUEST=None):
'''
'''
if props is None:
props = REQUEST
self.description = str(description)
for key in self.getAvailableCatalogVars():
# Populate var_matches.
fieldname = 'var_match_%s' % key
v = props.get(fieldname, '')
if v:
if not self.var_matches:
self.var_matches = PersistentMapping()
v = [ var.strip() for var in v.split(';') ]
self.var_matches[key] = tuple(v)
else:
if self.var_matches and self.var_matches.has_key(key):
del self.var_matches[key]
self.actbox_name = str(actbox_name)
self.actbox_url = str(actbox_url)
self.actbox_category = str(actbox_category)
g = Guard()
if g.changeFromProperties(props or REQUEST):
self.guard = g
else:
self.guard = None
if REQUEST is not None:
return self.manage_properties(REQUEST, 'Properties changed.')
InitializeClass(WorklistDefinition)
class Worklists(ContainerTab):
"""A container for worklist definitions"""
meta_type = 'Worklists'
security = ClassSecurityInfo()
security.declareObjectProtected(ManagePortal)
all_meta_types = ({'name':WorklistDefinition.meta_type,
'action':'addWorklist',
},)
_manage_worklists = DTMLFile('worklists', _dtmldir)
def manage_main(self, REQUEST, manage_tabs_message=None):
'''
'''
return self._manage_worklists(
REQUEST,
management_view='Worklists',
manage_tabs_message=manage_tabs_message,
)
def addWorklist(self, id, REQUEST=None):
'''
'''
qdef = WorklistDefinition(id)
self._setObject(id, qdef)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Worklist added.')
def deleteWorklists(self, ids, REQUEST=None):
'''
'''
for id in ids:
self._delObject(id)
if REQUEST is not None:
return self.manage_main(REQUEST, 'Worklist(s) removed.')
InitializeClass(Worklists)
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Web-configurable workflow.
$Id$
"""
from Products.CMFCore.utils import registerIcon
import DCWorkflow, States, Transitions, Variables, Worklists, Scripts
def initialize(context):
context.registerHelp(directory='help')
context.registerHelpTitle('DCWorkflow')
registerIcon(DCWorkflow.DCWorkflowDefinition,
'images/workflow.gif', globals())
registerIcon(States.States,
'images/state.gif', globals())
States.StateDefinition.icon = States.States.icon
registerIcon(Transitions.Transitions,
'images/transition.gif', globals())
Transitions.TransitionDefinition.icon = Transitions.Transitions.icon
registerIcon(Variables.Variables,
'images/variable.gif', globals())
Variables.VariableDefinition.icon = Variables.Variables.icon
registerIcon(Worklists.Worklists,
'images/worklist.gif', globals())
Worklists.WorklistDefinition.icon = Worklists.Worklists.icon
registerIcon(Scripts.Scripts,
'images/script.gif', globals())
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""DCWorkflow browser views.
$Id$
"""
<configure
xmlns:browser="http://namespaces.zope.org/browser"
package="Products.GenericSetup.browser"
>
<browser:page
for="zope.app.container.interfaces.IAdding"
name="addDCWorkflowDefinition.html"
template="addWithPresettings.pt"
class="Products.DCWorkflow.browser.workflow.DCWorkflowDefinitionAddView"
permission="cmf.ManagePortal"
/>
</configure>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""DCWorkflowDefinition browser views.
$Id$
"""
from xml.dom.minidom import parseString
from zope.component import queryMultiAdapter
from zope.component import queryUtility
from Products.GenericSetup.browser.utils import AddWithPresettingsViewBase
from Products.GenericSetup.interfaces import IBody
from Products.GenericSetup.interfaces import ISetupTool
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
class DCWorkflowDefinitionAddView(AddWithPresettingsViewBase):
"""Add view for DCWorkflowDefinition.
"""
klass = DCWorkflowDefinition
description = u'Add a web-configurable workflow.'
def getProfileInfos(self):
profiles = []
stool = queryUtility(ISetupTool)
if stool:
for info in stool.listContextInfos():
obj_ids = []
context = stool._getImportContext(info['id'])
file_ids = context.listDirectory('workflows')
for file_id in file_ids or ():
filename = 'workflows/%s/definition.xml' % file_id
body = context.readDataFile(filename)
if body is None:
continue
root = parseString(body).documentElement
obj_id = root.getAttribute('workflow_id')
obj_ids.append(obj_id)
if not obj_ids:
continue
obj_ids.sort()
profiles.append({'id': info['id'],
'title': info['title'],
'obj_ids': tuple(obj_ids)})
return tuple(profiles)
def _initSettings(self, obj, profile_id, obj_path):
stool = queryUtility(ISetupTool)
if stool is None:
return
context = stool._getImportContext(profile_id)
file_ids = context.listDirectory('workflows')
for file_id in file_ids or ():
filename = 'workflows/%s/definition.xml' % file_id
body = context.readDataFile(filename)
if body is None:
continue
root = parseString(body).documentElement
if not root.getAttribute('workflow_id') == obj_path[0]:
continue
importer = queryMultiAdapter((obj, context), IBody)
if importer is None:
continue
importer.body = body
return
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="cmf_default">
<include package=".browser"/>
<include file="exportimport.zcml"/>
<include file="tool.zcml"/>
<!-- profiles -->
<genericsetup:registerProfile
name="revision2"
title="CMF Default Workflow [Revision 2]"
description="Adds revision 2 of default workflow."
provides="Products.GenericSetup.interfaces.EXTENSION"
for="Products.CMFCore.interfaces.ISiteRoot"
/>
</configure>
Names and URLs for the actions box can be formatted using standard Python
string formatting. An example::
%(content_url)s/content_submit_form
The string '%(content_url)s' will be replaced by the value of content_url.
The following names are available:
- portal_url
- folder_url
- content_url
- user_id
- count (Available in work lists only. Represents the number of items in
the work list.)
The following names are also available, in case there is any use for them.
They are not strings.
- portal
- folder
- content
- isAnonymous
This product can be added to the portal_workflow tool of CMF site.
It provides fully customizable workflow.
To see an example, after installing DCWorkflow, using the "Contents"
tab of your portal_workflow instance add a "CMF default workflow (rev 2)"
instance. The other way to create it is to add a "Workflow" object,
selecting "dc_workflow" as the type. The second way will create a
blank workflow.
This tool is easiest to use if you draw a state diagram first. Your
diagram should have:
- States (bubbles)
- Transitions (arrows)
- Variables (both in states and transitions)
Remember to consider all the states your content can be in. Consider
the actions users will perform to make the transitions between states.
And consider not only who will be allowed to perform what functions,
but also who will be *required* to perform certain functions.
On the "States" tab, add a state with a simple ID for each state on
your diagram. On the "Transitions" tab, add a transition with a simple
ID for each group of arrows that point to the same state and have
similar characteristics. Then for each state choose which transitions
are allowed to leave that state.
Variables are useful for keeping track of things that aren't very well
represented as separate states, such as counters or information about
the action that was last performed. You can create variables that get
stored alongside the workflow state and you can make those variables
available in catalog searches. Some variables, such as the review
history, should not be stored at all. Those variables are accessible
through the getInfoFor() interface.
Worklists are a way to make people aware of tasks they are required
to perform. Worklists are implemented as a catalog query that puts
actions in the actions box when there is some task the user needs to
perform. Most of the time you just need to enter a state ID,
a role name, and the information to put in the actions box.
You can manage all of the actions a user can perform on an object by
setting up permissions to be managed by the workflow. Using the
"Permissions" tab, select which permissions should be state-dependent.
Then in each state, using the "permissions" tab, set up the
role to permission mappings appropriate for that state.
Finally, you can extend the workflow with scripts. Scripts can be
External Methods, Python Scripts, DTML methods, or any other callable
Zope object. They are accessible by name in expressions. Scripts
are invoked with a state_change object as the first argument; see
expressions.stx.
Once you've crafted your workflow, you hook it up with a content type
by using the portal_workflow top-level "Workflows" tab. Specify the
workflow name in the target content type's box.
## Script (Python) "checkin"
##parameters=sci
# Check in the object to a Zope version repository, disallowing changes.
object = sci.object
vt = object.portal_versions
if vt.isCheckedOut(object):
vt.checkin(object, sci.kwargs.get('comment', ''))
## Script (Python) "checkout"
##parameters=sci
# Check out the object from a repository, allowing changes.
#
# For workflows that control staging, it makes sense to call this script
# before all transitions.
object = sci.object
vt = object.portal_versions
if not vt.isCheckedOut(object):
vt.checkout(object)
## Script (Python) "retractStages"
##parameters=sci
# Remove the object from the given stages.
object = sci.object
st = object.portal_staging
st.removeStages(object, ['review', 'prod'])
## Script (Python) "updateProductionStage"
##parameters=sci
# Copy the object in development to review and production.
object = sci.object
st = object.portal_staging
st.updateStages(object, 'dev', ['review', 'prod'],
sci.kwargs.get('comment', ''))
## Script (Python) "updateReviewStage"
##parameters=sci
# Copy the object in development to review.
object = sci.object
st = object.portal_staging
st.updateStages(object, 'dev', ['review'],
sci.kwargs.get('comment', ''))
DCWorkflow Expressions
Expressions in DCWorkflow are TALES expressions.
(See the <a href="http://zope.org/Documentation/Books/ZopeBook">Zope Book</a>
for background on page templates and TALES.)
Some of the contexts have slightly different meanings from what is provided
for expressions in page templates.
- 'here' -- The content object
- 'container' -- The content object's container
Several other contexts are also provided.
- 'state_change' -- A special object containing info about the state change
- 'transition' -- The transition object being executed
- 'status' -- The former status
- 'workflow' -- The workflow definition object
- 'scripts' -- The scripts in the workflow definition object
'state_change' objects provide the following attributes:
- 'state_change.status' -- a mapping containing the workflow status.
- 'state_change.object' -- the object being modified by workflow.
- 'state_change.workflow' -- the workflow definition object.
- 'state_change.transition' -- the transition object being executed.
- 'state_change.old_state' -- the former state object.
- 'state_change.new_state' -- the destination state object.
- 'state_change.kwargs' -- the keyword arguments passed to the
doActionFor() method.
- 'state_change.getHistory()' -- returns a copy of the object's workflow
history.
- 'state_change.getPortal()' -- returns the root of the portal.
- 'state_change.ObjectDeleted' and 'ObjectMoved' -- exceptions that
can be raised by scripts to indicate to the workflow that an object
has been moved or deleted.
- 'state_change.getDateTime()' -- returns the DateTime of the transition.
From: John Morton <jwm@plain.co.nz>
Here's how I generally go about putting together a new workflow:
1. Draw up a state diagram with the nodes as states and the arcs as
transitions. I usually do this on paper, then do a good copy in dia
or some other diagram tool, so I have an image to go with the
documentation. I usually spot several corner cases in the process.
2. Start by creating an example DCworkflow, rather than a new one, as
it's faster to delete all the states and transitions than it is to
create all the standard review_state variables.
3. In the permissions tab, select all the permissions that you want the
workflow to govern.
4. Define any extra variables that you need.
5. Set up the states for your workflow, one for each node in your state
diagram. Try to stick to the standard names for a publication
workflow, as some badly behaved products have states like 'published'
hardcoded into their searches (ie CalendarTool, last I looked). Set
up the permissions on the states now, as well. I find that using
acquisition for the site visible states, and using explicit
permissions for the private and interim states works well. Reviewer
roles should either have view permissions on every state or you
should change the appropriate skins to take them somewhere sensible
after a transition or they'll end up with an ugly access denied page
after sending some content back to private state.
6. Set up any scripts that you'll need for transitions - pre and post
transition scripts and ones to handle complex guard conditions. Just
set up skeletons for now, if you haven't though through all the
details.
7. Create your transitions from all the arcs on your state diagram. You
should be able to pick the right destination state as all your states
are already defined, and set up the right scripts to run, as you've
defined those as well. It's worth noting that the guards are or'ed -
if any guard matches, the transition can occur. You can specify more
than one permission, role or expression by separating them with a
semicolon.
That about covers it. By working in this order, you tend to step through
the creation process one tab at a time, rather than switching back and
forth. I find it tends to be faster and less confusing that way.
Worklists are predefined catalog queries.
When defining a variable match, you can enter a list of possible matches
separated by semicolons. In addition, the matches will be formatted
according to the rules described in "actbox.stx":actbox.stx.
This way you can enter "%(user_id)s" to match only the current user.
<script type="text/javascript">
function guardExprDocs() {
window.open('guardExprDocs', '', 'width=640, height=480, resizable, scrollbars, status');
}
</script>
<table>
<tr>
<th align="left">Permission(s)</th>
<td><input type="text" name="guard_permissions" value="&dtml-getPermissionsText;" /></td>
<th align="left">Role(s)</th>
<td><input type="text" name="guard_roles" value="&dtml-getRolesText;" /></td>
<th align="left">Group(s)</th>
<td><input type="text" name="guard_groups" value="&dtml-getGroupsText;" /></td>
</tr>
<tr>
<th align="left">Expression</th>
<td colspan="3">
<input type="text" name="guard_expr" value="&dtml-getExprText;" size="50" />
<a href="#" onclick="guardExprDocs(); return false;">[?]</a>
</td>
</tr>
</table>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="manage_tabs_message request/manage_tabs_message | nothing"
tal:replace="structure here/manage_tabs">Tabs</h2>
<p class="form-help">
When objects are in this state, they will take on the group to role
mappings defined below. Only the <a href="../manage_groups">groups
and roles managed by this workflow</a> are shown.
</p>
<form action="setGroups" method="POST"
tal:define="wf here/getWorkflow; roles wf/getRoles">
<table width="100%" cellspacing="0" cellpadding="2" border="0" nowrap>
<tr class="list-header">
<td align="left">
<div class="form-label">
<strong>Group</strong>
</div>
</td>
<td align="left" tal:attributes="colspan python: len(roles)">
<div class="form-label">
<strong>Roles</strong>
</div>
</td>
</tr>
<tr class="row-normal">
<td></td>
<td tal:repeat="role roles" tal:content="role" class="list-item">
Authenticated
</td>
</tr>
<tr tal:repeat="group wf/getGroups" tal:attributes="class
python: repeat['group'].odd and 'row-normal' or 'row-hilite'">
<td tal:content="group" class="list-item">
(Group) Everyone
</td>
<tal:block tal:define="group_roles python: here.getGroupInfo(group)">
<td tal:repeat="role roles">
<input type="checkbox"
tal:attributes="name python: '%s|%s' % (group, role);
checked python: role in group_roles" />
</td>
</tal:block>
</tr>
</table>
<input class="form-element" type="submit" name="submit" value="Save Changes" />
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
When objects are in this state they will take on the role to permission
mappings defined below. Only the <a href="../manage_permissions">permissions
managed by this workflow</a> are shown.
</p>
<form action="setPermissions" method="POST">
<table width="100%" cellspacing="0" cellpadding="2" border="0" nowrap>
<tr class="list-header">
<td>&nbsp;</td>
<td align="left">
<div class="form-label">
<strong>Permission</strong>
</div>
</td>
<td align="left" colspan="<dtml-var expr="_.len(getAvailableRoles())">">
<div class="form-label">
<strong>Roles</strong>
</div>
</td>
</tr>
<tr class="row-normal">
<td align="left" valign="top">
<div class="form-label">
<strong>
Acquire<BR>permission<BR>settings?
</strong>
</div>
</td>
<td></td>
<dtml-in getAvailableRoles>
<td align="left">
<div class="list-item">
<dtml-var sequence-item>
</div>
</td>
</dtml-in>
</tr>
<dtml-in getManagedPermissions sort>
<dtml-let permission=sequence-item>
<dtml-with expr="getPermissionInfo(permission)" mapping>
<dtml-if sequence-odd>
<tr class="row-normal">
<dtml-else>
<tr class="row-hilite">
</dtml-if>
<td align="left" valign="top">
<dtml-let checked="acquired and 'checked' or ' '">
<input type="checkbox" name="acquire_&dtml-permission;" &dtml-checked; />
</dtml-let>
</td>
<td align="left" nowrap>
<div class="list-item">
&dtml-permission;
</div>
</td>
<dtml-in getAvailableRoles sort>
<td align="center">
<dtml-let checked="_['sequence-item'] in roles and 'checked' or ' '">
<input type="checkbox" name="&dtml-permission;|&dtml-sequence-item;" &dtml-checked; />
</dtml-let>
</td>
</dtml-in>
</tr>
</dtml-with>
</dtml-let>
</dtml-in>
<tr>
<td colspan="<dtml-var expr="_.len(getAvailableRoles())+2">" align="left">
<div class="form-element">
<input class="form-element" type="submit" name="submit" value="Save Changes" />
</div>
</td>
</tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="setProperties" method="POST">
<table>
<tr>
<th align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th align="left">Title</th>
<td><input type="text" name="title" value="&dtml-title;" size="50" /></td>
</tr>
<tr>
<th align="left" valign="top">Description</th>
<td><textarea name="description" rows="6" cols="35">&dtml-description;</textarea></td>
</tr>
<tr>
<th align="left" valign="top">Possible Transitions</th>
<td>
<dtml-in getAvailableTransitionIds sort>
<dtml-let checked="_['sequence-item'] in transitions and 'checked' or ' '">
<input type="checkbox" name="transitions:list"
value="&dtml-sequence-item;" &dtml-checked; /> &dtml-sequence-item;
<dtml-let t_title="getTransitionTitle(_['sequence-item'])">
<dtml-if t_title>(&dtml-t_title;)</dtml-if>
</dtml-let>
</dtml-let>
<br />
<dtml-else>
<em>No transitions defined.</em>
</dtml-in>
</select>
</td>
</tr>
</table>
<input type="submit" name="submit" value="Save changes" />
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
When objects move to this state the workflow variables will be assigned
the values below.
</p>
<dtml-if getVariableValues>
<form action="&dtml-absolute_url;" method="POST">
<table width="100%" cellspacing="0" cellpadding="2" border="0" nowrap>
<tr class="list-header">
<td>&nbsp;</td>
<td align="left" valign="top">
<div class="form-label">
<strong>Variable</strong>
</div>
</td>
<td align="left">
<div class="form-label">
<strong>Value</strong>
</div>
</td>
</tr>
<dtml-in getVariableValues sort>
<dtml-if sequence-odd>
<tr class="row-normal">
<dtml-else>
<tr class="row-hilite">
</dtml-if>
<td align="left" valign="top">
<input type="checkbox" name="ids:list" value="&dtml-sequence-key;"/>
</td>
<td align="left" nowrap>
<div class="list-item">
&dtml-sequence-key;
</div>
</td>
<td align="left">
<input type="text" name="varval_&dtml-sequence-key;" value="&dtml-sequence-item;" size="50" />
</td>
</tr>
</dtml-in>
<tr>
<td colspan="3" align="left">
<div class="form-element">
<input class="form-element" type="submit" name="setVariables:method" value="Save Changes" />
<input class="form-element" type="submit" name="deleteVariables:method" value="Delete" />
</div>
</td>
</tr>
</table>
</form>
</dtml-if>
<form action="addVariable" method="POST">
<table>
<tr>
<td>Add a variable value</td>
</tr>
<tr>
<td>Variable</td>
<td><select name="id">
<dtml-in getWorkflowVariables>
<option value="&dtml-sequence-item;">
<dtml-var sequence-item>
</option>
</dtml-in>
</select>
</td>
</tr>
<tr>
<td>Value</td><td><input type="text" name="value" value="" /></td>
</tr>
<tr><td><input type="submit" name="submit" value="Add" /></td></tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="&dtml-absolute_url;" method="POST">
<table border="0" cellspacing="0" cellpadding="2" width="100%">
<dtml-in values sort=id>
<tr bgcolor="#eeeeee">
<th align="left" colspan="2">
<input type="checkbox" name="ids:list" value="&dtml-id;" />
<dtml-if expr="id == initial_state">*</dtml-if>
<a href="&dtml.url_quote-id;/manage_properties">&dtml-id;</a>
&nbsp;
&dtml-title;
</th>
</tr>
<dtml-let state_id=id>
<dtml-in getTransitions>
<tr>
<td width="10%"></td>
<td>
<a href="../transitions/&dtml-sequence-item;/manage_properties"
>&dtml-sequence-item;</a>
<dtml-let t_title="getTransitionTitle(_['sequence-item'])">
<dtml-if t_title>(&dtml-t_title;)</dtml-if>
</dtml-let>
</td>
</tr>
<dtml-else>
<tr>
<td></td>
<td><em>No transitions.</em></td>
</tr>
</dtml-in>
</dtml-let>
<dtml-if getVariableValues>
<tr>
<th align="right">Variables</th>
<th></th>
</tr>
<dtml-in getVariableValues sort>
<tr>
<td></td>
<td>
&dtml-sequence-key; = &dtml-sequence-item;
</td>
</tr>
</dtml-in>
</dtml-if>
<dtml-else>
<tr><td><em>No states defined.</em></td></tr>
</dtml-in>
</table>
<dtml-if values>
<p>
<b>Note:</b> Renaming a state will not affect any items in that state. You
will need to fix them manually.
</p>
<input type="submit" name="manage_renameForm:method" value="Rename" />
<input type="submit" name="deleteStates:method" value="Delete" />
<input type="submit" name="setInitialState:method" value="Set Initial State" />
</dtml-if>
<hr />
<h3>Add a state</h3>
<p>Id <input type="text" name="id" value="" />
<input type="submit" name="addState:method" value="Add" /></p>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="setProperties" method="POST">
<table>
<tr>
<th align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th align="left">Title</th>
<td><input type="text" name="title" value="&dtml-title;" size="50" /></td>
</tr>
<tr>
<th align="left" valign="top">Description</th>
<td><textarea name="description" rows="6" cols="35">&dtml-description;</textarea></td>
</tr>
<tr>
<th align="left">Destination state</th>
<td>
<select name="new_state_id" size="1">
<dtml-let selected="not new_state_id and 'selected' or ' '">
<option value="" &dtml-selected;>(Remain in state)</option>
</dtml-let>
<dtml-in getAvailableStateIds sort>
<dtml-let selected="new_state_id == _['sequence-item'] and 'selected' or ' '">
<option value="&dtml-sequence-item;" &dtml-selected;>&dtml-sequence-item;</option>
</dtml-let>
</dtml-in>
</select>
</td>
</tr>
<tr>
<th align="left">Trigger type</th>
<td>
<dtml-let checked="trigger_type==0 and 'checked' or ' '">
<input type="radio" name="trigger_type" value="0" &dtml-checked; />
Automatic
</dtml-let>
</td>
</tr>
<tr>
<th></th>
<td>
<dtml-let checked="trigger_type==1 and 'checked' or ' '">
<input type="radio" name="trigger_type" value="1" &dtml-checked; />
Initiated by user action
</dtml-let>
</td>
</tr>
<tr>
<th align="left">Script (before)</th>
<td>
<select name="script_name">
<option value="">(None)</option>
<dtml-in getAvailableScriptIds sort>
<dtml-let selected="script_name == _['sequence-item'] and 'selected' or ' '">
<option value="&dtml-sequence-item;" &dtml-selected;>&dtml-sequence-item;</option>
</dtml-let>
</dtml-in>
</select>
</td>
</tr>
<tr>
<th align="left">Script (after)</th>
<td>
<select name="after_script_name">
<option value="">(None)</option>
<dtml-in getAvailableScriptIds sort>
<dtml-let selected="after_script_name == _['sequence-item'] and 'selected' or ' '">
<option value="&dtml-sequence-item;" &dtml-selected;>&dtml-sequence-item;</option>
</dtml-let>
</dtml-in>
</select>
</td>
</tr>
<tr>
<th align="left" valign="top">Guard</th>
<td>
<dtml-with getGuard>
<dtml-var guardForm>
</dtml-with>
</td>
</tr>
<tr>
<th align="left" valign="top">Display in actions box</th>
<td>
<table>
<tr>
<th align="left">Name (formatted)</th>
<td>
<input type="text" name="actbox_name"
value="&dtml-actbox_name;" size="50" />
</td>
</tr>
<tr>
<th align="left">URL (formatted)</th>
<td>
<input type="text" name="actbox_url"
value="&dtml-actbox_url;" size="50" />
</td>
</tr>
<tr>
<th align="left">Category</th>
<td>
<input type="text" name="actbox_category"
value="&dtml-actbox_category;" />
</td>
</tr>
</table>
</td>
</tr>
</table>
<input type="submit" name="submit" value="Save changes" />
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<p class="form-help">
When the transition is executed, the workflow variables are
updated according to the expressions below.
</p>
<dtml-if getVariableExprs>
<form action="&dtml-absolute_url;" method="POST">
<table width="100%" cellspacing="0" cellpadding="2" border="0" nowrap>
<tr class="list-header">
<td>&nbsp;</td>
<td align="left" valign="top">
<div class="form-label">
<strong>Variable</strong>
</div>
</td>
<td align="left">
<div class="form-label">
<strong>Expression</strong>
</div>
</td>
</tr>
<dtml-in getVariableExprs sort>
<dtml-if sequence-odd>
<tr class="row-normal">
<dtml-else>
<tr class="row-hilite">
</dtml-if>
<td align="left" valign="top">
<input type="checkbox" name="ids:list" value="&dtml-sequence-key;"/>
</td>
<td align="left" nowrap>
<div class="list-item">
&dtml-sequence-key;
</div>
</td>
<td align="left">
<input type="text" name="varexpr_&dtml-sequence-key;" value="&dtml-sequence-item;" size="50" />
</td>
</tr>
</dtml-in>
<tr>
<td colspan="3" align="left">
<div class="form-element">
<input class="form-element" type="submit" name="setVariables:method" value="Save Changes" />
<input class="form-element" type="submit" name="deleteVariables:method" value="Delete" />
</div>
</td>
</tr>
</table>
</form>
</dtml-if>
<form action="addVariable" method="POST">
<table>
<tr>
<td>Add a variable expression</td>
</tr>
<tr>
<td>Variable</td>
<td><select name="id">
<dtml-in getWorkflowVariables>
<option value="&dtml-sequence-item;">
<dtml-var sequence-item>
</option>
</dtml-in>
</select>
</td>
</tr>
<tr>
<td>Expression</td>
<td><input type="text" name="text" size="50" value="" /></td>
</tr>
<tr><td><input type="submit" name="submit" value="Add" /></td></tr>
</table>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="&dtml-absolute_url;" method="POST">
<table border="0" cellspacing="0" cellpadding="2" width="100%">
<dtml-in values sort=id>
<tr bgcolor="#eeeeee">
<th align="left" colspan="2">
<input type="checkbox" name="ids:list" value="&dtml-id;" />
<a href="&dtml.url_quote-id;/manage_properties">&dtml-id;</a>
&nbsp;
&dtml-title;
</th>
</tr>
<tr>
<th width="10%"></th>
<td>
Destination state: <code><dtml-if new_state_id>&dtml-new_state_id;<dtml-else>(Remain in state)</dtml-if></code> <br />
Trigger: <dtml-var expr="(trigger_type == 0 and 'Automatic') or
(trigger_type == 1 and 'User action')">
<br />
<dtml-if script_name>
Script (before): &dtml-script_name;
<br />
</dtml-if>
<dtml-if after_script_name>
Script (after): &dtml-after_script_name;
<br />
</dtml-if>
<dtml-if getGuardSummary><dtml-var getGuardSummary><br /></dtml-if>
<dtml-if actbox_name>Adds to actions box: <code>&dtml-actbox_name;</code></dtml-if>
</td>
</tr>
<dtml-if var_exprs>
<tr>
<th align="right">Variables</th>
<th></th>
</tr>
<dtml-in getVariableExprs sort>
<tr>
<td></td>
<td>
&dtml-sequence-key; = &dtml-sequence-item;
</td>
</tr>
</dtml-in>
</dtml-if>
<dtml-else>
<tr><td><em>No transitions defined.</em></td></tr>
</dtml-in>
</table>
<dtml-if values>
<p>
<b>Note:</b> Renaming a transition will not automatically update all
items in the workflow affected by it. You will need to fix them manually.
</p>
<input type="submit" name="manage_renameForm:method" value="Rename" />
<input type="submit" name="deleteTransitions:method" value="Delete" />
</dtml-if>
<hr />
<h3>Add a transition</h3>
<p>Id <input type="text" name="id" value="" />
<input type="submit" name="addTransition:method" value="Add" /></p>
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="setProperties" method="POST">
<table>
<tr>
<th align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th align="left">Description</th>
<td><input type="text" name="description" value="&dtml-description;"
size="50" /></td>
</tr>
<tr>
<th align="left">
<div class="form-label">
Make available to catalog
</div>
</th>
<td>
<div class="form-element">
<dtml-let checked="for_catalog and 'checked' or ' '">
<input type="checkbox" name="for_catalog" value="1" &dtml-checked; />
</dtml-let>
</div>
</td>
</tr>
<tr>
<th align="left">
<div class="form-label">
Store in workflow status
</div>
</th>
<td>
<div class="form-element">
<dtml-let checked="for_status and 'checked' or ' '">
<input type="checkbox" name="for_status" value="1" &dtml-checked; />
</dtml-let>
</div>
</td>
</tr>
<tr>
<th align="left">
<div class="form-label">
Variable update mode
</div>
</th>
<td>
<div class="form-element">
<select name="update_always">
<dtml-let checked="update_always and ' ' or 'selected'">
<option value="" &dtml-checked;>Update only when the transition or
new state specifies a new value</option>
</dtml-let>
<dtml-let checked="update_always and 'selected' or ' '">
<option value="1" &dtml-checked;>Update on every transition</option>
</dtml-let>
</div>
</td>
</tr>
<tr>
<th align="left">
<div class="form-label">
Default value
</div>
</th>
<td>
<div class="form-element">
<input type="text" name="default_value" value="&dtml-default_value;" />
</div>
</td>
</tr>
<tr>
<th align="left">
<div class="form-label">
Default expression<br />(overrides default value)
</div>
</th>
<td>
<div class="form-element">
<input type="text" name="default_expr" value="&dtml-getDefaultExprText;" size="50" />
</div>
</td>
</tr>
<tr>
<th align="left" valign="top">
<div class="form-label">
Info guard
</div>
</th>
<td>
<dtml-with getInfoGuard>
<dtml-var guardForm>
</dtml-with>
</td>
</tr>
</table>
<input type="submit" name="submit" value="Save changes" />
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="&dtml-absolute_url;" method="POST">
<table border="0" cellspacing="0" cellpadding="2" width="100%">
<dtml-in values sort=id>
<tr bgcolor="#eeeeee">
<th align="left" colspan="2">
<input type="checkbox" name="ids:list" value="&dtml-id;" />
<a href="&dtml.url_quote-id;/manage_properties">&dtml-id;</a>
&nbsp;
&dtml-description;
</th>
</tr>
<tr>
<th width="10%"></th>
<td>
Available to catalog:
<code><dtml-if for_catalog>Yes<dtml-else>No</dtml-if></code><br />
Stored in status:
<code><dtml-if for_status>Yes<dtml-else>No</dtml-if></code><br />
<dtml-if default_value>
Default value: <code>&dtml-default_value;</code><br />
</dtml-if>
<dtml-if getDefaultExprText>
Default expr: <code>&dtml-getDefaultExprText;</code><br />
</dtml-if>
<dtml-if getInfoGuardSummary>
<dtml-var getInfoGuardSummary><br />
</dtml-if>
</td>
</tr>
<dtml-else>
<tr><td><em>No variables defined.</em></td></tr>
</dtml-in>
</table>
<dtml-if values>
<input type="submit" name="manage_renameForm:method" value="Rename" />
<input type="submit" name="deleteVariables:method" value="Delete" />
</dtml-if>
</form>
<hr />
<form action="addVariable" method="POST">
<h3>Add a variable</h3>
<p>Id <input type="text" name="id" value="" />
<input type="submit" name="submit" value="Add" /></p>
</form>
<hr />
<form action="setStateVar" method="POST">
State variable name: <input type="text" name="id" value="&dtml-getStateVar;" />
<input type="submit" name="submit" value="Change" />
<i class="form-help">(Be careful!)</i>
</form>
<dtml-var manage_page_footer>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="manage_tabs_message request/manage_tabs_message | nothing"
tal:replace="structure here/manage_tabs">Tabs</h2>
<table>
<tr>
<td width="50%" valign="top">
<form action="." method="POST" tal:attributes="action here/absolute_url"
tal:define="groups here/getGroups">
<h3>Managed Groups</h3>
<div class="form-help">
This workflow controls access by the selected groups. The mappings
from group to role depend on the workflow state.
</div>
<div tal:repeat="group groups">
<input type="checkbox" name="groups:list" tal:attributes="value group" />
<span tal:replace="group">Everyone</span>
</div>
<div tal:condition="not:groups">
<em>No groups are managed by this workflow.</em>
</div>
<div tal:condition="groups">
<input type="submit" name="delGroups:method" value="Remove" />
</div>
<hr />
<h3>Add a managed group</h3>
<select name="group">
<option tal:repeat="group here/getAvailableGroups"
tal:attributes="value group" tal:content="group" />
</select>
<input type="submit" name="addGroup:method" value="Add" />
</form>
</td>
<td width="50%" style="border-left: 1px solid black; padding-left: 1em;"
valign="top">
<form method="POST" tal:attributes="action here/absolute_url">
<h3>Roles Mapped to Groups</h3>
<div class="form-help">
This workflow maps the following roles to groups. Roles not selected
are managed outside this workflow.
<div tal:define="roles here/getRoles"
tal:repeat="role here/getAvailableRoles">
<input type="checkbox" name="roles:list" tal:attributes="value role;
checked python:role in roles" /><span tal:content="role" />
</div>
</div>
<input type="submit" name="setRoles:method" value="Save Changes" />
</form>
</td>
</tr>
</table>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="&dtml-absolute_url;" method="POST">
<table>
<tr>
<td class="form-help">
The selected permissions are managed by this workflow. The role to permission
mappings for an object in this workflow depend on its state.
</td>
</tr>
<dtml-in permissions sort>
<tr>
<td>
<input type="checkbox" name="ps:list" value="&dtml-sequence-item;">
&dtml-sequence-item;
</td>
</tr>
<dtml-else>
<tr>
<td>
<em>No permissions are managed by this workflow.</em>
</td>
</tr>
</dtml-in>
</table>
<dtml-if permissions>
<input type="submit" name="delManagedPermissions:method" value="Remove selected" />
</dtml-if>
<hr />
<h3>Add a managed permission</h3>
<select name="p">
<dtml-in getPossiblePermissions><dtml-if
expr="_['sequence-item'] not in permissions">
<option value="&dtml-sequence-item;">&dtml-sequence-item;</option>
</dtml-if></dtml-in>
</select>
<input type="submit" name="addManagedPermission:method" value="Add" />
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="setProperties" method="POST">
<table>
<tr>
<th align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th align="left">Title</th>
<td><input type="text" name="title" value="&dtml-title;" size="40" /></td>
</tr>
<tr>
<th align="left">Description</th>
<td>
<textarea cols="45" rows="5" name="description">&dtml-description;</textarea>
</td>
</tr>
<tr>
<th align="left">'Manager' role bypasses guards</th>
<td>
<dtml-let cb="manager_bypass and 'checked=\'checked\'' or ''">
<input type="checkbox" name="manager_bypass" &dtml-cb; />
</dtml-let>
</td>
</tr>
<tr>
<th align="left" valign="top">Instance creation conditions</th>
<td>
<dtml-with getGuard>
<dtml-var guardForm>
</dtml-with>
</td>
</tr>
</table>
<input type="submit" name="submit" value="Save changes" />
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="setProperties" method="POST">
<table>
<tr>
<th align="left">Id</th>
<td>&dtml-id;</td>
</tr>
<tr>
<th align="left">Description</th>
<td>
<input type="text" name="description" value="&dtml-description;" size="50" />
</td>
</tr>
<tr>
<th align="left" valign="top">
<div class="form-label">
Cataloged variable matches (formatted)
</div>
</th>
<td>
<table>
<dtml-in getAvailableCatalogVars>
<tr>
<th align="left">&dtml-sequence-item; =</th>
<td>
<dtml-let value="getVarMatchText(_['sequence-item'])">
<input type="text" name="var_match_&dtml-sequence-item;"
value="&dtml-value;" />
</dtml-let>
</td>
</tr>
</dtml-in>
</table>
</td>
</tr>
<tr>
<th align="left" valign="top">Display in actions box</th>
<td>
<table>
<tr>
<th align="left">Name (formatted)</th>
<td>
<input type="text" name="actbox_name"
value="&dtml-actbox_name;" size="50" />
</td>
</tr>
<tr>
<th align="left">URL (formatted)</th>
<td>
<input type="text" name="actbox_url"
value="&dtml-actbox_url;" size="50" />
</td>
</tr>
<tr>
<th align="left">Category</th>
<td>
<input type="text" name="actbox_category"
value="&dtml-actbox_category;" />
</td>
</tr>
</table>
</td>
</tr>
<tr>
<th align="left" valign="top">
<div class="form-label">
Guard
</div>
</th>
<td>
<dtml-with getGuard>
<dtml-var guardForm>
</dtml-with>
</td>
</tr>
</table>
<input type="submit" name="submit" value="Save changes" />
</form>
<dtml-var manage_page_footer>
<dtml-var manage_page_header>
<dtml-var manage_tabs>
<form action="&dtml-absolute_url;" method="POST">
<table border="0" cellspacing="0" cellpadding="2" width="100%">
<dtml-in values sort=id>
<tr bgcolor="#eeeeee">
<th align="left" colspan="2">
<input type="checkbox" name="ids:list" value="&dtml-id;" />
<a href="&dtml.url_quote-id;/manage_properties">&dtml-id;</a>
&nbsp;
&dtml-description;
</th>
</tr>
<tr>
<th width="10%"></th>
<td>
<dtml-if name_fmt>
Name format: <code>&dtml-name_fmt;</code><br />
</dtml-if>
<dtml-if getVarMatchKeys>
Catalog matches:
<dtml-in getVarMatchKeys sort>
<dtml-let key=sequence-item value="getVarMatch(key)">
<code>&dtml-key;</code> =
<dtml-in value>
<code>&dtml-sequence-item;</code>
<dtml-unless sequence-end>or</dtml-unless>
</dtml-in>
<dtml-unless sequence-end>;</dtml-unless>
</dtml-let>
</dtml-in>
<br />
</dtml-if>
<dtml-if getGuardSummary>
<dtml-var getGuardSummary><br />
</dtml-if>
</td>
</tr>
<dtml-else>
<tr><td><em>No worklists defined.</em></td></tr>
</dtml-in>
</table>
<dtml-if values>
<input type="submit" name="manage_renameForm:method" value="Rename" />
<input type="submit" name="deleteWorklists:method" value="Delete" />
</dtml-if>
</form>
<hr />
<form action="addWorklist" method="POST">
<h3>Add a worklist</h3>
<p>Id <input type="text" name="id" value="" />
<input type="submit" name="submit" value="Add" /></p>
</form>
<dtml-var manage_page_footer>
from zope.interface import implements
from zope.component.interfaces import ObjectEvent
from interfaces import ITransitionEvent, IBeforeTransitionEvent, IAfterTransitionEvent
class TransitionEvent(ObjectEvent):
implements(ITransitionEvent)
def __init__(self, obj, workflow, old_state, new_state, transition, status, kwargs):
ObjectEvent.__init__(self, obj)
self.workflow = workflow
self.old_state = old_state
self.new_state = new_state
self.transition = transition
self.status = status
self.kwargs = kwargs
class BeforeTransitionEvent(TransitionEvent):
implements(IBeforeTransitionEvent)
class AfterTransitionEvent(TransitionEvent):
implements(IAfterTransitionEvent)
\ No newline at end of file
This diff is collapsed.
<configure
xmlns="http://namespaces.zope.org/zope">
<adapter factory=".exportimport.DCWorkflowDefinitionBodyAdapter"/>
</configure>
Overview
DCWorkflows provide workflow objects that are fully customizable via
the Zope Management Interface. You can specify the states, and the
permissions they set on content that is in that state, the transitions
between those states, and other things like variables for things that
aren't well represented by states, work lists for reviewers, and
scripts to embody complex guards and to extend pre and post transition
behaviour.
The process for creating a workflow runs something
like this:
- Draw a state diagram with the nodes (bubbles) as states and the
arcs (arrows) as transitions. Remember to consider all the states
your content can be in, and for each state, consider who should
have permission to access and change the content. Consider what
actions the users will perform to make the transitions between states,
and not only who will be allowed to perform them, but who will be
*required* to perform them.
It's often a good idea to start on paper, then transfer
the diagram to a digital copy using a diagram/flowchart tool, such as
'dia', so that you have an image to go with later documentation. This
process tends to make it easier to spot corner cases before you
actually create the workflow object.
- Start by creating an example DCworkflow, rather than a new one, as it's
faster to delete all the states and transitions than it is to create all the
standard variables that tend to be used by the CMF. [**Note:**
Perhaps we should have a bare dcworkflow, a workflow with standard
variables, and the couple of standard examples.]
- In the permissions tab, select all the permissions that you want the
workflow to govern. These will be dependent on the types of content
you'll be using with the workflow; 'Access contents information',
'Modify portal content', and 'View' are the standard permissions for
the default portal content types.
- Define any extra variables that you need for information that isn't
well represented by states. [**Note:** generic examples? I can think of
a few that could appear in some use cases, but they're all
idiosyncratic of particular publishing needs]
- Set up the states for your workflow, one for each node in your state
diagram. Try to stick to the standard names for a publication workflow, as
some badly behaved products have states like 'published' hardcoded into
their searches (ie CalendarTool, last I looked) [**Note**: Maybe I
should just file some bug reports rather than casting aspersions :-)].
Set up the permissions on the states now, as well, though see the
"State Tab" section for advice.
- Set up any scripts that you will be using in your transitions, such
as pre and post transition scripts and to handle complex guard
conditions. Just set up skeletons for now, if you haven't though
through all the details.
- Create your transitions from all the arcs on your state diagram. You
should be able to pick the right destination state as all your states
are already defined, and set up the right scripts to run, as you've
defined those as well. It's worth noting that the guard behaviour is
such that if any guard matches, the transition can occur. You can
specify more than one permission, role or expression by separating
them with a semicolon.
- Go back to the states tab and, for each state, set the possible
transitions from the list of all available transitions in each state.
- Finally, in the work lists tab, create any work lists for any states
that need them. Work lists are actions that indicate how many objects
of a given state are present, and usually link to some search page
that lists the actual object instances. You typically use them to list
all the pending content waiting for review. Work lists have several
unusual behaviours, however, so check the specific notes in the
"Worklists" section.
By working in this order, you will tend to step through the
creation process one tab at a time, rather than switching back and
forth between them, which tends to be slower and somewhat confusing.
Expressions
Expressions in DCWorkflow are TALES expressions. See
"TALES Overview":/Control_Panel/Products/PageTemplates/Help/tales.stx
for general TALES information. They are used as access guards and for
the setting variable values.
[**Note:** I haven't figured out what all these contexts actually are
and what you can use them for. Explanations are is welcome!]
Some of the contexts have slightly different meanings from what is provided
for expressions in page templates:
'here' -- The content object (rather than the workflow object)
'container' -- The content object's container
Several other contexts are also
provided:
'state_change' -- A special object containing information about the
state change (see below)
'transition' -- The transition object being executed
'status' -- The former status
'workflow' -- The workflow definition object
'scripts' -- The scripts in the workflow definition object
'state_change' objects provide the following attributes, some of which
are duplicates of the above information:
- 'status' is a mapping containing the workflow status. This
includes all the variables defined in the variable tab with "store
in state" checked.
- 'object' is the object being modified by workflow.
(Same as the 'here' variable above.)
- 'workflow' is the workflow definition object. (Same as the
'workflow' variable above.)
- 'transition' is the transition object being executed. (Same
as the 'transition' variable above.)
- 'old_state' is the former state object. The name of the former state,
for example "published", is available as 'old_state.getId()'. (Note
that DCWorkflow defines 'state' and 'status' as different entities;
the name of the current 'state' is stored in the 'status'. The word
clash is unfortunate; patches welcome.)
- 'new_state' is the destination state object. Use 'new_state.getId()'
to access the new state name.
- 'kwargs' is the keyword arguments passed to the doActionFor() method.
- 'getHistory()', a method that returns a copy of the object's workflow
history.
- 'getPortal()', which returns the root of the portal.
- 'ObjectDeleted' and 'ObjectMoved', exceptions that can be raised by
scripts to indicate to the workflow that an object has been moved or
deleted.
- 'getDateTime' is a method that returns the DateTime of the transition.
Guards
Guard settings control access to the transitions, variables or work
lists. Guards can be applied based on permissions, roles, groups or
a TALES expression. These elements are applied sequentially if they
contain values. If one of the conditions in each of the specified
permissions, roles or groups is met the guard element will be
satisfied. For the guard to allow access, all specified element that
contain values must be satisfied.
If no permissions, roles or expressions are specified, access is
automatically granted.
You can supply several options in each field by separating them with
a semicolon.
The context in which the guards evaluate permissions and roles is
obviously important. In the case of transitions and work lists, it
depends on the category. If it's 'worklist', then the context is
that of the content object, and local roles will behave just as
you'd expect. If the category is 'global', then the context will be
the site root, so the local roles between the site root and the
content won't be considered.
[**Note:** What about variables?]
Action Boxes
Action box settings are required for work lists and any transition that
is intended to be a user initiated action. They define how the action
will appear in the action box, what section it will appear in and
what it will link to.
Names and URLs for the actions box can be formatted using standard Python
string formatting. An example::
%(content_url)s/content_submit_form
The string '%(content_url)s' will be replaced by the value of content_url.
The following names are available:
- portal_url
- folder_url
- content_url
- count (Available in work lists only. Represents the number of items in
the work list.)
The following names are also available, in case there is any use for them.
They are not strings.
- portal
- folder
- content
- isAnonymous
Note that this formatting is done using standard Python string formatting
rather than TALES. It might be more appropriate to use TALES instead.
As always, patches welcome.
States Tab
From the states tab it's possible to add new states, and rename and
delete existing states. It is also possible to set a particular state
to be the initial state that new content is set to when created.
The list of existing states also displays each state's title and all
the possible transitions from that state (and their titles). You can go
straight to the details of each state and transition from here.
Within a state's properties tab you can set the title, description,
and the transitions that are possible from this state from a list of
all the available transitions created in the workflow's
transitions tab.
In the state's permissions tab, you can set up the roles to
permissions mappings that will apply to roles when content
managed by this workflow is in this state. It uses the usual cookie
cutter approach as do all other permissions tabs, except that the
only permissions listed are those that have been selected to be
managed by the workflow from the workflow's permissions tab.
A good strategy for managing permissions on each state is to rely on
acquisition for the "published" states, and to drop acquisition and
use explicit permissions on states that are private or interim
publishing states. This way, you can modify the access policy to
"published" content at the site root or for specific folders without
having to modify each workflow's set of "published" states.
[**Note**: The available roles in the permissions tab will be
whatever is acquired from the site root, so I guess creating
roles under sub-folders ought to be discouraged if people want
to use them in workflows]
Reviewer roles should either have view permissions on every
state or you should change the appropriate skins to take them
somewhere sensible after a transition or they'll end up with an ugly
access denied page after sending content back to private state.
In the state's variables tab, you can add, change and delete variables
that you want to assign a value to when objects move into
this state. The available variables are set in the workflow's
variables tab, and the value is a TALES expression (see Expressions
for more details).
Transitions Tab
Transitions are the actions that move content from one state in the
workflow to another. From the transitions tab it's possible to add new
transitions, and rename and delete existing transitions.
The list of existing transitions also displays a summary of each
transition's title, description, destination state, trigger, guards,
and action box entry. You can click through each transition to access
their details.
Within a transition's properties tab you can set the title, and
a collection of properties the define the transtion's behaviour, as
follows:
Destination state -- selected from all the states defined in the
states tab. A transition can remain in state, which is useful for a
reviewer adding comments to the review history, but not taking any
action, updating some variable, or invoking scripts.
Trigger type - There are two types:
- User actions are the familiar user initiated transitions activated
by actions in the action box.
- Automatic transitions are executed any time other workflow
events occur; so if a user action results in the content moving
to a state that has automatic transitions, they will be executed.
(You should use mutually exclusive guards to prevent indeterminate
behavior.)
Scripts - Perform complicated behaviours either before or after the
transition takes place. Scripts of all kinds are defined in the
workflow's scripts tab. Scripts called from here must accept only
one argument; a 'status_change' object. See Expressions for more
details.
Guards and Action boxes -- See the "Guards" and "Action Boxes"
sections for specific details about those fields. Note that
automatic transitions don't need the action box fields to be filled out.
What the action should link to.
In the transition's variables tab, you can add, change and delete
variables that you want to assign a value to, when the transition is
executed. The available variables are set in the workflow's variables
tab, and the value is a TALES expression (see Expressions for more
details).
Variables Tab
Variables are used to handle the state of various workflow related
information that doesn't justify a state of it's own. The default
CMF workflows use variables to track status history comments, and
store the the last transition, who initiated it and when, for example.
From the variables tab it's possible to add new variables, and rename
and delete existing variables.
The list of existing variables also displays a summary of each
variable's description, catalog availability, workflow status, default
value or expression and any access guards. You can click through to
each variable to configure it's details.
In each variable's property tab you can set the variable's
description and a collection of properties the define the variable's
behaviour, as follows:
Make available to catalog -- Just as it says, it makes this variable
available to the catalog for indexing, however it doesn't
automatically create an index for it - you have to create one by
hand that reflects the content of the variable. Once indexed, you can
query the catalog for content that has a particular value in is
variable, and update the variable by workflow actions.
Store in workflow status -- The workflow status is a mapping that
exists in the state_change object that is passed to scripts and
available to expressions.
Variable update mode -- Select whether the variable is updated on
every transition (in which case, you should set a default
expression), or whether it should only update if a transition or
state sets a value.
Default value -- Set the default value to some string.
Default expression -- This is a TALES expression (as described in
Expressions) and overrides the default value.
Guards -- See the "Guards" section.
State variable - stores the name of the variable the current state of
the content is stored in. CMF uses 'review_state' by default, and will
have already created a FieldIndex for it. The state variable is
effectively a variable with "Make available to catalog" set, a default
value of whatever the initial state is and a default expression that
sets to the new state on every transition.
Worklists Tab
Work lists are a way to make people aware of tasks they are required
to perform, usually reviewing content in the publishing context.
Work lists are implemented as a catalog query that puts an action in
the actions box when there are some tasks the member needs to perform.
From the work lists tab it's possible to add new work lists, and rename and
delete existing work lists. The list of existing work lists also
displays a short summary of each work list's description, the catalog query
it uses, and any guard conditions. You can access the details of each
work list by clicking on them.
In each work list's properties tab, you can set the description of
the work list, and it's various behaviour defining properties. The
"Catalog variable matches" field sets the state that is work list
matches. The "variable_name = " text to the left of the text box is
the name of the state variable defined at the bottom of the variables
tab. The values can be set to a number of possible matches separated
by semicolons. [**Note:** CVS feature. There's more in
doc/worklists.stx, but I'm not sure I understand the implications]
The action box fields are discussed in more detail in the Action Box
section. In this case, the url that the work list links to should
probably implement a search page with a catalog query similar to the
"Catalog variable matches", otherwise the difference between the
number of items waiting and the items reported in the search will be
confusing.
[**Note:** What we *really* need from the work list is a way to define
full catalog queries for the action, and a new action box variable
that urlquotes that query so it can be passed straight to the search
page. This way, the work list count and the number of items on the
search page will be the same as they are derived from the same
query string, defined in one place.
Reply from Shane: work lists already exercise the catalog too heavily,
and most people don't understand their purpose. Expanding their
capabilities further could impact performance. I think perhaps
the UI should instead display work lists on a user's home page rather
than the actions box, which would open up new possibilities.]
The guard fields are described in detail in the "Guards"
section. It's probably better to avoid using permission and role
guards, as they're not really necessary - a user will see a
work list action only if they can see content in that particular
state, so the state guards are usually sufficient. In addition, as
the work lists are in the 'global' actions category by default, and
global actions are evaluated in the context of the site root, local
roles like Owner or locally set Reviewer roles, and the permissions
they grant, will not apply. [*Note:* Does anyone know a good reason
why work lists appear in the global box rather than in the workflow
box? This particular problem should vanish if they are moved there.]
Whether a work list action appears in the action box, and the
number of items in the work list depends on several factors:
- The state that the work list is generated for
- The name of the state variable used to indicate the
current state of an object
- Whether the user can view content which is in that
state
This has some unexpected consequences:
- If you have several workflow that use the same state variable,
and similar state names, and each has a work list on, say, the
'pending' state, then both work lists will appear in the action box,
and the number of items in each will be the total of all the content
in a 'pending' state, regardless of which workflow manages that
content (except that if the work list action entries are exactly the
same text, the action tool will filter out the duplicate).
- If each workflow manages the permissions on content in the
'pending' state differently, by, say, using two different reviewer
roles, then users who have one role and not the other will
see a single work list entry with the right number of items, but
users with both roles will see the same as above.
So there are a few tricks to getting the work lists to do the kinds of
things we want.
If you have several similar workflows, such as a standard one, and a
couple of specialized ones for particular content, and you want to
have one reviewer role for the lot, then you should set up just one
work list in the standard workflow for the states that need them, and
leave the other workflow to rely on that work list.
If you have a workflow that uses a different reviewer role than
other workflows, and consequently, you want it to have it's own
separate work lists, you have two choices. One is to use state names
that are unique to each workflow, while the second is
to use state variable name that is unique each workflow. The
second option is obviously a lot easier, however, if you change the
name of the state variable when there exists content that is using
this workflow, they will immediately loose there workflow state
and default to the initial state. In addition, you'll need to add
a field index for the new state variable name in the portal_catalog
tool, by hand.
[Note: In the first instance, we could add an action box name field
to each state so that nicely formated names appear in the action
box for things like "Published (yet to be effective)" rather than
"published_not_yet_effective", and so we can lie about the names
to make them unique, so that "foo_workflow_pending" looks like
"Pending". In the second instance, I see no reason why the state
variable name change action shouldn't migrate the value of the old
state variable to the new for all the content managed by this
workflow, and it could probably automatically add indexes for
new state variable names if they don't already exists (and
perhaps remove indexes for state variable names not used elsewhere).
While we're thinking about ways to make sweeping workflow changes
less painful, there are a couple of changes that could be made
to the code that changes content type to workflow mappings: if a
content to workflow mapping has changed, then, for each instance of
that content type, attempt to keep the state variable the same
unless that state doesn't exist in the new workflow, then evaluate
any automatic transitions on that state. This way it's possible
to migrate between workflows by ensuring that states with the same
name have the same semantics, or if they don't exists in the new
workflow, we can create placeholder states with an automatic
transition to the state we want to be in.
]
Scripts Tab
Scripts are used to extend the workflow in various ways. Scripts can
be External Methods, Python Scripts, DTML methods, or any other callable
Zope object. They are accessible by name in expressions, eg::
scripts/myScript
or::
python:scripts.myScript(arg1, arg2...)
From transitions, as before and after scripts, they are invoked with a
'state_change' object as the first argument; see the Expressions section
for more details on the 'state_change' object.
Objects under the scripts are managed in the usual ZMI fashion.
Permissions Tab
You can manage all of the actions a user can perform on an object by
setting up permissions to be managed by the workflow under the
permissions tab. Here, you can select which permissions should be
state-dependent from a list of all available permissions, and you
can delete previously selected permissions. In each state, use
it's permissions tab to set up the role to permission mappings
appropriate for that state.
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""DCWorkflow interfaces.
$Id$
"""
from zope.interface import Interface, Attribute
from zope.component.interfaces import IObjectEvent
class IDCWorkflowDefinition(Interface):
"""Web-configurable workflow definition.
"""
class ITransitionEvent(IObjectEvent):
"""An event that's fired upon a workflow transition.
"""
workflow = Attribute(u"The workflow definition triggering the transition")
old_state = Attribute(u"The state definition of the workflow state before the transition")
new_state = Attribute(u"The state definition of the workflow state before after transition")
transition = Attribute(u"The transition definition taking place. "
"May be None if this is the 'transition' to the initial state.")
status = Attribute(u"The status dict of the object.")
kwargs = Attribute(u"Any keyword arguments passed to doActionFor() when the transition was invoked")
class IBeforeTransitionEvent(ITransitionEvent):
"""An event fired before a workflow transition.
"""
class IAfterTransitionEvent(ITransitionEvent):
"""An event that's fired after a workflow transition.
"""
""" DCWorkflow product permissions
$Id$
"""
from AccessControl import ModuleSecurityInfo
security = ModuleSecurityInfo('Products.DCWorkflow.permissions')
security.declarePublic('AccessContentsInformation')
from Products.CMFCore.permissions import AccessContentsInformation
security.declarePublic('ManagePortal')
from Products.CMFCore.permissions import ManagePortal
security.declarePublic('ModifyPortalContent')
from Products.CMFCore.permissions import ModifyPortalContent
security.declarePublic('RequestReview')
from Products.CMFCore.permissions import RequestReview
security.declarePublic('ReviewPortalContent')
from Products.CMFCore.permissions import ReviewPortalContent
security.declarePublic('View')
from Products.CMFCore.permissions import View
<?xml version="1.0"?>
<object name="portal_workflow" meta_type="CMF Workflow Tool">
<object name="default_workflow" meta_type="Workflow"/>
</object>
<?xml version="1.0"?>
<dc-workflow workflow_id="default_workflow"
title="CMF default workflow [Revision 2]"
state_variable="review_state"
initial_state="visible">
<permission>Access contents information</permission>
<permission>Modify portal content</permission>
<permission>View</permission>
<state state_id="pending" title="Waiting for reviewer">
<exit-transition transition_id="hide"/>
<exit-transition transition_id="publish"/>
<exit-transition transition_id="reject"/>
<exit-transition transition_id="retract"/>
<permission-map name="Access contents information"
acquired="True">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reviewer</permission-role>
</permission-map>
<permission-map name="Modify portal content"
acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
</permission-map>
<permission-map name="View" acquired="True">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reviewer</permission-role>
</permission-map>
</state>
<state state_id="private"
title="Visible and editable only by owner">
<exit-transition transition_id="show"/>
<permission-map name="Access contents information"
acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="Modify portal content"
acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
</state>
<state state_id="published" title="Public">
<exit-transition transition_id="reject"/>
<exit-transition transition_id="retract"/>
<permission-map name="Access contents information"
acquired="True">
<permission-role>Anonymous</permission-role>
<permission-role>Manager</permission-role>
</permission-map>
<permission-map name="Modify portal content"
acquired="False">
<permission-role>Manager</permission-role>
</permission-map>
<permission-map name="View" acquired="True">
<permission-role>Anonymous</permission-role>
<permission-role>Manager</permission-role>
</permission-map>
</state>
<state state_id="visible" title="Visible but not published">
<exit-transition transition_id="hide"/>
<exit-transition transition_id="publish"/>
<exit-transition transition_id="submit"/>
<permission-map name="Access contents information"
acquired="True">
<permission-role>Anonymous</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
</permission-map>
<permission-map name="Modify portal content"
acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="View" acquired="True">
<permission-role>Anonymous</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
</permission-map>
</state>
<transition transition_id="hide"
title="Member makes content private"
new_state="private" trigger="USER"
before_script="" after_script="">
<action url="%(content_url)s/content_hide_form"
category="workflow">Make private</action>
<guard>
<guard-role>Owner</guard-role>
</guard>
</transition>
<transition transition_id="publish"
title="Reviewer publishes content"
new_state="published" trigger="USER"
before_script="" after_script="">
<action url="%(object_url)s/content_publish_form"
category="workflow">Publish</action>
<guard>
<guard-permission>Review portal content</guard-permission>
</guard>
</transition>
<transition transition_id="reject"
title="Reviewer rejects submission"
new_state="visible" trigger="USER"
before_script="" after_script="">
<action url="%(object_url)s/content_reject_form"
category="workflow">Reject</action>
<guard>
<guard-permission>Review portal content</guard-permission>
</guard>
</transition>
<transition transition_id="retract"
title="Member retracts submission"
new_state="visible" trigger="USER"
before_script="" after_script="">
<action url="%(object_url)s/content_retract_form"
category="workflow">Retract</action>
<guard>
<guard-permission>Request review</guard-permission>
</guard>
</transition>
<transition transition_id="show"
title="Member makes content visible"
new_state="visible" trigger="USER"
before_script="" after_script="">
<action url="%(content_url)s/content_show_form"
category="workflow">Make visible</action>
<guard>
<guard-role>Owner</guard-role>
</guard>
</transition>
<transition transition_id="submit"
title="Member requests publishing"
new_state="pending" trigger="USER"
before_script="" after_script="">
<action url="%(object_url)s/content_submit_form"
category="workflow">Submit</action>
<guard>
<guard-permission>Request review</guard-permission>
</guard>
</transition>
<worklist worklist_id="reviewer_queue" title="">
<description>Reviewer tasks</description>
<action url="%(portal_url)s/search?review_state=pending"
category="global">Pending (%(count)d)</action>
<guard>
<guard-permission>Review portal content</guard-permission>
</guard>
<match name="review_state" values="pending"/>
</worklist>
<variable variable_id="action" for_catalog="False"
for_status="True" update_always="True">
<description>The last transition</description>
<default>
<expression>transition/getId|nothing</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="actor" for_catalog="False"
for_status="True" update_always="True">
<description>The ID of the user who performed the last transition</description>
<default>
<expression>user/getId</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="comments" for_catalog="False"
for_status="True" update_always="True">
<description>Comments about the last transition</description>
<default>
<expression>python:state_change.kwargs.get('comment', '')</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="review_history" for_catalog="False"
for_status="False" update_always="False">
<description>Provides access to workflow history</description>
<default>
<expression>state_change/getHistory</expression>
</default>
<guard>
<guard-permission>Request review</guard-permission>
<guard-permission>Review portal content</guard-permission>
</guard>
</variable>
<variable variable_id="time" for_catalog="False"
for_status="True" update_always="True">
<description>Time of the last transition</description>
<default>
<expression>state_change/getDateTime</expression>
</default>
<guard>
</guard>
</variable>
</dc-workflow>
##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Unit test layers.
$Id$
"""
from Products.Five import zcml
from zope.testing.cleanup import cleanUp
from Products.CMFCore.testing import _DUMMY_ZCML
class ExportImportZCMLLayer:
@classmethod
def setUp(cls):
import Products.Five
import Products.GenericSetup
import Products.CMFCore
import Products.CMFCore.exportimport
import Products.DCWorkflow
zcml.load_config('meta.zcml', Products.Five)
zcml.load_config('meta.zcml', Products.GenericSetup)
zcml.load_config('configure.zcml', Products.Five)
zcml.load_config('configure.zcml', Products.GenericSetup)
zcml.load_config('tool.zcml', Products.CMFCore)
zcml.load_config('configure.zcml', Products.CMFCore.exportimport)
zcml.load_config('tool.zcml', Products.DCWorkflow)
zcml.load_config('exportimport.zcml', Products.DCWorkflow)
zcml.load_string(_DUMMY_ZCML)
@classmethod
def tearDown(cls):
cleanUp()
# Derive from ZopeLite layer if available
try:
from Testing.ZopeTestCase.layer import ZopeLite
except ImportError:
pass # Zope < 2.11
else:
ExportImportZCMLLayer.__bases__ = (ZopeLite,)
"""\
Unit test package for DCWorkflow.
As test suites are added, they should be added to the
mega-test-suite in Products.DCWorkflow.tests.test_all.py
"""
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Unit tests for DCWorkflow module.
$Id$
"""
import unittest
import Testing
from zope.component import adapter
from zope.component import provideHandler
from zope.interface.verify import verifyClass
from Products.CMFCore.testing import TraversingEventZCMLLayer
from Products.CMFCore.tests.base.dummy import DummyContent
from Products.CMFCore.tests.base.dummy import DummySite
from Products.CMFCore.tests.base.dummy import DummyTool
from Products.CMFCore.WorkflowTool import WorkflowTool
from Products.DCWorkflow.interfaces import IAfterTransitionEvent
from Products.DCWorkflow.interfaces import IBeforeTransitionEvent
class DCWorkflowDefinitionTests(unittest.TestCase):
layer = TraversingEventZCMLLayer
def setUp(self):
self.site = DummySite('site')
self.site._setObject( 'portal_types', DummyTool() )
self.site._setObject( 'portal_workflow', WorkflowTool() )
self._constructDummyWorkflow()
def test_interfaces(self):
from Products.CMFCore.interfaces import IWorkflowDefinition
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
verifyClass(IWorkflowDefinition, DCWorkflowDefinition)
def _constructDummyWorkflow(self):
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
wftool = self.site.portal_workflow
wftool._setObject('wf', DCWorkflowDefinition('wf'))
wftool.setDefaultChain('wf')
wf = wftool.wf
wf.states.addState('private')
sdef = wf.states['private']
sdef.setProperties( transitions=('publish',) )
wf.states.addState('published')
wf.states.setInitialState('private')
wf.transitions.addTransition('publish')
tdef = wf.transitions['publish']
tdef.setProperties(title='', new_state_id='published', actbox_name='')
wf.variables.addVariable('comments')
vdef = wf.variables['comments']
vdef.setProperties(description='',
default_expr="python:state_change.kwargs.get('comment', '')",
for_status=1, update_always=1)
def _getDummyWorkflow(self):
wftool = self.site.portal_workflow
return wftool.wf
def test_doActionFor(self):
wftool = self.site.portal_workflow
wf = self._getDummyWorkflow()
dummy = self.site._setObject( 'dummy', DummyContent() )
wftool.notifyCreated(dummy)
self.assertEqual( wf._getStatusOf(dummy),
{'state': 'private', 'comments': ''} )
wf.doActionFor(dummy, 'publish', comment='foo' )
self.assertEqual( wf._getStatusOf(dummy),
{'state': 'published', 'comments': 'foo'} )
# XXX more
def test_events(self):
events = []
@adapter(IBeforeTransitionEvent)
def _handleBefore(event):
events.append(event)
provideHandler(_handleBefore)
@adapter(IAfterTransitionEvent)
def _handleAfter(event):
events.append(event)
provideHandler(_handleAfter)
wftool = self.site.portal_workflow
wf = self._getDummyWorkflow()
dummy = self.site._setObject( 'dummy', DummyContent() )
wftool.notifyCreated(dummy)
wf.doActionFor(dummy, 'publish', comment='foo', test='bar')
self.assertEquals(4, len(events))
evt = events[0]
self.failUnless(IBeforeTransitionEvent.providedBy(evt))
self.assertEquals(dummy, evt.object)
self.assertEquals('private', evt.old_state.id)
self.assertEquals('private', evt.new_state.id)
self.assertEquals(None, evt.transition)
self.assertEquals({}, evt.status)
self.assertEquals(None, evt.kwargs)
evt = events[1]
self.failUnless(IAfterTransitionEvent.providedBy(evt))
self.assertEquals(dummy, evt.object)
self.assertEquals('private', evt.old_state.id)
self.assertEquals('private', evt.new_state.id)
self.assertEquals(None, evt.transition)
self.assertEquals({'state': 'private', 'comments': ''}, evt.status)
self.assertEquals(None, evt.kwargs)
evt = events[2]
self.failUnless(IBeforeTransitionEvent.providedBy(evt))
self.assertEquals(dummy, evt.object)
self.assertEquals('private', evt.old_state.id)
self.assertEquals('published', evt.new_state.id)
self.assertEquals('publish', evt.transition.id)
self.assertEquals({'state': 'private', 'comments': ''}, evt.status)
self.assertEquals({'test' : 'bar', 'comment' : 'foo'}, evt.kwargs)
evt = events[3]
self.failUnless(IAfterTransitionEvent.providedBy(evt))
self.assertEquals(dummy, evt.object)
self.assertEquals('private', evt.old_state.id)
self.assertEquals('published', evt.new_state.id)
self.assertEquals('publish', evt.transition.id)
self.assertEquals({'state': 'published', 'comments': 'foo'}, evt.status)
self.assertEquals({'test' : 'bar', 'comment' : 'foo'}, evt.kwargs)
def test_checkTransitionGuard(self):
wftool = self.site.portal_workflow
wf = self._getDummyWorkflow()
dummy = self.site._setObject( 'dummy', DummyContent() )
wftool.notifyCreated(dummy)
self.assertEqual( wf._getStatusOf(dummy),
{'state': 'private', 'comments': ''} )
# Check
self.assert_(wf._checkTransitionGuard(wf.transitions['publish'],
dummy))
# Check with kwargs propagation
self.assert_(wf._checkTransitionGuard(wf.transitions['publish'],
dummy, arg1=1, arg2=2))
def test_isActionSupported(self):
wf = self._getDummyWorkflow()
dummy = self.site._setObject( 'dummy', DummyContent() )
# check publish
self.assert_(wf.isActionSupported(dummy, 'publish'))
# Check with kwargs.
self.assert_(wf.isActionSupported(dummy, 'publish', arg1=1, arg2=2))
# XXX more tests...
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(DCWorkflowDefinitionTests),
))
if __name__ == '__main__':
from Products.CMFCore.testing import run
run(test_suite())
This diff is collapsed.
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Unit tests of role-mapping machinery.
$Id$
"""
import unittest
import Testing
from OFS.Folder import Folder
from OFS.Application import Application
from Products.DCWorkflow.utils \
import modifyRolesForPermission, modifyRolesForGroup
class RoleMapTests(unittest.TestCase):
def setUp(self):
self.app = Application()
self.app.ob = Folder()
self.ob = self.app.ob
self.ob.__ac_local_roles__ = {
'(Group) Administrators': ['Manager', 'Member'],
'(Group) Users': ['Member'],
}
self.ob._View_Permission = ('Member', 'Manager')
self.ob._View_management_screens_Permission = ('Manager',)
def testModifyRolesForGroup(self):
modifyRolesForGroup(
self.ob, '(Group) Administrators', ['Owner'], ['Member', 'Owner'])
modifyRolesForGroup(
self.ob, '(Group) Users', [], ['Member'])
self.assertEqual(self.ob.__ac_local_roles__, {
'(Group) Administrators': ['Manager', 'Owner'],
})
modifyRolesForGroup(
self.ob, '(Group) Administrators', ['Member'], ['Member', 'Owner'])
modifyRolesForGroup(
self.ob, '(Group) Users', ['Member'], ['Member'])
self.assertEqual(self.ob.__ac_local_roles__, {
'(Group) Administrators': ['Manager', 'Member'],
'(Group) Users': ['Member'],
})
def testModifyRolesForPermission(self):
modifyRolesForPermission(self.ob, 'View', ['Manager'])
modifyRolesForPermission(
self.ob, 'View management screens', ['Member'])
self.assertEqual(self.ob._View_Permission, ['Manager'])
self.assertEqual(
self.ob._View_management_screens_Permission, ['Member'])
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(RoleMapTests),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<five:registerClass
class=".DCWorkflow.DCWorkflowDefinition"
meta_type="Workflow"
addview="addDCWorkflowDefinition.html"
permission="cmf.ManagePortal"
global="False"
/>
</configure>
This diff is collapsed.
2.2.0dev
\ No newline at end of file
This diff is collapsed.
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
(See Products/DCWorkflow/README.txt).
import os
from setuptools import setup
from setuptools import find_packages
NAME = 'DCWorkflow'
here = os.path.abspath(os.path.dirname(__file__))
package = os.path.join(here, 'Products', NAME)
def _package_doc(name):
f = open(os.path.join(package, name))
return f.read()
VERSION = _package_doc('version.txt').strip()
if VERSION.startswith(NAME):
VERSION = VERSION[len(NAME):]
while VERSION and VERSION[0] in '-_.':
VERSION = VERSION[1:]
_boundary = '\n' + ('-' * 60) + '\n'
README = (open(os.path.join(here, 'README.txt')).read()
+ _boundary + _package_doc('README.txt')
)
setup(name='Products.%s' % NAME,
version=VERSION,
description='DCWorkflow product for the Zope Content Management Framework',
long_description=README,
classifiers=[
"Development Status :: 5 - Production/Stable",
"Framework :: Plone",
"Framework :: Zope2",
"Intended Audience :: Developers",
"License :: OSI Approved :: Zope Public License",
"Programming Language :: Python",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries :: Application Frameworks",
],
keywords='web application server zope zope2 cmf',
author="Zope Corporation and contributors",
author_email="zope-cmf@lists.zope.org",
url="http://www.zope.org/Products/CMF",
license="ZPL 2.1 (http://www.zope.org/Resources/License/ZPL-2.1)",
packages=find_packages(),
include_package_data=True,
namespace_packages=['Products'],
zip_safe=False,
install_requires=[
'setuptools',
],
entry_points="""
[zope2.initialize]
Products.%s = Products.%s:initialize
""" % (NAME, NAME),
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment