diff --git a/gen/__init__.py b/gen/__init__.py
index 2287f95..ae9a670 100644
--- a/gen/__init__.py
+++ b/gen/__init__.py
@@ -1317,6 +1317,13 @@ class Boolean(Type):
def getDefaultLayouts(self):
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):
if value: res = obj.translate('yes', domain='plone')
else: res = obj.translate('no', domain='plone')
@@ -1751,7 +1758,8 @@ class Computed(Type):
searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=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
self.method = method
# 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
# will be HTML.
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,
False, show, page, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width,
@@ -1780,7 +1792,13 @@ class Computed(Type):
for name in names[:-1]:
page = getattr(page, name)
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):
'''Computes the value instead of getting it in the database.'''
@@ -2007,7 +2025,7 @@ class State:
class Transition:
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
# states: (fromState, toState). But it can also be a tuple of several
# (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.
self.show = show # If False, the end user will not be able to trigger
# the transition. It will only be possible by code.
+ self.confirm = confirm # If True, a confirm popup will show up.
def getUsedRoles(self):
'''self.condition can specify a role.'''
diff --git a/gen/plone25/generator.py b/gen/plone25/generator.py
index e2a4992..c89e532 100644
--- a/gen/plone25/generator.py
+++ b/gen/plone25/generator.py
@@ -809,14 +809,22 @@ class Generator(AbstractGenerator):
poMsg.produceNiceDefault()
self.labels.append(poMsg)
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:
# Appy will send a mail when this transition is triggered.
# So we need 2 i18n labels for every DC transition corresponding
# to this Appy transition: one for the mail subject and one for
# the mail body.
- tName = wfDescr.getNameOf(transition) # Appy name
- tNames = wfDescr.getTransitionNamesOf(tName, transition) # DC
- # name(s)
for tn in tNames:
subjectLabel = '%s_%s_mail_subject' % (wfName, tn)
poMsg = PoMessage(subjectLabel, '', PoMessage.EMAIL_SUBJECT)
diff --git a/gen/plone25/mixins/__init__.py b/gen/plone25/mixins/__init__.py
index e08e138..546fd1b 100644
--- a/gen/plone25/mixins/__init__.py
+++ b/gen/plone25/mixins/__init__.py
@@ -573,6 +573,10 @@ class BaseMixin:
# Get the corresponding Appy transition
appyTr = workflow._transitionsMapping[transition['id']]
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)
return res
@@ -747,17 +751,17 @@ class BaseMixin:
return True
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
recent change first) if p_reverse is True.'''
- batchSize = 5
key = self.workflow_history.keys()[0]
history = list(self.workflow_history[key][1:])
if not includeInvisible:
history = [e for e in history if e['comments'] != '_invisible_']
if reverse: history.reverse()
return {'events': history[startNumber:startNumber+batchSize],
- 'totalNumber': len(history), 'batchSize':batchSize}
+ 'totalNumber': len(history)}
def may(self, transitionName):
'''May the user execute transition named p_transitionName?'''
@@ -1074,7 +1078,7 @@ class BaseMixin:
return res
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.'''
cfg = self.getProductConfig()
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 not res: res = label
else:
- # Perform replacements
- res = res.replace('\r\n', ' ').replace('\n', ' ')
+ # Perform replacements, according to p_format.
+ if format == 'html':
+ res = res.replace('\r\n', ' ').replace('\n', ' ')
+ elif format == 'js':
+ res = res.replace('\r\n', '').replace('\n', '')
+ res = res.replace("'", "\\'")
+ # Perform variable replacements
for name, repl in mapping.iteritems():
res = res.replace('${%s}' % name, repl)
return res
diff --git a/gen/plone25/skin/callMacro.pt b/gen/plone25/skin/callMacro.pt
index 9e50f64..392385a 100644
--- a/gen/plone25/skin/callMacro.pt
+++ b/gen/plone25/skin/callMacro.pt
@@ -8,6 +8,8 @@
layoutType python:'view';
putils python: contextObj.plone_utils;
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;">
diff --git a/gen/plone25/skin/page.pt b/gen/plone25/skin/page.pt
index 820548b..d9189f2 100644
--- a/gen/plone25/skin/page.pt
+++ b/gen/plone25/skin/page.pt
@@ -144,9 +144,9 @@
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
- var params = {'startNumber': startNumber};
+ var params = {'maxPerPage': maxPerPage, 'startNumber': startNumber};
askAjaxChunk(hookId, 'GET', objectUrl, 'page', 'objectHistory', params);
}
@@ -207,10 +207,15 @@
}
}
// Function used for triggering a workflow transition
- function triggerTransition(transitionId) {
+ function triggerTransition(transitionId, msg) {
var theForm = document.getElementById('triggerTransitionForm');
theForm.workflow_action.value = transitionId;
- theForm.submit();
+ if (!msg) {
+ theForm.submit();
+ }
+ else { // Ask the user to confirm.
+ askConfirm('form', 'triggerTransitionForm', msg);
+ }
}
function onDeleteObject(objectUid) {
f = document.getElementById('deleteForm');
@@ -418,12 +423,12 @@
Table containing the history
@@ -525,7 +530,7 @@