diff --git a/src/collective/task/browser/cancel_attribution.py b/src/collective/task/browser/cancel_attribution.py
new file mode 100644
index 0000000..016ca1a
--- /dev/null
+++ b/src/collective/task/browser/cancel_attribution.py
@@ -0,0 +1,103 @@
+
+import z3c.form
+from Products.CMFCore.utils import getToolByName
+from attribute_task import find_nontask
+from collective.task import _
+from pfwbged.policy.subscribers.document import email_notification_of_canceled_subtask
+from plone import api
+from plone.supermodel import model
+from z3c.form import button
+from z3c.form.browser.checkbox import CheckBoxFieldWidget
+from z3c.form.field import Fields
+from z3c.form.interfaces import HIDDEN_MODE
+from zope import schema
+from zope.i18nmessageid import MessageFactory
+from zope.interface import directlyProvides
+from zope.interface import implements
+from zope.schema.interfaces import IContextSourceBinder
+from zope.schema.vocabulary import SimpleVocabulary
+
+PMF = MessageFactory('plone')
+
+
+def get_principal(principal_id):
+ principal = api.user.get(principal_id)
+ if not principal:
+ principal = api.group.get(principal_id)
+ return principal
+
+
+def responsibles_vocabulary(context):
+ acl_users = getToolByName(context, 'acl_users')
+ terms = []
+
+ subtasks = context.listFolderContents()
+ responsible_ids = [subtask.responsible[0] for subtask in subtasks]
+
+ for responsible_id in responsible_ids:
+ group = acl_users.getGroupById(responsible_id)
+ user = acl_users.getUserById(responsible_id)
+ if group:
+ responsible_name = group.getProperty('title')
+ elif user:
+ responsible_name = user.getProperty('fullname')
+ else:
+ responsible_name = responsible_id
+ terms.append(SimpleVocabulary.createTerm(responsible_id, str(responsible_id), responsible_name))
+
+ return SimpleVocabulary(terms)
+
+
+directlyProvides(responsibles_vocabulary, IContextSourceBinder)
+
+
+class ITaskRecipients(model.Schema):
+ """"""
+ responsible = schema.List(
+ value_type=schema.Choice(
+ source=responsibles_vocabulary,
+ ),
+ )
+
+
+class CancelTaskAttribution(z3c.form.form.Form):
+ """Cancel (delete) one or more subtasks
+ """
+
+ implements(z3c.form.interfaces.IFieldsForm)
+ fields = Fields(ITaskRecipients)
+ fields["responsible"].widgetFactory = CheckBoxFieldWidget
+
+ label = _(u'Cancel attribution(s)')
+ description = _(u'Please select attributions to cancel. The responsibles will be notified by mail.')
+ ignoreContext = True
+
+
+ @button.buttonAndHandler(_('Apply'), name='apply')
+ def handleApply(self, action):
+ data, errors = self.extractData()
+ responsibles = data.get('responsible')
+ if not responsibles:
+ return
+ for subtask in self.context.listFolderContents():
+ responsible = subtask.responsible[0]
+ if responsible in responsibles:
+
+ # remove Editor on document
+ document = find_nontask(subtask)
+ local_roles = document.get_local_roles_for_userid(responsible)
+ leftover_roles = set(local_roles).difference(['Editor'])
+ if leftover_roles:
+ # set leftover roles
+ document.manage_setLocalRoles(responsible, leftover_roles)
+ else:
+ # delete local roles for user
+ document.manage_delLocalRoles([responsible])
+
+ # remove relevant subtask
+ self.context.manage_delObjects(subtask.id)
+
+ # notify responsible by mail it's removed
+ email_notification_of_canceled_subtask(subtask)
+
+ self.request.response.redirect(find_nontask(self.context).absolute_url())
diff --git a/src/collective/task/browser/configure.zcml b/src/collective/task/browser/configure.zcml
index 5b28e66..5186e5f 100644
--- a/src/collective/task/browser/configure.zcml
+++ b/src/collective/task/browser/configure.zcml
@@ -19,6 +19,14 @@
permission="zope2.View"
/>
+
+
+
+
Manager
Reviewer
@@ -149,6 +150,12 @@
Editor
+
+ Cancel attribution(s)
+
+ Editor
+
+
Reviewer tasks
Pending (%(count)d)
diff --git a/src/collective/task/subscribers.py b/src/collective/task/subscribers.py
index 67ca473..b04e2d9 100644
--- a/src/collective/task/subscribers.py
+++ b/src/collective/task/subscribers.py
@@ -47,6 +47,14 @@ def task_changed_state(context, event):
pass
parent.reindexObject(idxs=['review_state'])
elif event.new_state.id == 'abandoned':
+ # don't transition parent task if other subtasks are still live
+ sibling_subtasks = set(parent.listFolderContents()).difference([context])
+ if any([
+ task for task in sibling_subtasks
+ if api.content.get_state(task) not in ('abandoned', 'done')
+ ]):
+ return
+
with api.env.adopt_user('admin'):
try:
api.content.transition(obj=parent, transition='subtask-abandoned')