Custom messages can now be returned as result of triggering transitions; added a mechanism for asking a confirmation to the user before saving it; bugfix in navigation (navigation info disappeared when firing workflow actions.
This commit is contained in:
parent
cccdc12372
commit
502c86dab8
|
@ -47,6 +47,10 @@ class ToolMixin(BaseMixin):
|
||||||
res['action'] = appyType.action
|
res['action'] = appyType.action
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def getSiteUrl(self):
|
||||||
|
'''Returns the absolute URL of this site.'''
|
||||||
|
return self.portal_url.getPortalObject().absolute_url()
|
||||||
|
|
||||||
def generateDocument(self):
|
def generateDocument(self):
|
||||||
'''Generates the document from field-related info. UID of object that
|
'''Generates the document from field-related info. UID of object that
|
||||||
is the template target is given in the request.'''
|
is the template target is given in the request.'''
|
||||||
|
|
|
@ -18,7 +18,7 @@ class BaseMixin:
|
||||||
_appy_meta_type = 'Class'
|
_appy_meta_type = 'Class'
|
||||||
|
|
||||||
def get_o(self):
|
def get_o(self):
|
||||||
'''In some cases, we wand the Zope object, we don't know if the current
|
'''In some cases, we want the Zope object, we don't know if the current
|
||||||
object is a Zope or Appy object. By defining this property,
|
object is a Zope or Appy object. By defining this property,
|
||||||
"someObject.o" produces always the Zope object, be someObject an Appy
|
"someObject.o" produces always the Zope object, be someObject an Appy
|
||||||
or Zope object.'''
|
or Zope object.'''
|
||||||
|
@ -79,6 +79,18 @@ class BaseMixin:
|
||||||
'''This methods is self's suicide.'''
|
'''This methods is self's suicide.'''
|
||||||
self.getParentNode().manage_delObjects([self.id])
|
self.getParentNode().manage_delObjects([self.id])
|
||||||
|
|
||||||
|
def onDelete(self):
|
||||||
|
rq = self.REQUEST
|
||||||
|
self.delete()
|
||||||
|
if self.getUrl(rq['HTTP_REFERER'],mode='raw') ==self.getUrl(mode='raw'):
|
||||||
|
# We were consulting the object that has been deleted. Go back to
|
||||||
|
# the main page.
|
||||||
|
urlBack = self.getTool().getSiteUrl()
|
||||||
|
else:
|
||||||
|
urlBack = self.getUrl(rq['HTTP_REFERER'])
|
||||||
|
self.plone_utils.addPortalMessage(self.translate('delete_done'))
|
||||||
|
self.goto(urlBack)
|
||||||
|
|
||||||
def onCreate(self):
|
def onCreate(self):
|
||||||
'''This method is called when a user wants to create a root object in
|
'''This method is called when a user wants to create a root object in
|
||||||
the application folder or an object through a reference field.'''
|
the application folder or an object through a reference field.'''
|
||||||
|
@ -152,6 +164,7 @@ class BaseMixin:
|
||||||
the "final" object in the database. If the object is not a temporary
|
the "final" object in the database. If the object is not a temporary
|
||||||
one, this method updates its fields in the database.'''
|
one, this method updates its fields in the database.'''
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
|
tool = self.getTool()
|
||||||
errorMessage = self.translate(
|
errorMessage = self.translate(
|
||||||
'Please correct the indicated errors.', domain='plone')
|
'Please correct the indicated errors.', domain='plone')
|
||||||
isNew = rq.get('is_new') == 'True'
|
isNew = rq.get('is_new') == 'True'
|
||||||
|
@ -161,12 +174,12 @@ class BaseMixin:
|
||||||
if rq.get('nav', ''):
|
if rq.get('nav', ''):
|
||||||
# We can go back to the initiator page.
|
# We can go back to the initiator page.
|
||||||
splitted = rq['nav'].split('.')
|
splitted = rq['nav'].split('.')
|
||||||
initiator = self.getTool().getObject(splitted[1])
|
initiator = tool.getObject(splitted[1])
|
||||||
initiatorPage = splitted[2].split(':')[1]
|
initiatorPage = splitted[2].split(':')[1]
|
||||||
urlBack = initiator.getUrl(page=initiatorPage, nav='')
|
urlBack = initiator.getUrl(page=initiatorPage, nav='')
|
||||||
else:
|
else:
|
||||||
# Go back to the root of the site.
|
# Go back to the root of the site.
|
||||||
urlBack = self.portal_url.getPortalObject().absolute_url()
|
urlBack = tool.getSiteUrl()
|
||||||
else:
|
else:
|
||||||
urlBack = self.getUrl()
|
urlBack = self.getUrl()
|
||||||
self.plone_utils.addPortalMessage(
|
self.plone_utils.addPortalMessage(
|
||||||
|
@ -192,12 +205,21 @@ class BaseMixin:
|
||||||
self.plone_utils.addPortalMessage(errorMessage)
|
self.plone_utils.addPortalMessage(errorMessage)
|
||||||
return self.skyn.edit(self)
|
return self.skyn.edit(self)
|
||||||
|
|
||||||
|
# Before saving data, must we ask a confirmation by the user ?
|
||||||
|
appyObj = self.appy()
|
||||||
|
saveConfirmed = rq.get('confirmed') == 'True'
|
||||||
|
if hasattr(appyObj, 'confirm') and not saveConfirmed:
|
||||||
|
msg = appyObj.confirm(values)
|
||||||
|
if msg:
|
||||||
|
rq.set('confirmMsg', msg.replace("'", "\\'"))
|
||||||
|
return self.skyn.edit(self)
|
||||||
|
|
||||||
# Create or update the object in the database
|
# Create or update the object in the database
|
||||||
obj = self.createOrUpdate(isNew, values)
|
obj = self.createOrUpdate(isNew, values)
|
||||||
|
|
||||||
# Redirect the user to the appropriate page
|
# Redirect the user to the appropriate page
|
||||||
msg = obj.translate('Changes saved.', domain='plone')
|
msg = obj.translate('Changes saved.', domain='plone')
|
||||||
if rq.get('buttonOk.x', None):
|
if rq.get('buttonOk.x', None) or saveConfirmed:
|
||||||
# Go to the consult view for this object
|
# Go to the consult view for this object
|
||||||
obj.plone_utils.addPortalMessage(msg)
|
obj.plone_utils.addPortalMessage(msg)
|
||||||
return self.goto(obj.getUrl())
|
return self.goto(obj.getUrl())
|
||||||
|
@ -237,13 +259,6 @@ class BaseMixin:
|
||||||
return self.goto(obj.getUrl())
|
return self.goto(obj.getUrl())
|
||||||
return obj.skyn.edit(obj)
|
return obj.skyn.edit(obj)
|
||||||
|
|
||||||
def onDelete(self):
|
|
||||||
rq = self.REQUEST
|
|
||||||
msg = self.translate('delete_done')
|
|
||||||
self.delete()
|
|
||||||
self.plone_utils.addPortalMessage(msg)
|
|
||||||
self.goto(self.getUrl(rq['HTTP_REFERER']))
|
|
||||||
|
|
||||||
def rememberPreviousData(self):
|
def rememberPreviousData(self):
|
||||||
'''This method is called before updating an object and remembers, for
|
'''This method is called before updating an object and remembers, for
|
||||||
every historized field, the previous value. Result is a dict
|
every historized field, the previous value. Result is a dict
|
||||||
|
@ -754,17 +769,11 @@ class BaseMixin:
|
||||||
rq = self.REQUEST
|
rq = self.REQUEST
|
||||||
self.portal_workflow.doActionFor(self, rq['workflow_action'],
|
self.portal_workflow.doActionFor(self, rq['workflow_action'],
|
||||||
comment = rq.get('comment', ''))
|
comment = rq.get('comment', ''))
|
||||||
# Where to redirect the user back ?
|
|
||||||
urlBack = rq['HTTP_REFERER']
|
|
||||||
if urlBack.find('?') != -1:
|
|
||||||
# Remove params; this way, the user may be redirected to correct
|
|
||||||
# phase when relevant.
|
|
||||||
urlBack = urlBack[:urlBack.find('?')]
|
|
||||||
msg = self.translate(u'Your content\'s status has been modified.',
|
|
||||||
domain='plone')
|
|
||||||
self.plone_utils.addPortalMessage(msg)
|
|
||||||
self.reindexObject()
|
self.reindexObject()
|
||||||
return self.goto(urlBack)
|
# Where to redirect the user back ?
|
||||||
|
# TODO (?): remove the "phase" param for redirecting the user to the
|
||||||
|
# next phase when relevant.
|
||||||
|
return self.goto(self.getUrl(rq['HTTP_REFERER']))
|
||||||
|
|
||||||
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
def fieldValueSelected(self, fieldName, vocabValue, dbValue):
|
||||||
'''When displaying a selection box (ie a String with a validator being a
|
'''When displaying a selection box (ie a String with a validator being a
|
||||||
|
@ -916,15 +925,28 @@ class BaseMixin:
|
||||||
'''Returns a Appy URL.
|
'''Returns a Appy URL.
|
||||||
* If p_base is None, it will be the base URL for this object
|
* If p_base is None, it will be the base URL for this object
|
||||||
(ie, self.absolute_url()).
|
(ie, self.absolute_url()).
|
||||||
* p_mode can de "edit" or "view".
|
* p_mode can be "edit", "view" or "raw" (a non-param, base URL)
|
||||||
* p_kwargs can store additional parameters to add to the URL.
|
* p_kwargs can store additional parameters to add to the URL.
|
||||||
In this dict, every value that is a string will be added to the
|
In this dict, every value that is a string will be added to the
|
||||||
URL as-is. Every value that is True will be replaced by the value
|
URL as-is. Every value that is True will be replaced by the value
|
||||||
in the request for the corresponding key (if existing; else, the
|
in the request for the corresponding key (if existing; else, the
|
||||||
param will not be included in the URL at all).'''
|
param will not be included in the URL at all).'''
|
||||||
# Define base URL if ommitted
|
# Define the URL suffix
|
||||||
|
suffix = ''
|
||||||
|
if mode != 'raw': suffix = '/skyn/%s' % mode
|
||||||
|
# Define base URL if omitted
|
||||||
if not base:
|
if not base:
|
||||||
base = '%s/skyn/%s' % (self.absolute_url(), mode)
|
base = self.absolute_url() + suffix
|
||||||
|
# If a raw URL is asked, remove any param and suffix.
|
||||||
|
if mode == 'raw':
|
||||||
|
if '?' in base: base = base[:base.index('?')]
|
||||||
|
base = base.strip('/')
|
||||||
|
for mode in ('view', 'edit'):
|
||||||
|
suffix = 'skyn/%s' % mode
|
||||||
|
if base.endswith(suffix):
|
||||||
|
base = base[:-len(suffix)].strip('/')
|
||||||
|
break
|
||||||
|
return base
|
||||||
# Manage default args
|
# Manage default args
|
||||||
if not kwargs: kwargs = self.getUrlDefaults
|
if not kwargs: kwargs = self.getUrlDefaults
|
||||||
if 'page' not in kwargs: kwargs['page'] = True
|
if 'page' not in kwargs: kwargs['page'] = True
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
page request/page|python:'main';
|
page request/page|python:'main';
|
||||||
cssAndJs python: contextObj.getCssAndJs(layoutType, page);
|
cssAndJs python: contextObj.getCssAndJs(layoutType, page);
|
||||||
css python: cssAndJs[0];
|
css python: cssAndJs[0];
|
||||||
js python: cssAndJs[1]">
|
js python: cssAndJs[1];
|
||||||
|
confirmMsg request/confirmMsg | nothing;">
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
|
||||||
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
||||||
|
@ -46,15 +47,19 @@
|
||||||
<body>
|
<body>
|
||||||
<metal:fill fill-slot="main">
|
<metal:fill fill-slot="main">
|
||||||
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
<metal:prologue use-macro="here/skyn/page/macros/prologue"/>
|
||||||
<form name="edit_form" method="post" enctype="multipart/form-data"
|
<form id="appyEditForm" name="appyEditForm" method="post" enctype="multipart/form-data"
|
||||||
class="enableUnloadProtection atBaseEditForm"
|
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do';
|
||||||
tal:attributes="action python: contextObj.absolute_url()+'/skyn/do'">
|
class python: test(confirmMsg, 'atBaseEditForm', 'enableUnloadProtection atBaseEditForm')">
|
||||||
<input type="hidden" name="action" value="Update"/>
|
<input type="hidden" name="action" value="Update"/>
|
||||||
<input type="hidden" name="page" tal:attributes="value page"/>
|
<input type="hidden" name="page" tal:attributes="value page"/>
|
||||||
<input type="hidden" name="nav" tal:attributes="value request/nav|nothing"/>
|
<input type="hidden" name="nav" tal:attributes="value request/nav|nothing"/>
|
||||||
<input type="hidden" name="is_new" tal:attributes="value contextObj/isTemporary"/>
|
<input type="hidden" name="is_new" tal:attributes="value contextObj/isTemporary"/>
|
||||||
|
<input type="hidden" name="confirmed" value="False"/>
|
||||||
<metal:show use-macro="here/skyn/page/macros/show"/>
|
<metal:show use-macro="here/skyn/page/macros/show"/>
|
||||||
</form>
|
</form>
|
||||||
|
<script tal:condition="confirmMsg"
|
||||||
|
tal:content="python: 'askConfirm(\'script\', \'postConfirmedEditForm()\', \'%s\')' % confirmMsg">
|
||||||
|
</script>
|
||||||
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
<metal:footer use-macro="here/skyn/page/macros/footer"/>
|
||||||
</metal:fill>
|
</metal:fill>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -46,11 +46,16 @@
|
||||||
if (xhrObjects[pos].onGet) {
|
if (xhrObjects[pos].onGet) {
|
||||||
xhrObjects[pos].onGet(xhrObjects[pos], hookElem);
|
xhrObjects[pos].onGet(xhrObjects[pos], hookElem);
|
||||||
}
|
}
|
||||||
|
// Eval inner scripts if any.
|
||||||
|
var innerScripts = document.getElementsByName("appyHook");
|
||||||
|
for (var i=0; i<innerScripts.length; i++) {
|
||||||
|
eval(innerScripts[i].innerHTML);
|
||||||
}
|
}
|
||||||
xhrObjects[pos].freed = 1;
|
xhrObjects[pos].freed = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
|
function askAjaxChunk(hook,mode,url,page,macro,params,beforeSend,onGet) {
|
||||||
/* This function will ask to get a chunk of HTML on the server through a
|
/* This function will ask to get a chunk of HTML on the server through a
|
||||||
|
@ -304,7 +309,13 @@
|
||||||
// We must execute Javascript code in "action"
|
// We must execute Javascript code in "action"
|
||||||
eval(action);
|
eval(action);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Function that finally posts the edit form after the user has confirmed that
|
||||||
|
// she really wants to post it.
|
||||||
|
function postConfirmedEditForm() {
|
||||||
|
var theForm = document.getElementById('appyEditForm');
|
||||||
|
theForm.confirmed.value = "True";
|
||||||
|
theForm.submit();
|
||||||
}
|
}
|
||||||
// Function that shows or hides a tab. p_action is 'show' or 'hide'.
|
// Function that shows or hides a tab. p_action is 'show' or 'hide'.
|
||||||
function manageTab(tabId, action) {
|
function manageTab(tabId, action) {
|
||||||
|
|
|
@ -92,8 +92,7 @@
|
||||||
tal:define= "innerRef innerRef|python:False;
|
tal:define= "innerRef innerRef|python:False;
|
||||||
ajaxHookId python: contextObj.UID() + name"
|
ajaxHookId python: contextObj.UID() + name"
|
||||||
tal:attributes = "id ajaxHookId">
|
tal:attributes = "id ajaxHookId">
|
||||||
<script language="javascript"
|
<script name="appyHook" tal:content="python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url(), name, innerRef)">
|
||||||
tal:content="python: 'askRefField(\'%s\',\'%s\',\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url(), name, innerRef)">
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -148,9 +147,6 @@
|
||||||
<tal:objectIsPresent condition="objs">
|
<tal:objectIsPresent condition="objs">
|
||||||
<tal:obj repeat="obj objs">
|
<tal:obj repeat="obj objs">
|
||||||
<td tal:define="includeShownInfo python:True"><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" /></td>
|
<td tal:define="includeShownInfo python:True"><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" /></td>
|
||||||
<td tal:condition="not: appyType/isBack">
|
|
||||||
<metal:showObjectActions use-macro="portal/skyn/widgets/ref/macros/objectActions" />
|
|
||||||
</td>
|
|
||||||
</tal:obj>
|
</tal:obj>
|
||||||
</tal:objectIsPresent>
|
</tal:objectIsPresent>
|
||||||
</tr></table>
|
</tr></table>
|
||||||
|
|
|
@ -142,6 +142,7 @@ def do(transitionName, stateChange, logger):
|
||||||
ploneObj = stateChange.object
|
ploneObj = stateChange.object
|
||||||
workflow = ploneObj.getWorkflow()
|
workflow = ploneObj.getWorkflow()
|
||||||
transition = workflow._transitionsMapping[transitionName]
|
transition = workflow._transitionsMapping[transitionName]
|
||||||
|
msg = ''
|
||||||
# Must I execute transition-related actions and notifications?
|
# Must I execute transition-related actions and notifications?
|
||||||
doAction = False
|
doAction = False
|
||||||
if transition.action:
|
if transition.action:
|
||||||
|
@ -161,11 +162,25 @@ def do(transitionName, stateChange, logger):
|
||||||
if doAction or doNotify:
|
if doAction or doNotify:
|
||||||
obj = ploneObj.appy()
|
obj = ploneObj.appy()
|
||||||
if doAction:
|
if doAction:
|
||||||
|
msg = ''
|
||||||
if type(transition.action) in (tuple, list):
|
if type(transition.action) in (tuple, list):
|
||||||
# We need to execute a list of actions
|
# We need to execute a list of actions
|
||||||
for act in transition.action: act(workflow, obj)
|
for act in transition.action:
|
||||||
|
msgPart = act(workflow, obj)
|
||||||
|
if msgPart: msg += msgPart
|
||||||
else: # We execute a single action only.
|
else: # We execute a single action only.
|
||||||
transition.action(workflow, obj)
|
msgPart = transition.action(workflow, obj)
|
||||||
|
if msgPart: msg += msgPart
|
||||||
if doNotify:
|
if doNotify:
|
||||||
notifier.sendMail(obj, transition, transitionName, workflow, logger)
|
notifier.sendMail(obj, transition, transitionName, workflow, logger)
|
||||||
|
# Produce a message to the user
|
||||||
|
if hasattr(ploneObj, '_v_appy_do') and not ploneObj._v_appy_do['doSay']:
|
||||||
|
# We do not produce any message if the transition was triggered
|
||||||
|
# programmatically.
|
||||||
|
return
|
||||||
|
# Produce a default message if no transition has given a custom one.
|
||||||
|
if not msg:
|
||||||
|
msg = ploneObj.translate(u'Your content\'s status has been modified.',
|
||||||
|
domain='plone')
|
||||||
|
ploneObj.plone_utils.addPortalMessage(msg)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -90,6 +90,8 @@ class AbstractWrapper:
|
||||||
typeName = property(get_typeName)
|
typeName = property(get_typeName)
|
||||||
def get_id(self): return self.o.id
|
def get_id(self): return self.o.id
|
||||||
id = property(get_id)
|
id = property(get_id)
|
||||||
|
def get_uid(self): return self.o.UID()
|
||||||
|
uid = property(get_uid)
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
return self.o.portal_workflow.getInfoFor(self.o, 'review_state')
|
return self.o.portal_workflow.getInfoFor(self.o, 'review_state')
|
||||||
state = property(get_state)
|
state = property(get_state)
|
||||||
|
@ -238,7 +240,8 @@ class AbstractWrapper:
|
||||||
# Set in a versatile attribute details about what to execute or not
|
# Set in a versatile attribute details about what to execute or not
|
||||||
# (actions, notifications) after the transition has been executed by DC
|
# (actions, notifications) after the transition has been executed by DC
|
||||||
# workflow.
|
# workflow.
|
||||||
self.o._v_appy_do = {'doAction': doAction, 'doNotify': doNotify}
|
self.o._v_appy_do = {'doAction': doAction, 'doNotify': doNotify,
|
||||||
|
'doSay': False}
|
||||||
if not doHistory:
|
if not doHistory:
|
||||||
comment = '_invisible_' # Will not be displayed.
|
comment = '_invisible_' # Will not be displayed.
|
||||||
# At first sight, I wanted to remove the entry from
|
# At first sight, I wanted to remove the entry from
|
||||||
|
|
Loading…
Reference in a new issue