Commit 37c3c7b6 authored by wenjie.zheng's avatar wenjie.zheng

Transition.py: Now generate workflow history for both Action and Workflow...

Transition.py: Now generate workflow history for both Action and Workflow Method, add Guard Object in real time, improve state getters.
parent 2973fc8a
##############################################################################
#
# Copyright (c) 2015 Nexedi SARL and Contributors. All Rights Reserved.
# 2015 Wenjie ZHENG <wenjie.zheng@tiolive.com>
# 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 erp5-configurable workflow.
"""
from cgi import escape
from AccessControl.SecurityInfo import ClassSecurityInfo
from Acquisition import Explicit
from Acquisition import aq_base
from App.class_init import InitializeClass
from App.special_dtml import DTMLFile
from Persistence import Persistent
from Products.CMFCore.Expression import Expression
from Products.CMFCore.utils import _checkPermission
from Products.DCWorkflow.Expression import StateChangeInfo
from Products.DCWorkflow.Expression import createExprContext
from Products.DCWorkflow.permissions import ManagePortal
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type import Permissions, PropertySheet
class Guard(XMLObject):
permissions = ()
roles = ()
groups = ()
expr = None
meta_type = 'ERP5 Workflow'
portal_type = 'Guard'
add_permission = Permissions.AddPortalContent
isPortalContent = 1
isRADContent = 1
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = (
PropertySheet.Base,
PropertySheet.XMLObject,
PropertySheet.CategoryCore,
PropertySheet.DublinCore,
PropertySheet.Guard,
)
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)
......@@ -38,6 +38,12 @@ from Products.ERP5Type.Utils import convertToUpperCase, convertToMixedCase
from Products.DCWorkflow.DCWorkflow import ObjectDeleted, ObjectMoved
from copy import deepcopy
from Products.ERP5Type.patches.WorkflowTool import WorkflowHistoryList
#from Products.ERP5Workflow.Document.Guard import Guard
from Products.DCWorkflow.Guard import Guard
TRIGGER_AUTOMATIC = 0
TRIGGER_USER_ACTION = 1
TRIGGER_WORKFLOW_METHOD = 2
class Transition(XMLObject):
"""
......@@ -49,6 +55,13 @@ class Transition(XMLObject):
add_permission = Permissions.AddPortalContent
isPortalContent = 1
isRADContent = 1
trigger_type = TRIGGER_USER_ACTION #zwj: type is int 0, 1, 2
guard = None
actbox_name = ''
actbox_url = ''
actbox_icon = ''
actbox_category = 'workflow'
var_exprs = None # A mapping.
# Declarative security
security = ClassSecurityInfo()
......@@ -63,6 +76,47 @@ class Transition(XMLObject):
PropertySheet.Transition,
)
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:
self.generateGuard()
return self.guard ### only generate gurad when self is a User Action
#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 generateGuard(self):
if self.trigger_type == TRIGGER_USER_ACTION:
if self.guard == None:
self.guard = Guard(permissions=self.getPermissionList(),
roles=self.getRoleList(),
groups=self.getGroupList(),
expr=self.getExpression())
if self.guard.roles != self.getRoleList():
self.guard.roles = self.getRoleList()
elif self.guard.permissions != self.getPermissionList():
self.guard.permissions = self.getPermissionList()
elif self.guard.groups != self.getGroupList():
self.guard.groups = self.getGroupList()
elif self.guard.expr != self.getExpression():
self.guard.expr = self.getExpression()
def execute(self, document, form_kw=None):
"""
Execute transition.
......@@ -76,16 +130,22 @@ class Transition(XMLObject):
if form_kw is None:
form_kw = {}
workflow = self.getParentValue()
# Get variable values
### get related history
state_bc_id = workflow.getStateBaseCategory()
status_dict = workflow.getCurrentStatusDict(document)
state_object = workflow._getOb(status_dict[state_bc_id])
state_object = workflow._getOb(status_dict[state_bc_id], None)
if state_object == None:
state_object = workflow.getSourceValue()
old_state = state_object.getId()
new_state = document.unrestrictedTraverse(self.getDestination()).getId()
old_sdef = state_object
new_state = self.getDestinationId()
if new_state is None:
new_state = document.unrestrictedTraverse(workflow.getSource()).getId()
new_state = workflow.getSourceId()
if not new_state:
# Do nothing if there is no initial state. We may want to create
# workflows with no state at all, only for worklists.
......@@ -93,9 +153,9 @@ class Transition(XMLObject):
former_status = {}
else:
former_status = state_object.getId()
old_sdef = state_object
try:
new_sdef = document.unrestrictedTraverse(self.getDestination())
new_sdef = self.getDestinationValue()
except KeyError:
raise WorkflowException('Destination state undefined: ' + new_state)
......@@ -130,10 +190,15 @@ class Transition(XMLObject):
if validation_exc :
# reraise validation failed exception
raise validation_exc, None, validation_exc_traceback
return new_sdef
return old_state
# update state
self._changeState(document)
state = self.getDestination()
if state is None:
state = old_sdef
state_bc_id = self.getParentValue().getStateBaseCategory()
document.setCategoryMembership(state_bc_id, state)
### zwj: update Role mapping, also in Workflow, initialiseDocument()
self.getParent().updateRoleMappingsFor(document)
......@@ -141,7 +206,6 @@ class Transition(XMLObject):
status_dict['action'] = self.getId()
# Modify workflow history
#status_dict[state_bc_id] = document.getCategoryMembershipList(state_bc_id)[0]
status_dict[state_bc_id] = new_state
object = workflow.getStateChangeInformation(document, state_object, transition=self)
......@@ -163,21 +227,25 @@ class Transition(XMLObject):
for variable in self.contentValues(portal_type='Transition Variable'):
status_dict[variable.getCausalityTitle()] = variable.getInitialValue(object=object)
# Generate Workflow History List
self.setStatusOf(workflow.getId(), document, status_dict)
# Execute the "after" script.
script_id = self.getAfterScriptId()
if script_id is not None:
kwargs = form_kw
# Script can be either script or workflow method
if script_id in old_sdef.getDestinationTitleList():
if script_id in old_sdef.getDestinationIdList():
getattr(workflow, convertToMixedCase(script_id)).execute(document)
else:
script = self.getParent()._getOb(script_id)
# Pass lots of info to the script in a single parameter.
sci = StateChangeInfo(
document, workflow, former_status, self, old_sdef, new_sdef, kwargs)
script.execute(sci) # May throw an exception.
if script.getTypeInfo().getId() == 'Workflow Script':
sci = StateChangeInfo(
document, workflow, former_status, self, old_sdef, new_sdef, kwargs)
script.execute(sci) # May throw an exception.
else:
raise NotImplementedError ('Unsupported Script %s for state %s'%(script_id, old_sdef.getId()))
# Return the new state object.
if moved_exc is not None:
# Propagate the notification that the object has moved.
......@@ -185,16 +253,6 @@ class Transition(XMLObject):
else:
return new_sdef
def _changeState(self, document):
"""
Change the state of the object.
"""
state = self.getDestination()
if state is not None:
# Some transitions don't update the state
state_bc_id = self.getParentValue().getStateBaseCategory()
document.setCategoryMembership(state_bc_id, state)
def _checkPermission(self, document):
"""
Check if transition is allowed.
......@@ -227,6 +285,6 @@ class Transition(XMLObject):
if wfh is None:
wfh = WorkflowHistoryList()
if not has_history:
ob.workflow_history = PersistentMapping()
ob.workflow_history = PersistentMapping()
ob.workflow_history[wf_id] = wfh
wfh.append(status)
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