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:
parent
90553381a3
commit
b48525c5bb
|
@ -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.'''
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue