2013-07-08 16:39:16 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# This file is part of Appy, a framework for building applications in the Python
|
|
|
|
# language. Copyright (C) 2007 Gaetan Delannay
|
|
|
|
|
|
|
|
# Appy is free software; you can redistribute it and/or modify it under the
|
|
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
|
|
# Foundation; either version 3 of the License, or (at your option) any later
|
|
|
|
# version.
|
|
|
|
|
|
|
|
# Appy is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
|
|
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
|
|
|
# Appy. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2014-04-15 14:46:13 -05:00
|
|
|
import os.path
|
2013-07-08 16:39:16 -05:00
|
|
|
from appy.fields import Field
|
|
|
|
from appy.px import Px
|
|
|
|
from appy.shared import utils as sutils
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class Action(Field):
|
|
|
|
'''An action is a Python method that can be triggered by the user on a
|
|
|
|
given gen-class. An action is rendered as a button.'''
|
|
|
|
|
2014-12-05 04:55:25 -06:00
|
|
|
# PX for viewing the Action button
|
2013-07-08 16:39:16 -05:00
|
|
|
pxView = pxCell = Px('''
|
2014-04-15 14:46:13 -05:00
|
|
|
<form var="formId='%s_%s_form' % (zobj.id, name);
|
2014-04-20 12:22:40 -05:00
|
|
|
label=_(field.labelId);
|
2014-12-15 12:36:00 -06:00
|
|
|
descr=field.hasDescr and _(field.descrId) or None;
|
2015-01-06 14:13:30 -06:00
|
|
|
smallButtons=smallButtons|False;
|
2015-02-04 02:27:07 -06:00
|
|
|
css=ztool.getButtonCss(label, smallButtons);
|
|
|
|
back=(layoutType == 'cell') and q(zobj.id) or 'null'"
|
2015-01-07 11:20:48 -06:00
|
|
|
id=":formId" action=":zobj.absolute_url() + '/onExecuteAction'"
|
2014-11-13 08:02:33 -06:00
|
|
|
style="display:inline">
|
2013-07-08 16:39:16 -05:00
|
|
|
<input type="hidden" name="fieldName" value=":name"/>
|
2014-10-22 15:17:26 -05:00
|
|
|
<input type="hidden" name="comment" value=""/>
|
2015-02-04 02:27:07 -06:00
|
|
|
<input type="button" class=":css" title=":descr"
|
|
|
|
var="textConfirm=field.confirm and _(field.labelId+'_confirm') or '';
|
|
|
|
showComment=(field.confirm == 'text') and 'true' or 'false'"
|
2015-01-19 08:44:09 -06:00
|
|
|
value=":label" style=":url(field.icon, bg=True)"
|
2015-02-04 02:27:07 -06:00
|
|
|
onclick=":'submitForm(%s,%s,%s,%s)' % (q(formId), q(textConfirm), \
|
|
|
|
showComment, back)"/>
|
2013-07-08 16:39:16 -05:00
|
|
|
</form>''')
|
|
|
|
|
2014-12-05 04:55:25 -06:00
|
|
|
# It is not possible to edit an action, not to search it
|
2013-07-08 16:39:16 -05:00
|
|
|
pxEdit = pxSearch = ''
|
|
|
|
|
|
|
|
def __init__(self, validator=None, multiplicity=(1,1), default=None,
|
2014-11-10 06:34:52 -06:00
|
|
|
show=('view', 'result'), page='main', group=None, layouts=None,
|
2014-12-26 10:29:14 -06:00
|
|
|
move=0, specificReadPermission=False,
|
|
|
|
specificWritePermission=False, width=None, height=None,
|
|
|
|
maxChars=None, colspan=1, action=None, result='computation',
|
|
|
|
confirm=False, master=None, masterValue=None, focus=False,
|
|
|
|
historized=False, mapping=None, label=None, icon=None,
|
|
|
|
view=None, xml=None):
|
2013-07-08 16:39:16 -05:00
|
|
|
# Can be a single method or a list/tuple of methods
|
|
|
|
self.action = action
|
|
|
|
# For the 'result' param:
|
|
|
|
# * value 'computation' means that the action will simply compute
|
|
|
|
# things and redirect the user to the same page, with some status
|
|
|
|
# message about execution of the action;
|
|
|
|
# * 'file' means that the result is the binary content of a file that
|
|
|
|
# the user will download.
|
|
|
|
# * 'redirect' means that the action will lead to the user being
|
|
|
|
# redirected to some other page.
|
|
|
|
self.result = result
|
|
|
|
# If following field "confirm" is True, a popup will ask the user if
|
|
|
|
# she is really sure about triggering this action.
|
|
|
|
self.confirm = confirm
|
2014-12-05 04:55:25 -06:00
|
|
|
# If no p_icon is specified, "action.png" will be used
|
|
|
|
self.icon = icon or 'action'
|
2013-07-08 16:39:16 -05:00
|
|
|
Field.__init__(self, None, (0,1), default, show, page, group, layouts,
|
2014-12-26 10:29:14 -06:00
|
|
|
move, False, True, False, specificReadPermission,
|
2013-07-08 16:39:16 -05:00
|
|
|
specificWritePermission, width, height, None, colspan,
|
2014-03-05 09:19:11 -06:00
|
|
|
master, masterValue, focus, historized, mapping, label,
|
2014-12-09 08:19:28 -06:00
|
|
|
None, None, None, None, False, view, xml)
|
2013-07-08 16:39:16 -05:00
|
|
|
self.validable = False
|
2014-12-05 04:55:25 -06:00
|
|
|
self.renderLabel = False # Label is rendered directly within the button
|
2013-07-08 16:39:16 -05:00
|
|
|
|
|
|
|
def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'}
|
|
|
|
|
2014-10-22 15:17:26 -05:00
|
|
|
def callAction(self, obj, method, hasParam, param):
|
|
|
|
'''Calls p_method on p_obj. m_method can be the single action as defined
|
|
|
|
in self.action or one of them is self.action contains several
|
|
|
|
methods. Calling m_method can be done with a p_param (when p_hasParam
|
|
|
|
is True), ie, when self.confirm is "text".'''
|
|
|
|
if hasParam: return method(obj, param)
|
|
|
|
else: return method(obj)
|
|
|
|
|
2013-07-08 16:39:16 -05:00
|
|
|
def __call__(self, obj):
|
|
|
|
'''Calls the action on p_obj.'''
|
2014-10-22 15:17:26 -05:00
|
|
|
# Must we call the method(s) with a param ?
|
|
|
|
hasParam = self.confirm == 'text'
|
|
|
|
param = hasParam and obj.request.get('comment', None)
|
2013-07-08 16:39:16 -05:00
|
|
|
if type(self.action) in sutils.sequenceTypes:
|
|
|
|
# There are multiple Python methods
|
|
|
|
res = [True, '']
|
|
|
|
for act in self.action:
|
2014-10-22 15:17:26 -05:00
|
|
|
actRes = self.callAction(obj, act, hasParam, param)
|
2013-07-08 16:39:16 -05:00
|
|
|
if type(actRes) in sutils.sequenceTypes:
|
|
|
|
res[0] = res[0] and actRes[0]
|
|
|
|
if self.result.startswith('file'):
|
|
|
|
res[1] = res[1] + actRes[1]
|
|
|
|
else:
|
|
|
|
res[1] = res[1] + '\n' + actRes[1]
|
|
|
|
else:
|
|
|
|
res[0] = res[0] and actRes
|
|
|
|
else:
|
|
|
|
# There is only one Python method
|
2014-10-22 15:17:26 -05:00
|
|
|
actRes = self.callAction(obj, self.action, hasParam, param)
|
2013-07-08 16:39:16 -05:00
|
|
|
if type(actRes) in sutils.sequenceTypes:
|
|
|
|
res = list(actRes)
|
|
|
|
else:
|
|
|
|
res = [actRes, '']
|
|
|
|
# If res is None (ie the user-defined action did not return anything),
|
|
|
|
# we consider the action as successfull.
|
|
|
|
if res[0] == None: res[0] = True
|
|
|
|
return res
|
|
|
|
|
|
|
|
def isShowable(self, obj, layoutType):
|
2014-04-15 14:46:13 -05:00
|
|
|
if layoutType == 'edit': return
|
|
|
|
return Field.isShowable(self, obj, layoutType)
|
|
|
|
|
2015-01-19 18:03:23 -06:00
|
|
|
# Action fields can a priori be shown on every layout, "buttons" included
|
|
|
|
def isRenderable(self, layoutType): return True
|
|
|
|
|
2014-04-15 14:46:13 -05:00
|
|
|
def onUiRequest(self, obj, rq):
|
|
|
|
'''This method is called when a user triggers the execution of this
|
|
|
|
action from the user interface.'''
|
2015-01-07 11:20:48 -06:00
|
|
|
# Execute the action (method __call__)
|
2014-04-15 14:46:13 -05:00
|
|
|
actionRes = self(obj.appy())
|
|
|
|
parent = obj.getParentNode()
|
|
|
|
parentAq = getattr(parent, 'aq_base', parent)
|
|
|
|
if not hasattr(parentAq, obj.id):
|
2015-01-07 11:20:48 -06:00
|
|
|
# The action has led to obj's deletion
|
2014-04-15 14:46:13 -05:00
|
|
|
obj.reindex()
|
2015-01-07 11:20:48 -06:00
|
|
|
# Unwrap action results
|
2014-04-15 14:46:13 -05:00
|
|
|
successfull, msg = actionRes
|
|
|
|
if not msg:
|
2015-01-07 11:20:48 -06:00
|
|
|
# Use the default i18n messages
|
2014-04-15 14:46:13 -05:00
|
|
|
suffix = successfull and 'done' or 'ko'
|
|
|
|
msg = obj.translate('action_%s' % suffix)
|
|
|
|
if (self.result == 'computation') or not successfull:
|
2015-02-04 02:27:07 -06:00
|
|
|
# If we are called from an Ajax request, simply return msg
|
|
|
|
if hasattr(rq, 'pxContext') and rq.pxContext['ajax']: return msg
|
2014-04-15 14:46:13 -05:00
|
|
|
obj.say(msg)
|
|
|
|
return obj.goto(obj.getUrl(rq['HTTP_REFERER']))
|
|
|
|
elif self.result == 'file':
|
2015-01-07 11:20:48 -06:00
|
|
|
# msg does not contain a message, but a file instance
|
2014-04-15 14:46:13 -05:00
|
|
|
response = rq.RESPONSE
|
|
|
|
response.setHeader('Content-Type', sutils.getMimeType(msg.name))
|
|
|
|
response.setHeader('Content-Disposition', 'inline;filename="%s"' %\
|
|
|
|
os.path.basename(msg.name))
|
|
|
|
response.write(msg.read())
|
|
|
|
msg.close()
|
|
|
|
elif self.result == 'redirect':
|
|
|
|
# msg does not contain a message, but the URL where to redirect
|
|
|
|
# the user.
|
|
|
|
return obj.goto(msg)
|
2013-07-08 16:39:16 -05:00
|
|
|
# ------------------------------------------------------------------------------
|