diff --git a/fields/workflow.py b/fields/workflow.py
index ce9922d..4de2e3c 100644
--- a/fields/workflow.py
+++ b/fields/workflow.py
@@ -331,7 +331,7 @@ class Transition:
return msg
def trigger(self, transitionName, obj, wf, comment, doAction=True,
- doNotify=True, doHistory=True, doSay=True):
+ doNotify=True, doHistory=True, doSay=True, reindex=True):
'''This method triggers this transition on p_obj. The transition is
supposed to be triggerable (call to self.isTriggerable must have been
performed before calling this method). If p_doAction is False, the
@@ -341,7 +341,9 @@ class Transition:
transition has been triggered will not be launched. If p_doHistory is
False, there will be no trace from this transition triggering in the
workflow history. If p_doSay is False, we consider the transition is
- trigger programmatically, and no message is returned to the user.'''
+ trigger programmatically, and no message is returned to the user.
+ If p_reindex is False, object reindexing will be performed by the
+ calling method.'''
# Create the workflow_history dict if it does not exist.
if not hasattr(obj.aq_base, 'workflow_history'):
from persistent.mapping import PersistentMapping
@@ -368,12 +370,12 @@ class Transition:
if not doHistory: comment = '_invisible_'
obj.addHistoryEvent(action, review_state=targetStateName,
comments=comment)
- # Reindex the object if required. Not only security-related indexes
- # (Allowed, State) need to be updated here.
- if not obj.isTemporary(): obj.reindex()
# Execute the related action if needed
msg = ''
if doAction and self.action: msg = self.executeAction(obj, wf)
+ # Reindex the object if required. Not only security-related indexes
+ # (Allowed, State) need to be updated here.
+ if reindex and not obj.isTemporary(): obj.reindex()
# Send notifications if needed
if doNotify and self.notify and obj.getTool(True).mailEnabled:
sendNotification(obj.appy(), self, transitionName, wf)
@@ -382,10 +384,21 @@ class Transition:
if not msg: msg = obj.translate('object_saved')
obj.say(msg)
+ def onUiRequest(self, obj, wf, name, rq):
+ '''Executed when a user wants to trigger this transition from the UI.'''
+ tool = obj.getTool()
+ # Is this transition triggerable?
+ if not self.isTriggerable(obj, wf):
+ raise Exception('Transition "%s" can\'t be triggered.' % name)
+ # Trigger the transition
+ self.trigger(name, obj, wf, rq.get('comment', ''), reindex=False)
+ # Reindex obj if required.
+ if not obj.isTemporary(): obj.reindex()
+ return tool.goto(obj.getUrl(rq['HTTP_REFERER']))
+
class UiTransition:
'''Represents a widget that displays a transition.'''
-
- pxView = Px('''
+ pxView = Px('''
- ''')
+ value=":label" title=":transition.reason"/>''')
def __init__(self, name, transition, obj, mayTrigger, ):
self.name = name
@@ -419,7 +431,7 @@ class UiTransition:
if not mayTrigger:
self.mayTrigger = False
self.reason = mayTrigger.msg
- # Require by the UiGroup.
+ # Required by the UiGroup.
self.colspan = 1
# ------------------------------------------------------------------------------
diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py
index 90d2aec..8752910 100644
--- a/gen/mixins/__init__.py
+++ b/gen/mixins/__init__.py
@@ -663,16 +663,6 @@ class BaseMixin:
self.REQUEST.set(field.name, '')
return self.edit()
- def showField(self, name, layoutType='view'):
- '''Must I show field named p_name on this p_layoutType ?'''
- return self.getAppyType(name).isShowable(self, layoutType)
-
- def getMethod(self, methodName):
- '''Returns the method named p_methodName.'''
- # If I write "self.aq_base" instead of self, acquisition will be
- # broken on returned object.
- return getattr(self, methodName, None)
-
def getCreateFolder(self):
'''When an object must be created from this one through a Ref field, we
must know where to put the newly create object: within this one if it
@@ -847,55 +837,6 @@ class BaseMixin:
return klass.styles[elem]
return elem
- def getTransitions(self, includeFake=True, includeNotShowable=False,
- grouped=True):
- '''This method returns info about transitions (as UiTransition
- instances) that one can trigger from the user interface.
- * if p_includeFake is True, it retrieves transitions that the user
- can't trigger, but for which he needs to know for what reason he
- can't trigger it;
- * if p_includeNotShowable is True, it includes transitions for which
- show=False. Indeed, because "showability" is only a UI concern,
- and not a security concern, in some cases it has sense to set
- includeNotShowable=True, because those transitions are triggerable
- from a security point of view.
- * If p_grouped is True, transitions are grouped according to their
- "group" attribute, in a similar way to fields or searches.
- '''
- res = []
- groups = {} # The already encountered groups of transitions.
- wfPage = gen.Page('workflow')
- wf = self.getWorkflow()
- currentState = self.State(name=False)
- # Loop on every transition
- for name in dir(wf):
- transition = getattr(wf, name)
- if (transition.__class__.__name__ != 'Transition'): continue
- # Filter transitions that do not have currentState as start state
- if not transition.hasState(currentState, True): continue
- # Check if the transition can be triggered
- mayTrigger = transition.isTriggerable(self, wf)
- # Compute the condition that will lead to including or not this
- # transition
- if not includeFake:
- includeIt = mayTrigger
- else:
- includeIt = mayTrigger or isinstance(mayTrigger, gen.No)
- if not includeNotShowable:
- includeIt = includeIt and transition.isShowable(wf, self)
- if not includeIt: continue
- # Create the UiTransition instance.
- info = UiTransition(name, transition, self, mayTrigger)
- # Add the transition into the result.
- if not transition.group or not grouped:
- res.append(info)
- else:
- # Insert the UiGroup instance corresponding to transition.group.
- uiGroup = transition.group.insertInto(res, groups, wfPage,
- self.__class__.__name__, content='transitions')
- uiGroup.addElement(info)
- return res
-
def getAppyPhases(self, currentOnly=False, layoutType='view'):
'''Gets the list of phases that are defined for this content type. If
p_currentOnly is True, the search is limited to the phase where the
@@ -965,10 +906,10 @@ class BaseMixin:
if hasattr(appyObj, 'getSubTitle'): return appyObj.getSubTitle()
return ''
- def notifyWorkflowCreated(self):
- '''This method is called every time an object is created, be it temp or
- not. The objective here is to initialise workflow-related data on
- the object.'''
+ # Workflow methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ def initializeWorkflow(self):
+ '''Called when an object is created, be it temp or not, for initializing
+ workflow-related data on the object.'''
wf = self.getWorkflow()
# Get the initial workflow state
initialState = self.State(name=False)
@@ -994,6 +935,55 @@ class BaseMixin:
stateName = stateName or self.State()
return '%s_%s' % (self.getWorkflow(name=True), stateName)
+ def getTransitions(self, includeFake=True, includeNotShowable=False,
+ grouped=True):
+ '''This method returns info about transitions (as UiTransition
+ instances) that one can trigger from the user interface.
+ * if p_includeFake is True, it retrieves transitions that the user
+ can't trigger, but for which he needs to know for what reason he
+ can't trigger it;
+ * if p_includeNotShowable is True, it includes transitions for which
+ show=False. Indeed, because "showability" is only a UI concern,
+ and not a security concern, in some cases it has sense to set
+ includeNotShowable=True, because those transitions are triggerable
+ from a security point of view.
+ * If p_grouped is True, transitions are grouped according to their
+ "group" attribute, in a similar way to fields or searches.
+ '''
+ res = []
+ groups = {} # The already encountered groups of transitions.
+ wfPage = gen.Page('workflow')
+ wf = self.getWorkflow()
+ currentState = self.State(name=False)
+ # Loop on every transition
+ for name in dir(wf):
+ transition = getattr(wf, name)
+ if (transition.__class__.__name__ != 'Transition'): continue
+ # Filter transitions that do not have currentState as start state
+ if not transition.hasState(currentState, True): continue
+ # Check if the transition can be triggered
+ mayTrigger = transition.isTriggerable(self, wf)
+ # Compute the condition that will lead to including or not this
+ # transition
+ if not includeFake:
+ includeIt = mayTrigger
+ else:
+ includeIt = mayTrigger or isinstance(mayTrigger, gen.No)
+ if not includeNotShowable:
+ includeIt = includeIt and transition.isShowable(wf, self)
+ if not includeIt: continue
+ # Create the UiTransition instance.
+ info = UiTransition(name, transition, self, mayTrigger)
+ # Add the transition into the result.
+ if not transition.group or not grouped:
+ res.append(info)
+ else:
+ # Insert the UiGroup instance corresponding to transition.group.
+ uiGroup = transition.group.insertInto(res, groups, wfPage,
+ self.__class__.__name__, content='transitions')
+ uiGroup.addElement(info)
+ return res
+
def applyUserIdChange(self, oldId, newId):
'''A user whose ID was p_oldId has now p_newId. If the old ID was
mentioned in self's local roles, update it to the new ID. This
@@ -1156,34 +1146,20 @@ class BaseMixin:
return True
def onExecuteAction(self):
- '''This method is called every time a user wants to execute an Appy
- action on an object.'''
+ '''Called when a user wants to execute an Appy action on an object.'''
rq = self.REQUEST
return self.getAppyType(rq['fieldName']).onUiRequest(self, rq)
- def trigger(self, transitionName, comment='', doAction=True, doNotify=True,
- doHistory=True, doSay=True, noSecurity=False):
- '''Triggers transition named p_transitionName.'''
- # Check that this transition exists.
- wf = self.getWorkflow()
- if not hasattr(wf, transitionName) or \
- getattr(wf, transitionName).__class__.__name__ != 'Transition':
- raise 'Transition "%s" was not found.' % transitionName
- # Is this transition triggerable?
- transition = getattr(wf, transitionName)
- if not transition.isTriggerable(self, wf, noSecurity=noSecurity):
- raise 'Transition "%s" can\'t be triggered.' % transitionName
- # Trigger the transition
- transition.trigger(transitionName, self, wf, comment, doAction=doAction,
- doNotify=doNotify, doHistory=doHistory, doSay=doSay)
-
def onTrigger(self):
- '''This method is called whenever a user wants to trigger a workflow
- transition on an object.'''
+ '''Called when a user wants to trigger a transition on an object.'''
rq = self.REQUEST
- self.trigger(rq['workflow_action'], comment=rq.get('comment', ''))
- self.reindex()
- return self.goto(self.getUrl(rq['HTTP_REFERER']))
+ wf = self.getWorkflow()
+ # Get the transition
+ name = rq['transition']
+ transition = getattr(wf, name, None)
+ if not transition or (transition.__class__.__name__ != 'Transition'):
+ raise Exception('Transition "%s" not found.' % name)
+ return transition.onUiRequest(self, wf, name, rq)
def getRolesFor(self, permission):
'''Gets, according to the workflow, the roles that are currently granted
diff --git a/gen/ui/appy.css b/gen/ui/appy.css
index 7cdd033..dca0bed 100644
--- a/gen/ui/appy.css
+++ b/gen/ui/appy.css
@@ -70,7 +70,7 @@ input.button { color: #666666; height: 20px; width: 130px;
cursor:pointer; font-size: 90%; padding-left: 10px;
background-color: white; background-repeat: no-repeat;
background-position: 5% 25%; box-shadow: 2px 2px 2px #888888}
-.fakeButton { background-color: #e6e6e6 !important ; cursor:help !important }
+.fake { background-color: #e6e6e6 !important ; cursor:help !important }
.buttons { margin-left: 4px }
.message { position: absolute; top: -40px; left: 50%; font-size: 90%;
width: 600px; border: 1px #F0C36D solid; padding: 6px;
@@ -99,7 +99,7 @@ td.search { padding-top: 8px }
.content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 }
.popup { display: none; position: absolute; top: 30%; left: 35%;
width: 350px; z-index : 100; background: white; padding: 8px;
- border: 1px solid grey }
+ border: 1px solid grey; box-shadow: 2px 2px 2px #888888}
.dropdown { display:none; position: absolute; border: 1px solid #cccccc;
background-color: white; padding: 3px 4px 0 }
.dropdownMenu { cursor: pointer; padding-right: 4px; font-size: 93% }
diff --git a/gen/ui/appy.js b/gen/ui/appy.js
index d2b5c4b..9031f3b 100644
--- a/gen/ui/appy.js
+++ b/gen/ui/appy.js
@@ -529,10 +529,8 @@ function submitAppyForm(button) {
// Function used for triggering a workflow transition
function triggerTransition(formId, transitionId, msg) {
var theForm = document.getElementById(formId);
- theForm.workflow_action.value = transitionId;
- if (!msg) {
- theForm.submit();
- }
+ theForm.transition.value = transitionId;
+ if (!msg) { theForm.submit(); }
else { // Ask the user to confirm.
askConfirm('form', formId, msg, true);
}
diff --git a/gen/ui/search.png b/gen/ui/search.png
new file mode 100644
index 0000000..dd1dd41
Binary files /dev/null and b/gen/ui/search.png differ
diff --git a/gen/utils.py b/gen/utils.py
index 0454714..890064d 100644
--- a/gen/utils.py
+++ b/gen/utils.py
@@ -46,7 +46,7 @@ def createObject(folder, id, className, appName, wf=True, noSecurity=False):
obj.modified = obj.created
from persistent.mapping import PersistentMapping
obj.__ac_local_roles__ = PersistentMapping({ userId: ['Owner'] })
- if wf: obj.notifyWorkflowCreated()
+ if wf: obj.initializeWorkflow()
return obj
# ------------------------------------------------------------------------------
diff --git a/gen/wrappers/ToolWrapper.py b/gen/wrappers/ToolWrapper.py
index 1cb057a..0302a2e 100644
--- a/gen/wrappers/ToolWrapper.py
+++ b/gen/wrappers/ToolWrapper.py
@@ -347,7 +347,7 @@ class ToolWrapper(AbstractWrapper):
:field.pxRender
+ if="field.isShowable(zobj, 'result')">:field.pxRender
''')
# Show query results as a list.
diff --git a/gen/wrappers/__init__.py b/gen/wrappers/__init__.py
index a8b1889..78eb486 100644
--- a/gen/wrappers/__init__.py
+++ b/gen/wrappers/__init__.py
@@ -366,7 +366,7 @@ class AbstractWrapper(object):
var2="formId='trigger_%s' % targetObj.UID()" method="post"
id=":formId" action=":targetObj.absolute_url() + '/do'">
-
+
@@ -854,13 +854,17 @@ class AbstractWrapper(object):
return self.o.translate(label, mapping, domain, language=language,
format=format)
- def do(self, transition, comment='', doAction=True, doNotify=True,
- doHistory=True, noSecurity=False):
- '''This method allows to trigger on p_self a workflow p_transition
- programmatically. See doc in self.o.do.'''
- return self.o.trigger(transition, comment, doAction=doAction,
- doNotify=doNotify, doHistory=doHistory,
- doSay=False, noSecurity=noSecurity)
+ def do(self, name, comment='', doAction=True, doNotify=True, doHistory=True,
+ noSecurity=False):
+ '''Triggers on p_self a transition named p_name programmatically.'''
+ o = self.o
+ wf = o.getWorkflow()
+ tr = getattr(wf, name, None)
+ if not tr or (tr.__class__.__name__ != 'Transition'):
+ raise Exception('Transition "%s" not found.' % name)
+ return tr.trigger(name, o, wf, comment, doAction=doAction,
+ doNotify=doNotify, doHistory=doHistory, doSay=False,
+ noSecurity=noSecurity)
def log(self, message, type='info'): return self.o.log(message, type)
def say(self, message, type='info'): return self.o.say(message, type)