Updated workflow transitions conditions
authorThierry Florac <thierry.florac@onf.fr>
Mon, 11 Sep 2017 14:44:49 +0200
changeset 129 5b02c3b2eaf2
parent 128 6cdea4a15bfd
child 130 a27c1887736f
Updated workflow transitions conditions
src/pyams_content/workflow/__init__.py
--- a/src/pyams_content/workflow/__init__.py	Mon Sep 11 14:43:43 2017 +0200
+++ b/src/pyams_content/workflow/__init__.py	Mon Sep 11 14:44:49 2017 +0200
@@ -17,13 +17,13 @@
 from datetime import datetime
 
 # import interfaces
-from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, MANAGE_CONTENT_PERMISSION, PUBLISH_CONTENT_PERMISSION, \
-    CREATE_CONTENT_PERMISSION
-from pyams_content.shared.common.interfaces import IWfSharedContentRoles, IManagerRestrictions, IWfSharedContent
+from pyams_content.interfaces import MANAGE_SITE_ROOT_PERMISSION, MANAGE_CONTENT_PERMISSION, \
+    PUBLISH_CONTENT_PERMISSION, CREATE_CONTENT_PERMISSION
+from pyams_content.shared.common.interfaces import IWfSharedContentRoles, IManagerRestrictions
 from pyams_content.workflow.interfaces import IContentWorkflow
 from pyams_security.interfaces import IRoleProtectedObject
-from pyams_workflow.interfaces import IWorkflow, AUTOMATIC, IWorkflowPublicationInfo, SYSTEM, IWorkflowVersions, \
-    IWorkflowState, ObjectClonedEvent, IWorkflowInfo, IWorkflowStateLabel
+from pyams_workflow.interfaces import ObjectClonedEvent, IWorkflow, IWorkflowVersions, IWorkflowInfo, \
+    IWorkflowState, IWorkflowStateLabel, IWorkflowPublicationInfo, AUTOMATIC, SYSTEM
 
 # import packages
 from pyams_utils.adapter import adapter_config, ContextAdapter
@@ -66,20 +66,6 @@
               ARCHIVED,
               DELETED)
 
-UPDATE_STATES = (DRAFT, RETIRED)
-
-READONLY_STATES = (ARCHIVED, DELETED)
-
-PROTECTED_STATES = (PUBLISHED, RETIRING, ARCHIVING)
-
-MANAGER_STATES = (PROPOSED, )
-
-VISIBLE_STATES = PUBLISHED_STATES = (PUBLISHED, RETIRING)
-
-WAITING_STATES = (PROPOSED, RETIRING, ARCHIVING)
-
-RETIRED_STATES = (RETIRED, ARCHIVING)
-
 STATES_LABELS = (_("Draft"),
                  _("Proposed"),
                  _("Canceled"),
@@ -91,6 +77,9 @@
                  _("Archived"),
                  _("Deleted"))
 
+STATES_VOCABULARY = SimpleVocabulary([SimpleTerm(STATES_IDS[i], title=t)
+                                      for i, t in enumerate(STATES_LABELS)])
+
 STATES_HEADERS = {DRAFT: _("draft created"),
                   PROPOSED: _("publication requested"),
                   PUBLISHED: _("published"),
@@ -99,8 +88,24 @@
                   ARCHIVING: _("archiving requested"),
                   ARCHIVED: _("archived")}
 
-STATES_VOCABULARY = SimpleVocabulary([SimpleTerm(STATES_IDS[i], title=t)
-                                      for i, t in enumerate(STATES_LABELS)])
+
+UPDATE_STATES = (DRAFT, )
+'''Default state available to contributors in update mode'''
+
+READONLY_STATES = (RETIRED, ARCHIVING, ARCHIVED, DELETED)
+'''Retired and archived contents can't be modified'''
+
+PROTECTED_STATES = (PUBLISHED, RETIRING)
+'''Protected states are available to webmasters in update mode'''
+
+MANAGER_STATES = (PROPOSED, )
+'''Only managers can update proposed contents (if their restrictions apply)'''
+
+VISIBLE_STATES = PUBLISHED_STATES = (PUBLISHED, RETIRING)
+
+WAITING_STATES = (PROPOSED, RETIRING, ARCHIVING)
+
+RETIRED_STATES = (RETIRED, ARCHIVING)
 
 
 #
@@ -109,15 +114,24 @@
 
 def can_propose_content(wf, context):
     """Check if a content can be proposed"""
+    # can't propose content if another proposed version already exists
     versions = IWorkflowVersions(context)
     if versions.has_version(PROPOSED):
         return False
     request = check_request()
+    # grant access to webmaster
     if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, context):
         return True
-    if request.principal.id in context.owner | {context.creator} | context.contributors:
+    # grant access to owner, creator and local contributors
+    principal_id = request.principal.id
+    if principal_id in context.owner | {context.creator} | context.contributors:
         return True
-    return False
+    # grant access to local content managers
+    if principal_id in context.managers:
+        return True
+    # grant access to shared tool managers if restrictions apply
+    restrictions = IManagerRestrictions(context).get_restrictions(principal_id)
+    return restrictions and restrictions.check_access(context, permission=MANAGE_CONTENT_PERMISSION, request=request)
 
 
 def can_backdraft_content(wf, context):
