From b48525c5bb0ca3258ab69d1887aedee1b17eda3b Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Sat, 29 Jan 2011 02:18:14 +0100 Subject: [PATCH] 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. --- gen/__init__.py | 25 ++++++++++++++++++++++--- gen/plone25/generator.py | 14 +++++++++++--- gen/plone25/mixins/__init__.py | 21 +++++++++++++++------ gen/plone25/skin/callMacro.pt | 4 +++- gen/plone25/skin/page.pt | 24 +++++++++++++++--------- 5 files changed, 66 insertions(+), 22 deletions(-) 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 @@ + onClick python: 'triggerTransition(\'%s\',\'%s\')' % (transition['id'],transition['confirm']);"/> @@ -540,6 +545,7 @@ tal:define="showCommonInfo python: layoutType == 'view'; showWorkflow python: tool.getAttr('showWorkflowFor' + contextObj.meta_type); hasHistory contextObj/hasHistory; + historyMaxPerPage options/maxPerPage|python: 5; historyExpanded python: tool.getCookieValue('appyHistory', default='collapsed') == 'expanded'; creator contextObj/Creator" tal:condition="not: contextObj/isTemporary"> @@ -592,7 +598,7 @@ tal:attributes="style python:test(historyExpanded, 'display:block', 'display:none')">
-