# ------------------------------------------------------------------------------ # 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 . # ------------------------------------------------------------------------------ import os.path 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.''' # PX for viewing the Action button pxView = pxCell = Px('''
''') # It is not possible to edit an action, not to search it pxEdit = pxSearch = '' def __init__(self, validator=None, multiplicity=(1,1), default=None, show=('view', 'result'), page='main', group=None, layouts=None, 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): # 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 # If no p_icon is specified, "action.png" will be used self.icon = icon or 'action' Field.__init__(self, None, (0,1), default, show, page, group, layouts, move, False, True, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, None, None, None, None, False, view, xml) self.validable = False self.renderLabel = False # Label is rendered directly within the button def getDefaultLayouts(self): return {'view': 'l-f', 'edit': 'lrv-f'} 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) def __call__(self, obj): '''Calls the action on p_obj.''' # Must we call the method(s) with a param ? hasParam = self.confirm == 'text' param = hasParam and obj.request.get('comment', None) if type(self.action) in sutils.sequenceTypes: # There are multiple Python methods res = [True, ''] for act in self.action: actRes = self.callAction(obj, act, hasParam, param) 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 actRes = self.callAction(obj, self.action, hasParam, param) 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): if layoutType == 'edit': return return Field.isShowable(self, obj, layoutType) def onUiRequest(self, obj, rq): '''This method is called when a user triggers the execution of this action from the user interface.''' # Execute the action (method __call__) actionRes = self(obj.appy()) parent = obj.getParentNode() parentAq = getattr(parent, 'aq_base', parent) if not hasattr(parentAq, obj.id): # The action has led to obj's deletion obj.reindex() # Unwrap action results successfull, msg = actionRes if not msg: # Use the default i18n messages suffix = successfull and 'done' or 'ko' msg = obj.translate('action_%s' % suffix) if (self.result == 'computation') or not successfull: obj.say(msg) return obj.goto(obj.getUrl(rq['HTTP_REFERER'])) elif self.result == 'file': # msg does not contain a message, but a file instance 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) # ------------------------------------------------------------------------------