Added the concept of 'fake' workflow transitions: when a user can't trigger a transition but needs an explanation about why he can't, a fake button is shown, with a explanation.

This commit is contained in:
Gaetan Delannay 2011-02-01 11:09:54 +01:00
parent b48525c5bb
commit 77112c45be
11 changed files with 141 additions and 34 deletions
gen/plone25/mixins

View file

@ -24,6 +24,7 @@ class ToolMixin(BaseMixin):
'''Returns the name of the portal_type that is based on
p_metaTypeOrAppyType.'''
appName = self.getProductConfig().PROJECTNAME
res = metaTypeOrAppyClass
if not isinstance(metaTypeOrAppyClass, basestring):
res = getClassName(metaTypeOrAppyClass, appName)
if res.find('Extensions_appyWrappers') != -1:

View file

@ -5,11 +5,11 @@
# ------------------------------------------------------------------------------
import os, os.path, sys, types, mimetypes, urllib
import appy.gen
from appy.gen import Type, String, Selection, Role
from appy.gen import Type, String, Selection, Role, No
from appy.gen.utils import *
from appy.gen.layout import Table, defaultPageLayouts
from appy.gen.plone25.descriptors import ClassDescriptor
from appy.gen.plone25.utils import updateRolesForPermission
from appy.gen.plone25.utils import updateRolesForPermission,checkTransitionGuard
# ------------------------------------------------------------------------------
class BaseMixin:
@ -342,6 +342,7 @@ class BaseMixin:
def showField(self, name, layoutType='view'):
'''Must I show field named p_name on this p_layoutType ?'''
if name == 'state': return False
return self.getAppyType(name).isShowable(self, layoutType)
def getMethod(self, methodName):
@ -560,24 +561,66 @@ class BaseMixin:
res.append(StateDescr(stateName, stateStatus).get())
return res
def getAppyTransitions(self):
'''Returns the transitions that the user can trigger on p_self.'''
transitions = self.portal_workflow.getTransitionsFor(self)
def getAppyTransitions(self, includeFake=True, includeNotShowable=False):
'''This method is similar to portal_workflow.getTransitionsFor, but:
* is able (or not, depending on boolean p_includeFake) to retrieve
transitions that the user can't trigger, but for which he needs to
know for what reason he can't trigger it;
* is able (or not, depending on p_includeNotShowable) to include
transitions for which show=False at the Appy level. Indeed, because
"showability" is only a GUI concern, and not a security concern,
in some cases it has sense to set includeNotShowable=True, because
those transitions are triggerable from a security point of view;
* the transition-info is richer: it contains fake-related info (as
described above) and confirm-related info (ie, when clicking on
the button, do we ask the user to confirm via a popup?)'''
res = []
if transitions:
# Retrieve the corresponding Appy transition, to check if the user
# may view it.
workflow = self.getWorkflow(appy=True)
if not workflow: return transitions
for transition in transitions:
# 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)
# Get some Plone stuff from the Plone-level config.py
TRIGGER_USER_ACTION = self.getProductConfig().TRIGGER_USER_ACTION
sm = self.getProductConfig().getSecurityManager
# Get the workflow definition for p_obj.
workflow = self.getWorkflow(appy=False)
if not workflow: return res
appyWorkflow = self.getWorkflow(appy=True)
# What is the current state for this object?
currentState = workflow._getWorkflowStateOf(self)
if not currentState: return res
# Analyse all the transitions that start from this state.
for transitionId in currentState.transitions:
transition = workflow.transitions.get(transitionId, None)
appyTr = appyWorkflow._transitionsMapping[transitionId]
if not transition or (transition.trigger_type!=TRIGGER_USER_ACTION)\
or not transition.actbox_name: continue
# We have a possible candidate for a user-triggerable transition
if transition.guard is None:
mayTrigger = True
else:
mayTrigger = checkTransitionGuard(transition.guard, sm(),
workflow, self)
# Compute the condition that will lead to including or not this
# transition
if not includeFake:
includeIt = mayTrigger
else:
includeIt = mayTrigger or isinstance(mayTrigger, No)
if not includeNotShowable:
includeIt = includeIt and appyTr.isShowable(appyWorkflow, self)
if not includeIt: continue
# Add transition-info to the result.
tInfo = {'id': transition.id, 'title': transition.title,
'title_or_id': transition.title_or_id(),
'description': transition.description, 'confirm': '',
'name': transition.actbox_name, 'may_trigger': True,
'url': transition.actbox_url %
{'content_url': self.absolute_url(),
'portal_url' : '', 'folder_url' : ''}}
if appyTr.confirm:
label = '%s_confirm' % tInfo['name']
tInfo['confirm'] = self.translate(label, format='js')
if not mayTrigger:
tInfo['may_trigger'] = False
tInfo['reason'] = mayTrigger.msg
res.append(tInfo)
return res
def getAppyPhases(self, currentOnly=False, layoutType='view'):
@ -947,12 +990,6 @@ class BaseMixin:
return stateShow(workflow, self.appy())
else: return stateShow
def _appy_showTransition(self, workflow, transitionShow):
'''Must I show a transition whose "show value" is p_transitionShow?'''
if callable(transitionShow):
return transitionShow(workflow, self.appy())
else: return transitionShow
def _appy_managePermissions(self):
'''When an object is created or updated, we must update "add"
permissions accordingly: if the object is a folder, we must set on