[gen] Optimisations, bugfixes and refactorings.

This commit is contained in:
Gaetan Delannay 2014-04-21 12:11:41 +02:00
parent 822e1a7c63
commit f0c1f69573
9 changed files with 104 additions and 115 deletions

View file

@ -270,8 +270,7 @@ class Ref(Field):
<x if="refField.name != 'title'">
<x var="zobj=tied.o; obj=tied; layoutType='cell';
innerRef=True; field=refField"
if="zobj.showField(field.name, \
layoutType='result')">:field.pxRender</x>
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
</x>
</td>
<td if="checkboxes" class="cbCell">

View file

@ -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('''<x>
pxView = Px('''
<!-- Real button -->
<input if="transition.mayTrigger" type="button" class="button"
var2="label=transition.title"
@ -397,11 +410,10 @@ class UiTransition:
<!-- Fake button, explaining why the transition can't be triggered -->
<input if="not transition.mayTrigger" type="button"
class="fakeButton button" var2="label=transition.title"
class="fake button" var2="label=transition.title"
style=":'%s; %s' % (url('fake', bg=True),
ztool.getButtonWidth(label))"
value=":label" title=":'%s: %s' % (label, transition.reason)"/>
</x>''')
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
# ------------------------------------------------------------------------------

View file

@ -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

View file

@ -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% }

View file

@ -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);
}

BIN
gen/ui/search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

View file

@ -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
# ------------------------------------------------------------------------------

View file

@ -347,7 +347,7 @@ class ToolWrapper(AbstractWrapper):
<!-- Any other field -->
<x if="field.name != 'title'">
<x var="layoutType='cell'; innerRef=True"
if="zobj.showField(field.name, 'result')">:field.pxRender</x>
if="field.isShowable(zobj, 'result')">:field.pxRender</x>
</x>''')
# Show query results as a list.

View file

@ -366,7 +366,7 @@ class AbstractWrapper(object):
var2="formId='trigger_%s' % targetObj.UID()" method="post"
id=":formId" action=":targetObj.absolute_url() + '/do'">
<input type="hidden" name="action" value="Trigger"/>
<input type="hidden" name="workflow_action"/>
<input type="hidden" name="transition"/>
<!-- Input field for storing the comment coming from the popup -->
<textarea id="comment" name="comment" cols="30" rows="3"
style="display:none"></textarea>
@ -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)