Back to index

plone3  3.1.7
DefaultWorkflow.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ A simple submit/review/publish workflow.
00014 
00015 $Id: DefaultWorkflow.py 77113 2007-06-26 20:36:26Z yuppie $
00016 """
00017 
00018 from AccessControl import ClassSecurityInfo
00019 from Acquisition import aq_base
00020 from Acquisition import aq_inner
00021 from Acquisition import aq_parent
00022 from DateTime import DateTime
00023 from Globals import InitializeClass
00024 from zope.interface import implements
00025 
00026 from Products.CMFCore.interfaces import IWorkflowDefinition
00027 from Products.CMFCore.interfaces.portal_workflow \
00028         import WorkflowDefinition as z2IWorkflowDefinition
00029 from Products.CMFCore.utils import _checkPermission
00030 from Products.CMFCore.utils import _modifyPermissionMappings
00031 from Products.CMFCore.utils import getToolByName
00032 from Products.CMFCore.utils import SimpleItemWithProperties
00033 
00034 from exceptions import AccessControl_Unauthorized
00035 from permissions import ModifyPortalContent
00036 from permissions import RequestReview
00037 from permissions import ReviewPortalContent
00038 from permissions import View
00039 
00040 
00041 class DefaultWorkflowDefinition(SimpleItemWithProperties):
00042 
00043     """ Default workflow definition.
00044     """
00045 
00046     implements(IWorkflowDefinition)
00047     __implements__ = z2IWorkflowDefinition
00048 
00049     meta_type = 'CMF Default Workflow'
00050     id = 'default_workflow'
00051     title = 'Simple Review / Publish Policy'
00052 
00053     security = ClassSecurityInfo()
00054 
00055     def __init__(self, id):
00056         self.id = id
00057 
00058     security.declarePrivate('getReviewStateOf')
00059     def getReviewStateOf(self, ob):
00060         tool = aq_parent(aq_inner(self))
00061         status = tool.getStatusOf(self.getId(), ob)
00062         if status is not None:
00063             review_state = status['review_state']
00064         else:
00065             if hasattr(aq_base(ob), 'review_state'):
00066                 # Backward compatibility.
00067                 review_state = ob.review_state
00068             else:
00069                 review_state = 'private'
00070         return review_state
00071 
00072     security.declarePrivate('getCatalogVariablesFor')
00073     def getCatalogVariablesFor(self, ob):
00074         '''
00075         Allows this workflow to make workflow-specific variables
00076         available to the catalog, making it possible to implement
00077         queues in a simple way.
00078         Returns a mapping containing the catalog variables
00079         that apply to ob.
00080         '''
00081         return {'review_state': self.getReviewStateOf(ob)}
00082 
00083     security.declarePrivate('listObjectActions')
00084     def listObjectActions(self, info):
00085         '''
00086         Allows this workflow to
00087         include actions to be displayed in the actions box.
00088         Called only when this workflow is applicable to
00089         info.object.
00090         Returns the actions to be displayed to the user.
00091         '''
00092         if info.isAnonymous:
00093             return None
00094 
00095         # The following operation is quite expensive.
00096         # We don't need to perform it if the user
00097         # doesn't have the required permission.
00098         content = info.object
00099         content_url = info.object_url
00100         content_creator = content.Creator()
00101         pm = getToolByName(self, 'portal_membership')
00102         current_user = pm.getAuthenticatedMember().getId()
00103         review_state = self.getReviewStateOf(content)
00104         actions = []
00105 
00106         allow_review = _checkPermission(ReviewPortalContent, content)
00107         allow_request = _checkPermission(RequestReview, content)
00108 
00109         append_action = (lambda name, p, url=content_url, a=actions.append:
00110                          a({'name': name,
00111                             'url': url + '/' + p,
00112                             'permissions': (),
00113                             'category': 'workflow'}))
00114 
00115         show_reject = 0
00116         show_retract = 0
00117 
00118         if review_state == 'private':
00119             if allow_review:
00120                 append_action('Publish', 'content_publish_form')
00121             elif allow_request:
00122                 append_action('Submit', 'content_submit_form')
00123 
00124         elif review_state == 'pending':
00125             if content_creator == current_user and allow_request:
00126                 show_retract = 1
00127             if allow_review:
00128                 append_action('Publish', 'content_publish_form')
00129                 show_reject = 1
00130 
00131         elif review_state == 'published':
00132             if content_creator == current_user and allow_request:
00133                 show_retract = 1
00134             if allow_review:
00135                 show_reject = 1
00136 
00137         if show_retract:
00138             append_action('Retract', 'content_retract_form')
00139         if show_reject:
00140             append_action('Reject', 'content_reject_form')
00141         if allow_review or allow_request:
00142             append_action('Status history', 'content_status_history')
00143 
00144         return actions
00145 
00146     security.declarePrivate('listGlobalActions')
00147     def listGlobalActions(self, info):
00148         '''
00149         Allows this workflow to include actions to be displayed
00150         in the actions box.  Called on every request.
00151 
00152         Returns the actions to be displayed to the user.
00153         '''
00154         if info.isAnonymous:
00155             return None
00156 
00157         actions = []
00158         catalog = getToolByName(self, 'portal_catalog', None)
00159         if catalog is None:
00160             return actions
00161 
00162         pending = len(catalog.searchResults(review_state='pending'))
00163         if pending > 0:
00164             actions.append(
00165                 {'name': 'Pending review (%d)' % pending,
00166                  'url': info.portal_url +
00167                  '/search?review_state=pending',
00168                  'permissions': (ReviewPortalContent, ),
00169                  'category': 'global'}
00170                 )
00171 
00172         return actions
00173 
00174     security.declarePrivate('isActionSupported')
00175     def isActionSupported(self, ob, action, **kw):
00176         '''
00177         Returns a true value if the given action name is supported.
00178         '''
00179         return (action in ('submit', 'retract', 'publish', 'reject',))
00180 
00181     security.declarePrivate('doActionFor')
00182     def doActionFor(self, ob, action, comment=''):
00183         '''
00184         Allows the user to request a workflow action.  This method
00185         must perform its own security checks.
00186         '''
00187         allow_review = _checkPermission(ReviewPortalContent, ob)
00188         allow_request = _checkPermission(RequestReview, ob)
00189         review_state = self.getReviewStateOf(ob)
00190         tool = aq_parent(aq_inner(self))
00191 
00192         if action == 'submit':
00193             if not allow_request:
00194                 raise AccessControl_Unauthorized('Not authorized')
00195             elif review_state != 'private':
00196                 raise AccessControl_Unauthorized('Already in submit state')
00197             self.setReviewStateOf(ob, 'pending', action, comment)
00198 
00199         elif action == 'retract':
00200             if not allow_request:
00201                 raise AccessControl_Unauthorized('Not authorized')
00202             elif review_state == 'private':
00203                 raise AccessControl_Unauthorized('Already private')
00204             content_creator = ob.Creator()
00205             pm = getToolByName(self, 'portal_membership')
00206             current_user = pm.getAuthenticatedMember().getId()
00207             if (content_creator != current_user) and not allow_review:
00208                 raise AccessControl_Unauthorized('Not creator or reviewer')
00209             self.setReviewStateOf(ob, 'private', action, comment)
00210 
00211         elif action == 'publish':
00212             if not allow_review:
00213                 raise AccessControl_Unauthorized('Not authorized')
00214             self.setReviewStateOf(ob, 'published', action, comment)
00215 
00216         elif action == 'reject':
00217             if not allow_review:
00218                 raise AccessControl_Unauthorized('Not authorized')
00219             self.setReviewStateOf(ob, 'private', action, comment)
00220 
00221     security.declarePrivate('isInfoSupported')
00222     def isInfoSupported(self, ob, name):
00223         '''
00224         Returns a true value if the given info name is supported.
00225         '''
00226         return (name in ('review_state', 'review_history'))
00227 
00228     security.declarePrivate('getInfoFor')
00229     def getInfoFor(self, ob, name, default):
00230         '''
00231         Allows the user to request information provided by the
00232         workflow.  This method must perform its own security checks.
00233         '''
00234         # Treat this as public.
00235         if name == 'review_state':
00236             return self.getReviewStateOf(ob)
00237 
00238         allow_review = _checkPermission(ReviewPortalContent, ob)
00239         allow_request = _checkPermission(RequestReview, ob)
00240         if not allow_review and not allow_request:
00241             return default
00242 
00243         elif name == 'review_history':
00244             tool = aq_parent(aq_inner(self))
00245             history = tool.getHistoryOf(self.getId(), ob)
00246             # Make copies for security.
00247             return tuple(map(lambda dict: dict.copy(), history))
00248 
00249     security.declarePrivate('setReviewStateOf')
00250     def setReviewStateOf(self, ob, review_state, action, comment):
00251         tool = aq_parent(aq_inner(self))
00252         pm = getToolByName(self, 'portal_membership')
00253         current_user = pm.getAuthenticatedMember().getId()
00254         status = {
00255             'actor': current_user,
00256             'action': action,
00257             'review_state': review_state,
00258             'time': DateTime(),
00259             'comments': comment,
00260             }
00261         tool.setStatusOf(self.getId(), ob, status)
00262         self.updateRoleMappingsFor(ob)
00263 
00264     security.declarePrivate('notifyCreated')
00265     def notifyCreated(self, ob):
00266         '''
00267         Notifies this workflow after an object has been created
00268         and put in its new place.
00269         '''
00270         self.setReviewStateOf( ob, 'private', 'created', '' )
00271         self.notifySuccess(ob, 'created', '')
00272 
00273     security.declarePrivate('notifyBefore')
00274     def notifyBefore(self, ob, action):
00275         '''
00276         Notifies this workflow of an action before it happens,
00277         allowing veto by exception.  Unless an exception is thrown, either
00278         a notifySuccess() or notifyException() can be expected later on.
00279         The action usually corresponds to a method name.
00280         '''
00281         pass
00282 
00283     security.declarePrivate('notifySuccess')
00284     def notifySuccess(self, ob, action, result):
00285         '''
00286         Notifies this workflow that an action has taken place.
00287         '''
00288         pass
00289 
00290     security.declarePrivate('notifyException')
00291     def notifyException(self, ob, action, exc):
00292         '''
00293         Notifies this workflow that an action failed.
00294         '''
00295         pass
00296 
00297     security.declarePrivate('updateRoleMappingsFor')
00298     def updateRoleMappingsFor(self, ob):
00299         '''
00300         Changes the object permissions according to the current
00301         review_state.
00302         '''
00303         review_state = self.getReviewStateOf(ob)
00304         if review_state == 'private':
00305             anon_view = 0
00306             owner_modify = 1
00307             reviewer_view = 0
00308         elif review_state == 'pending':
00309             anon_view = 0
00310             owner_modify = 0  # Require a retraction for editing.
00311             reviewer_view = 1
00312         elif review_state == 'published':
00313             anon_view = 1
00314             owner_modify = 0
00315             reviewer_view = 1
00316         else:   # This object is in an unknown state
00317             anon_view = 0
00318             owner_modify = 1
00319             reviewer_view = 0
00320 
00321         # Modify role to permission mappings directly.
00322 
00323         new_map = { View: {'Anonymous': anon_view,
00324                            'Reviewer': reviewer_view,
00325                            'Owner': 1}
00326                   , ModifyPortalContent: {'Owner': owner_modify}
00327                   }
00328         return _modifyPermissionMappings(ob, new_map)
00329 
00330 InitializeClass(DefaultWorkflowDefinition)