Added the possibility to give a custom context to the macro specified for a Computed field; added param 'confirm' to a workflow transition, allowing to show a confirm popup before triggering it; added param 'format' for translate method, allowing to produce strings that conform to HTML or JS.

This commit is contained in:
Gaetan Delannay 2011-01-29 02:18:14 +01:00
parent 90553381a3
commit b48525c5bb
5 changed files with 66 additions and 22 deletions

View file

@ -1317,6 +1317,13 @@ class Boolean(Type):
def getDefaultLayouts(self): def getDefaultLayouts(self):
return {'view': 'l;f!_', 'edit': Table('f;lrv;=', width=None)} return {'view': 'l;f!_', 'edit': Table('f;lrv;=', width=None)}
def getValue(self, obj):
'''Never returns "None". Returns always "True" or "False", even if
"None" is stored in the DB.'''
value = Type.getValue(self, obj)
if value == None: return False
return value
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
if value: res = obj.translate('yes', domain='plone') if value: res = obj.translate('yes', domain='plone')
else: res = obj.translate('no', domain='plone') else: res = obj.translate('no', domain='plone')
@ -1751,7 +1758,8 @@ class Computed(Type):
searchable=False, specificReadPermission=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
colspan=1, method=None, plainText=True, master=None, colspan=1, method=None, plainText=True, master=None,
masterValue=None, focus=False, historized=False, sync=True): masterValue=None, focus=False, historized=False, sync=True,
context={}):
# The Python method used for computing the field value # The Python method used for computing the field value
self.method = method self.method = method
# Does field computation produce plain text or XHTML? # Does field computation produce plain text or XHTML?
@ -1760,6 +1768,10 @@ class Computed(Type):
# When field computation is done with a macro, we know the result # When field computation is done with a macro, we know the result
# will be HTML. # will be HTML.
self.plainText = False self.plainText = False
# The context is a dict (or method returning a dict) that will be given
# to the macro specified in self.method. If the dict contains key
# "someKey", it will be available to the macro as "options/someKey".
self.context = context
Type.__init__(self, None, multiplicity, index, default, optional, Type.__init__(self, None, multiplicity, index, default, optional,
False, show, page, group, layouts, move, indexed, False, False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
@ -1780,7 +1792,13 @@ class Computed(Type):
for name in names[:-1]: for name in names[:-1]:
page = getattr(page, name) page = getattr(page, name)
macroName = names[-1] macroName = names[-1]
return macroPage(obj, contextObj=obj, page=page, macroName=macroName) # Compute the macro context.
ctx = {'contextObj':obj, 'page':page, 'macroName':macroName}
if callable(self.context):
ctx.update(self.context(obj.appy()))
else:
ctx.update(self.context)
return macroPage(obj, **ctx)
def getValue(self, obj): def getValue(self, obj):
'''Computes the value instead of getting it in the database.''' '''Computes the value instead of getting it in the database.'''
@ -2007,7 +2025,7 @@ class State:
class Transition: class Transition:
def __init__(self, states, condition=True, action=None, notify=None, def __init__(self, states, condition=True, action=None, notify=None,
show=True): show=True, confirm=False):
self.states = states # In its simpler form, it is a tuple with 2 self.states = states # In its simpler form, it is a tuple with 2
# states: (fromState, toState). But it can also be a tuple of several # states: (fromState, toState). But it can also be a tuple of several
# (fromState, toState) sub-tuples. This way, you may define only 1 # (fromState, toState) sub-tuples. This way, you may define only 1
@ -2022,6 +2040,7 @@ class Transition:
# notified by email after the transition has been executed. # notified by email after the transition has been executed.
self.show = show # If False, the end user will not be able to trigger self.show = show # If False, the end user will not be able to trigger
# the transition. It will only be possible by code. # the transition. It will only be possible by code.
self.confirm = confirm # If True, a confirm popup will show up.
def getUsedRoles(self): def getUsedRoles(self):
'''self.condition can specify a role.''' '''self.condition can specify a role.'''

View file

@ -809,14 +809,22 @@ class Generator(AbstractGenerator):
poMsg.produceNiceDefault() poMsg.produceNiceDefault()
self.labels.append(poMsg) self.labels.append(poMsg)
for transition in wfDescr.getTransitions(): for transition in wfDescr.getTransitions():
# Get the Appy transition name
tName = wfDescr.getNameOf(transition)
# Get the names of the corresponding DC transition(s)
tNames = wfDescr.getTransitionNamesOf(tName, transition)
if transition.confirm:
# We need to generate a label for the message that will be shown
# in the confirm popup.
for tn in tNames:
label = '%s_%s_confirm' % (wfName, tn)
poMsg = PoMessage(label, '', PoMessage.CONFIRM)
self.labels.append(poMsg)
if transition.notify: if transition.notify:
# Appy will send a mail when this transition is triggered. # Appy will send a mail when this transition is triggered.
# So we need 2 i18n labels for every DC transition corresponding # So we need 2 i18n labels for every DC transition corresponding
# to this Appy transition: one for the mail subject and one for # to this Appy transition: one for the mail subject and one for
# the mail body. # the mail body.
tName = wfDescr.getNameOf(transition) # Appy name
tNames = wfDescr.getTransitionNamesOf(tName, transition) # DC
# name(s)
for tn in tNames: for tn in tNames:
subjectLabel = '%s_%s_mail_subject' % (wfName, tn) subjectLabel = '%s_%s_mail_subject' % (wfName, tn)
poMsg = PoMessage(subjectLabel, '', PoMessage.EMAIL_SUBJECT) poMsg = PoMessage(subjectLabel, '', PoMessage.EMAIL_SUBJECT)

View file

@ -573,6 +573,10 @@ class BaseMixin:
# Get the corresponding Appy transition # Get the corresponding Appy transition
appyTr = workflow._transitionsMapping[transition['id']] appyTr = workflow._transitionsMapping[transition['id']]
if self._appy_showTransition(workflow, appyTr.show): if self._appy_showTransition(workflow, appyTr.show):
transition['confirm'] = ''
if appyTr.confirm:
label = '%s_confirm' % transition['name']
transition['confirm']=self.translate(label, format='js')
res.append(transition) res.append(transition)
return res return res
@ -747,17 +751,17 @@ class BaseMixin:
return True return True
return False return False
def getHistory(self, startNumber=0, reverse=True, includeInvisible=False): def getHistory(self, startNumber=0, reverse=True, includeInvisible=False,
batchSize=5):
'''Returns the history for this object, sorted in reverse order (most '''Returns the history for this object, sorted in reverse order (most
recent change first) if p_reverse is True.''' recent change first) if p_reverse is True.'''
batchSize = 5
key = self.workflow_history.keys()[0] key = self.workflow_history.keys()[0]
history = list(self.workflow_history[key][1:]) history = list(self.workflow_history[key][1:])
if not includeInvisible: if not includeInvisible:
history = [e for e in history if e['comments'] != '_invisible_'] history = [e for e in history if e['comments'] != '_invisible_']
if reverse: history.reverse() if reverse: history.reverse()
return {'events': history[startNumber:startNumber+batchSize], return {'events': history[startNumber:startNumber+batchSize],
'totalNumber': len(history), 'batchSize':batchSize} 'totalNumber': len(history)}
def may(self, transitionName): def may(self, transitionName):
'''May the user execute transition named p_transitionName?''' '''May the user execute transition named p_transitionName?'''
@ -1074,7 +1078,7 @@ class BaseMixin:
return res return res
def translate(self, label, mapping={}, domain=None, default=None, def translate(self, label, mapping={}, domain=None, default=None,
language=None): language=None, format='html'):
'''Translates a given p_label into p_domain with p_mapping.''' '''Translates a given p_label into p_domain with p_mapping.'''
cfg = self.getProductConfig() cfg = self.getProductConfig()
if not domain: domain = cfg.PROJECTNAME if not domain: domain = cfg.PROJECTNAME
@ -1106,8 +1110,13 @@ class BaseMixin:
# If still no result, put the label instead of a translated message # If still no result, put the label instead of a translated message
if not res: res = label if not res: res = label
else: else:
# Perform replacements # Perform replacements, according to p_format.
res = res.replace('\r\n', '<br/>').replace('\n', '<br/>') if format == 'html':
res = res.replace('\r\n', '<br/>').replace('\n', '<br/>')
elif format == 'js':
res = res.replace('\r\n', '').replace('\n', '')
res = res.replace("'", "\\'")
# Perform variable replacements
for name, repl in mapping.iteritems(): for name, repl in mapping.iteritems():
res = res.replace('${%s}' % name, repl) res = res.replace('${%s}' % name, repl)
return res return res

View file

@ -8,6 +8,8 @@
layoutType python:'view'; layoutType python:'view';
putils python: contextObj.plone_utils; putils python: contextObj.plone_utils;
portal python: contextObj.portal_url.getPortalObject(); portal python: contextObj.portal_url.getPortalObject();
portal_url python: contextObj.portal_url();"> portal_url python: contextObj.portal_url();
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
phase phaseInfo/name;">
<metal:callMacro use-macro="python: page.macros[macroName]"/> <metal:callMacro use-macro="python: page.macros[macroName]"/>
</tal:call> </tal:call>

View file

@ -144,9 +144,9 @@
askAjaxChunk(hookId,'GET',objectUrl, 'result', 'queryResult', params); askAjaxChunk(hookId,'GET',objectUrl, 'result', 'queryResult', params);
} }
function askObjectHistory(hookId, objectUrl, startNumber) { function askObjectHistory(hookId, objectUrl, maxPerPage, startNumber) {
// Sends an Ajax request for getting the history of an object // Sends an Ajax request for getting the history of an object
var params = {'startNumber': startNumber}; var params = {'maxPerPage': maxPerPage, 'startNumber': startNumber};
askAjaxChunk(hookId, 'GET', objectUrl, 'page', 'objectHistory', params); askAjaxChunk(hookId, 'GET', objectUrl, 'page', 'objectHistory', params);
} }
@ -207,10 +207,15 @@
} }
} }
// Function used for triggering a workflow transition // Function used for triggering a workflow transition
function triggerTransition(transitionId) { function triggerTransition(transitionId, msg) {
var theForm = document.getElementById('triggerTransitionForm'); var theForm = document.getElementById('triggerTransitionForm');
theForm.workflow_action.value = transitionId; theForm.workflow_action.value = transitionId;
theForm.submit(); if (!msg) {
theForm.submit();
}
else { // Ask the user to confirm.
askConfirm('form', 'triggerTransitionForm', msg);
}
} }
function onDeleteObject(objectUid) { function onDeleteObject(objectUid) {
f = document.getElementById('deleteForm'); f = document.getElementById('deleteForm');
@ -418,12 +423,12 @@
<metal:history define-macro="objectHistory" <metal:history define-macro="objectHistory"
tal:define="startNumber request/startNumber|python:0; tal:define="startNumber request/startNumber|python:0;
startNumber python: int(startNumber); startNumber python: int(startNumber);
historyInfo python: contextObj.getHistory(startNumber); batchSize python: int(request.get('maxPerPage'));
historyInfo python: contextObj.getHistory(startNumber, batchSize=batchSize);
objs historyInfo/events; objs historyInfo/events;
batchSize historyInfo/batchSize;
totalNumber historyInfo/totalNumber; totalNumber historyInfo/totalNumber;
ajaxHookId python:'appyHistory'; ajaxHookId python:'appyHistory';
navBaseCall python: 'askObjectHistory(\'%s\',\'%s\',**v**)' % (ajaxHookId, contextObj.absolute_url()); navBaseCall python: 'askObjectHistory(\'%s\',\'%s\',%d,**v**)' % (ajaxHookId, contextObj.absolute_url(),batchSize);
tool contextObj/getTool"> tool contextObj/getTool">
<tal:comment replace="nothing">Table containing the history</tal:comment> <tal:comment replace="nothing">Table containing the history</tal:comment>
@ -525,7 +530,7 @@
<td align="right" tal:repeat="transition transitions"> <td align="right" tal:repeat="transition transitions">
<input type="button" class="appyButton" <input type="button" class="appyButton"
tal:attributes="value python: tool.translate(transition['name']); tal:attributes="value python: tool.translate(transition['name']);
onClick python: 'triggerTransition(\'%s\')' % transition['id'];"/> onClick python: 'triggerTransition(\'%s\',\'%s\')' % (transition['id'],transition['confirm']);"/>
</td> </td>
</tr> </tr>
</table> </table>
@ -540,6 +545,7 @@
tal:define="showCommonInfo python: layoutType == 'view'; tal:define="showCommonInfo python: layoutType == 'view';
showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type); showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type);
hasHistory contextObj/hasHistory; hasHistory contextObj/hasHistory;
historyMaxPerPage options/maxPerPage|python: 5;
historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded'; historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded';
creator contextObj/Creator" creator contextObj/Creator"
tal:condition="not: contextObj/isTemporary"> tal:condition="not: contextObj/isTemporary">
@ -592,7 +598,7 @@
tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')"> tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">
<div tal:define="ajaxHookId python: contextObj.UID() + '_history';" <div tal:define="ajaxHookId python: contextObj.UID() + '_history';"
tal:attributes="id ajaxHookId"> tal:attributes="id ajaxHookId">
<script language="javascript" tal:content="python: 'askObjectHistory(\'%s\',\'%s\',0)' % (ajaxHookId, contextObj.absolute_url())"> <script language="javascript" tal:content="python: 'askObjectHistory(\'%s\',\'%s\',%d,0)' % (ajaxHookId, contextObj.absolute_url(),historyMaxPerPage)">
</script> </script>
</div> </div>
</span> </span>