@@ -132,6 +146,7 @@
 
 def can_create_new_version(wf, context):
     """Check if we can create a new version"""
+    # can't create new version when previous draft already exists
     versions = IWorkflowVersions(context)
     if (versions.has_version(DRAFT) or
         versions.has_version(PROPOSED) or
@@ -139,40 +154,73 @@
         versions.has_version(REFUSED)):
         return False
     request = check_request()
+    # grant access to webmaster
     if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, context):
         return True
-    if request.principal.id in context.owner | {context.creator} | context.contributors:
+    # grant access to owner, creator and local contributors
+    principal_id = request.principal.id
+    if principal_id in context.owner | {context.creator} | context.contributors:
         return True
-    return False
+    # grant access to local content managers
+    if principal_id in context.managers:
+        return True
+    # grant access to shared tool managers if restrictions apply
+    restrictions = IManagerRestrictions(context).get_restrictions(principal_id)
+    return restrictions and restrictions.check_access(context, permission=CREATE_CONTENT_PERMISSION, request=request)
 
 
 def can_delete_version(wf, context):
     """Check if we can delete a draft version"""
     request = check_request()
+    # grant access to webmaster
     if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, context):
         return True
-    return request.principal.id in context.owner | {context.creator} | context.contributors
+    # grant access to owner, creator and local contributors
+    principal_id = request.principal.id
+    if principal_id in context.owner | {context.creator} | context.contributors:
+        return True
+    # grant access to local content managers
+    if principal_id in context.managers:
+        return True
+    # grant access to shared tool managers if restrictions apply
+    restrictions = IManagerRestrictions(context).get_restrictions(principal_id)
+    return restrictions and restrictions.check_access(context, permission=MANAGE_CONTENT_PERMISSION, request=request)
 
 
 def can_manage_content(wf, context):
     """Check if a manager can handle content"""
     request = check_request()
+    # grant access to webmaster
     if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, context):
         return True
-    if request.principal.id in context.managers:
+    # local content managers can manage content
+    principal_id = request.principal.id
+    if principal_id in context.managers:
         return True
-    restrictions = IManagerRestrictions(context).get_restrictions(request.principal.id)
+    # shared tool managers can manage content if restrictions apply
+    restrictions = IManagerRestrictions(context).get_restrictions(principal_id)
     return restrictions and restrictions.check_access(context, permission=PUBLISH_CONTENT_PERMISSION, request=request)
 
 
 def can_cancel_operation(wf, context):
     """Check if we can cancel a request"""
     request = check_request()
+    # grant access to webmaster
     if request.has_permission(MANAGE_SITE_ROOT_PERMISSION, context):
         return True
-    if request.principal.id in context.owner | {context.creator} | context.contributors:
+    principal_id = request.principal.id
+    # workflow actor can cancel it's own request
+    if principal_id == IWorkflowState(context).state_principal:
+        return True
+    # owner, creator and contributors can cancel workflow request
+    if principal_id in context.owner | {context.creator} | context.contributors:
         return True
-    return request.principal.id == IWorkflowState(context).state_principal
+    # local content managers can cancel workflow request
+    if principal_id in context.managers:
+        return True
+    # shared tool managers can cancel workflow request if restrictions apply
+    restrictions = IManagerRestrictions(context).get_restrictions(principal_id)
+    return restrictions and restrictions.check_access(context, permission=MANAGE_CONTENT_PERMISSION, request=request)
 
 
 #
@@ -189,7 +237,7 @@
     version_id = IWorkflowState(context).version_id
     for version in IWorkflowVersions(context).get_versions((PUBLISHED, RETIRING, RETIRED, ARCHIVING)):
         if version is not context:
-            IWorkflowInfo(version).fire_transition_toward('archived',
+            IWorkflowInfo(version).fire_transition_toward(ARCHIVED,
                                                           comment=translate(_("Published version {0}")).format(version_id))
 
 
@@ -341,6 +389,13 @@
                                    notify_message=_("A retire request has been submitted for content « {0} »"),
                                    order=7)
 
+published_to_retired = Transition(transition_id='published_to_retired',
+                                  title=_("Retired content"),
+                                  source=PUBLISHED,
+                                  destination=RETIRED,
+                                  trigger=SYSTEM,
+                                  history_label=_("Content retired after passed expiration date"))
+
 retiring_to_published = Transition(transition_id='retiring_to_published',
                                    title=_("Cancel retiring request"),
                                    source=RETIRING,
@@ -515,6 +570,7 @@
                   refused_to_retired,
                   proposed_to_published,
                   published_to_retiring,
+                  published_to_retired,
                   retiring_to_published,
                   retiring_to_retired,
                   retired_to_archiving,
@@ -563,7 +619,7 @@
             request = check_request()
         translate = request.localizer.translate
         state = IWorkflowState(content)
-        if len(state.history) == 1:
+        if len(state.history) <= 2:
             if state.version_id == 1:
                 state_label = translate(STATES_HEADERS[state.state])
             else:
@@ -585,7 +641,8 @@
                      manager_states=MANAGER_STATES,
                      published_states=VISIBLE_STATES,
                      waiting_states=WAITING_STATES,
-                     retired_states=RETIRED_STATES)
+                     retired_states=RETIRED_STATES,
+                     auto_retired_state=RETIRED)
 
 
 @utility_config(name='PyAMS default workflow', provides=IWorkflow)