diff --git a/src/pfwbged/policy/browser/guards.py b/src/pfwbged/policy/browser/guards.py index ceac13b..4d3aa62 100644 --- a/src/pfwbged/policy/browser/guards.py +++ b/src/pfwbged/policy/browser/guards.py @@ -107,6 +107,40 @@ class CanValidateOrRefuse(grok.View): return False +class CanCancelRefusal(grok.View): + grok.name("can_cancel_refusal") + grok.context(IDmsFile) + grok.require('zope2.View') + + def render(self): + + workflow = api.portal.get_tool('portal_workflow') + with api.env.adopt_user('admin'): + review_history = workflow.getInfoFor(self.context, 'review_history') + if not review_history: + return False + last_transition = review_history[-1] + return last_transition.get('action') == 'refuse' and \ + last_transition.get('actor') == api.user.get_current().id + + +class CanCancelValidation(grok.View): + grok.name("can_cancel_validation") + grok.context(IDmsFile) + grok.require('zope2.View') + + def render(self): + + workflow = api.portal.get_tool('portal_workflow') + with api.env.adopt_user('admin'): + review_history = workflow.getInfoFor(self.context, 'review_history') + if not review_history: + return False + last_transition = review_history[-1] + return last_transition.get('action') == 'validate' and \ + last_transition.get('actor') == api.user.get_current().id + + class CanBeTrashedDmsFile(grok.View): """""" grok.name('can_be_trashed') diff --git a/src/pfwbged/policy/locales/fr/LC_MESSAGES/pfwbged.policy.po b/src/pfwbged/policy/locales/fr/LC_MESSAGES/pfwbged.policy.po index 64af93f..1abf0af 100644 --- a/src/pfwbged/policy/locales/fr/LC_MESSAGES/pfwbged.policy.po +++ b/src/pfwbged/policy/locales/fr/LC_MESSAGES/pfwbged.policy.po @@ -27,7 +27,15 @@ msgstr "L'élément ${title} a été supprimé." msgid "${title} is locked and cannot be deleted." msgstr "L'élément ${title} est verrouillé et ne peut être supprimé." -#: ../subscribers/document.py:430 +#: ../subscribers/document.py:417 +msgid "A previously refused version has returned to waiting validation" +msgstr "Un document précédemment refusé est revenu en attente de validation" + +#: ../subscribers/document.py:415 +msgid "A previously validated version has returned to waiting validation" +msgstr "Un document précédemment validé est revenu en attente de validation" + +#: ../subscribers/document.py:495 msgid "A validation request has been refused" msgstr "Une demande de validation a été refusée." @@ -86,10 +94,18 @@ msgstr "Visible par" msgid "Cancel" msgstr "Annuler" +#: ../menu.py:58 +msgid "Cancel refusal of ${version}" +msgstr "Annuler le refus de la version ${version}" + #: ../menu.py:56 msgid "Cancel validate and finish ${version}" msgstr "Annuler la validation et finalisation de la version ${version}" +#: ../menu.py:57 +msgid "Cancel validation of ${version}" +msgstr "Annuler la validation de la version ${version}" + #: ../browser/refuse.py:33 #: ../browser/send_by_email.py:35 #: ../browser/to_process.py:25 diff --git a/src/pfwbged/policy/locales/fr/LC_MESSAGES/plone.po b/src/pfwbged/policy/locales/fr/LC_MESSAGES/plone.po index 3fd2699..e54e683 100644 --- a/src/pfwbged/policy/locales/fr/LC_MESSAGES/plone.po +++ b/src/pfwbged/policy/locales/fr/LC_MESSAGES/plone.po @@ -61,6 +61,14 @@ msgstr "Retour en à traiter" msgid "Back to writing" msgstr "Retour à la rédaction" +#: ../profiles/default/workflows/versionnote_workflow/definition.xml +msgid "Cancel refusal" +msgstr "Annuler le refus" + +#: ../profiles/default/workflows/versionnote_workflow/definition.xml +msgid "Cancel validation" +msgstr "Annuler la validation" + #: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml #: ../profiles/default/workflows/incomingmail_workflow/definition.xml #: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml diff --git a/src/pfwbged/policy/locales/pfwbged.policy.pot b/src/pfwbged/policy/locales/pfwbged.policy.pot index 2f63f89..8d7a1f4 100644 --- a/src/pfwbged/policy/locales/pfwbged.policy.pot +++ b/src/pfwbged/policy/locales/pfwbged.policy.pot @@ -29,7 +29,15 @@ msgstr "" msgid "${title} is locked and cannot be deleted." msgstr "" -#: ../subscribers/document.py:430 +#: ../subscribers/document.py:417 +msgid "A previously refused version has returned to waiting validation" +msgstr "" + +#: ../subscribers/document.py:415 +msgid "A previously validated version has returned to waiting validation" +msgstr "" + +#: ../subscribers/document.py:495 msgid "A validation request has been refused" msgstr "" @@ -88,10 +96,18 @@ msgstr "" msgid "Cancel" msgstr "" +#: ../menu.py:58 +msgid "Cancel refusal of ${version}" +msgstr "" + #: ../menu.py:56 msgid "Cancel validate and finish ${version}" msgstr "" +#: ../menu.py:57 +msgid "Cancel validation of ${version}" +msgstr "" + #: ../browser/refuse.py:33 #: ../browser/send_by_email.py:35 #: ../browser/to_process.py:25 diff --git a/src/pfwbged/policy/locales/plone.pot b/src/pfwbged/policy/locales/plone.pot index 1fc88aa..6d82c8e 100644 --- a/src/pfwbged/policy/locales/plone.pot +++ b/src/pfwbged/policy/locales/plone.pot @@ -62,6 +62,14 @@ msgstr "" msgid "Back to writing" msgstr "" +#: ../profiles/default/workflows/versionnote_workflow/definition.xml +msgid "Cancel refusal" +msgstr "" + +#: ../profiles/default/workflows/versionnote_workflow/definition.xml +msgid "Cancel validation" +msgstr "" + #: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml #: ../profiles/default/workflows/incomingmail_workflow/definition.xml #: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml diff --git a/src/pfwbged/policy/menu.py b/src/pfwbged/policy/menu.py index e9a31b0..16769a4 100644 --- a/src/pfwbged/policy/menu.py +++ b/src/pfwbged/policy/menu.py @@ -54,6 +54,8 @@ dmsfile_wfactions_mapping = {'ask_opinion': _(u"Ask opinion about version ${vers 'send_with_docbow': _(u"Send version ${version} with PES"), 'restore_from_trash': _(u"Restore version ${version}"), 'back_to_draft': _(u"Cancel validate and finish ${version}"), + 'cancel-validation': _(u"Cancel validation of ${version}"), + 'cancel-refusal': _(u"Cancel refusal of ${version}"), } @@ -232,6 +234,21 @@ class CustomMenu(menu.WorkflowMenu): title = IGNORE_(title, mapping={'version': version}) cssClass += ' version-action version-id-%s' % context.id cssClass += ' version-action-%s' % action['id'] + + # limit cancelling actions to a version's rightful owner + # guards are bypassed for Managers, so call them at least once + action_guards = { + 'cancel-refusal': 'can_cancel_refusal', + 'cancel-validation': 'can_cancel_validation', + } + if action['id'] in action_guards: + view = queryMultiAdapter( + (context, request), + name=action_guards.get(action['id']) + ) + if view and not view.render(): + action['allowed'] = False + else: title = action['title'] diff --git a/src/pfwbged/policy/profiles/default/workflows/versionnote_workflow/definition.xml b/src/pfwbged/policy/profiles/default/workflows/versionnote_workflow/definition.xml index 60e50c8..3dd09f4 100644 --- a/src/pfwbged/policy/profiles/default/workflows/versionnote_workflow/definition.xml +++ b/src/pfwbged/policy/profiles/default/workflows/versionnote_workflow/definition.xml @@ -16,6 +16,7 @@ + Editor Manager @@ -109,6 +110,7 @@ + Editor Manager @@ -203,6 +205,18 @@ here/@@can_validate_or_refuse + + Cancel validation + + here/@@can_cancel_validation + + + + Cancel validation + + here/@@can_cancel_refusal + + Previous transition diff --git a/src/pfwbged/policy/subscribers/document.py b/src/pfwbged/policy/subscribers/document.py index 516419a..6f47610 100644 --- a/src/pfwbged/policy/subscribers/document.py +++ b/src/pfwbged/policy/subscribers/document.py @@ -100,6 +100,16 @@ def change_validation_state(context, event): if api.content.get_state(validation) == 'todo': api.content.transition(validation, 'validate') validation.reindexObject(idxs=['review_state']) + elif event.transition.id == 'cancel-validation': + for ref in catalog.findRelations(query): + validation = ref.from_object + api.content.transition(validation, 'cancel-validation') + validation.reindexObject(idxs=['review_state']) + elif event.transition.id == 'cancel-refusal': + for ref in catalog.findRelations(query): + validation = ref.from_object + api.content.transition(validation, 'cancel-refusal') + validation.reindexObject(idxs=['review_state']) @grok.subscribe(IDmsFile, IObjectWillBeRemovedEvent) @@ -397,6 +407,63 @@ def email_notification_of_tasks(context, event): job = async.queueJob(email_notification_of_tasks_sync, **kwargs) +@grok.subscribe(IValidation, IAfterTransitionEvent) +def email_notification_of_validation_reversal(context, event): + """Notify a validation requester when their previously validated + (or refused) request has returned to pending state""" + if not event.transition: + return + elif event.transition.id == 'cancel-validation': + comment = translate(_('A previously validated version has returned to waiting validation'), context=context.REQUEST) + elif event.transition.id == 'cancel-refusal': + comment = translate(_('A previously refused version has returned to waiting validation'), context=context.REQUEST) + else: + return + + # go up in the acquisition chain to find the document + document = None + for obj in aq_chain(context): + obj = aq_parent(obj) + if IDmsDocument.providedBy(obj): + document = obj + break + if not document: + return + + email_enquirer = None + for enquirer in (context.enquirer or []): + member = context.portal_membership.getMemberById(enquirer) + if member: + email_enquirer = member.getProperty('email', None) + if email_enquirer: + break + + if not email_enquirer: + return + + email_from = api.user.get_current().email or api.portal.get().getProperty('email_from_address') or 'admin@localhost' + + subject = '%s - %s' % (context.title, document.title) + + body = comment + \ + '\n\n' + \ + translate(_('Title: %s'), context=context.REQUEST) % context.title + \ + '\n\n' + \ + translate(_('Document: %s'), context=context.REQUEST) % document.title + \ + '\n\n' + \ + translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url() + + body += '\n\n\n-- \n' + translate(_('Sent by GED')) + body = body.encode('utf-8') + + try: + context.MailHost.send(body, email_enquirer, email_from, subject, charset='utf-8') + except Exception as e: + # do not abort transaction in case of email error + log = logging.getLogger('pfwbged.policy') + log.exception(e) + + @grok.subscribe(IValidation, IAfterTransitionEvent) def email_notification_of_refused_task(context, event): if event.new_state.id != 'refused':