Compare commits

...
This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.

24 Commits

Author SHA1 Message Date
Nicolas Demonte e23e31aca0 Do task deletions through a taskqueue #4753 2020-12-21 12:32:45 +01:00
Nicolas Demonte 656681290a Set Memorandum & Notice as Ready to send when finishing a version #48248
Same behavior as Outgoing mail
2020-12-04 16:50:59 +01:00
Nicolas Demonte f960f15ed2 Remove search input above documents table #37864 2020-11-09 15:42:56 +01:00
Nicolas Demonte e638aa2caf Revert "Disable live search (upgrade step) #37864"
This reverts commit a60881fa
2020-11-09 13:45:11 +01:00
Nicolas Demonte 5982a97bbb Fix edge case on task subscriber #47329 2020-10-27 13:39:21 +01:00
Nicolas Demonte a60881fac6 Disable live search (upgrade step) #37864 2020-10-06 13:17:01 +02:00
Nicolas Demonte 20e957d2a4 Make saved searches private by default #4566 2020-10-06 13:17:01 +02:00
Nicolas Demonte d82a32792c Send email to task enquirer when it is marked as done #32827 2020-10-06 13:17:01 +02:00
Nicolas Demonte 998f04473c Create user group allowing reader access to all mails #23648 2020-10-06 13:17:01 +02:00
Laurent Lasudry 9171319411 Add collective.impersonate 2020-06-22 02:30:38 +02:00
Nicolas Demonte e1b3003109 Fix select2 results hidden in background (Firefox only) #38844 2020-01-29 11:27:13 +01:00
Nicolas Demonte ef4ffcc5fa Fix folder path #32829 2020-01-24 10:25:20 +01:00
Nicolas Demonte c555b03dec Add multi-action to create links #32829 2020-01-24 09:16:20 +01:00
Nicolas Demonte 3987ec9e82 Allow custom address in email selection #38844 2020-01-20 09:10:22 +01:00
Nicolas Demonte fb769655c1 Set conditions to create a board decision from a note for board #38884
- User is a member of group Gestion-secretariat-general
- There is no existing board decision related to that note for board
2020-01-17 10:17:28 +01:00
Nicolas Demonte d24bb396d1 Build absolute url used in taskqueue subrequests #38725
requires an environment variable: ROOT_URL
2020-01-03 11:51:45 +01:00
Nicolas Demonte 6530bedf9a Handle unavailable answer transition #22013 2019-12-11 09:43:37 +01:00
Nicolas Demonte dd1fe898df Create a follow-up mail from a board decision #22013 2019-12-11 09:11:06 +01:00
Nicolas Demonte 9e3ced236e Remove APF content types #22019 2019-12-09 14:54:10 +01:00
Nicolas Demonte f85614073d Add 5 PFWB content types #22019
- pfwb.copminutes
- pfwb.copnote
- pfwb.informationnote
- pfwb.memorandum
- pfwb.notice
2019-12-09 14:54:10 +01:00
Nicolas Demonte ec6af89840 Multi-select emails from LDAP in Send by email view #32825 2019-12-04 11:28:00 +01:00
Nicolas Demonte 7720ab11bd Create an outgoing mail from a board decision #22013 2019-11-21 17:37:10 +01:00
Nicolas Demonte 4213ecfd77 Create a board decision from a note for board #22013 2019-11-21 17:25:57 +01:00
Nicolas Demonte 9be18c4b00 Execute multi-actions in the background #33748 2019-10-25 14:29:32 +02:00
32 changed files with 971 additions and 495 deletions

View File

@ -51,8 +51,10 @@ setup(
'collective.edm.listing',
'collective.externaleditor',
'collective.onlogin',
'collective.select2',
'collective.solr',
'collective.task',
'collective.taskqueue',
'five.grok',
'pfwbged.basecontent',
'pfwbged.collection',
@ -62,6 +64,7 @@ setup(
'plone.app.contenttypes',
'z3c.jbot',
'Products.AROfficeTransforms',
'collective.impersonate',
],
extras_require={
'test': [

View File

@ -1,8 +1,14 @@
import pickle
from copy import deepcopy
from Products.Five.browser import BrowserView
import z3c.form
from collective.taskqueue import taskqueue
from z3c.form import button
import zope.event
from zope.component import getUtility
from zope.i18nmessageid.message import MessageFactory
from plone import api
@ -13,33 +19,14 @@ from plone.stringinterp.adapters import _recursiveGetMembersFromIds
from collective.task import _
class AddInformation(DefaultAddForm):
"""Custom add information view"""
portal_type = "information"
@property
def action(self):
return self.request.getURL() + '?documents=' + self.request.documents
@button.buttonAndHandler(_('Add'), name='add')
def handleAdd(self, action):
data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
for document_id in self.request.documents.split(','):
base_document = api.content.get(str(document_id))
self.add_info(base_document, data)
self._finishedAdd = True
return
def add_info(self, base_document, data):
class BackgroundAddInformationView(BrowserView):
def __call__(self):
base_document = self.context
data = pickle.load(self.request.stdin)
portal = api.portal.get()
objs = []
seen = {}
for responsible in data['responsible']:
group = api.group.get(responsible)
if group is not None:
@ -63,4 +50,28 @@ class AddInformation(DefaultAddForm):
createContentInContainer(base_document, 'information', **_data)
seen[responsible] = True
print 'seen:', seen
class AddInformation(DefaultAddForm):
"""Custom add information view"""
portal_type = "information"
@property
def action(self):
return self.request.getURL() + '?documents=' + self.request.documents
@button.buttonAndHandler(_('Add'), name='add')
def handleAdd(self, action):
data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
for document_id in self.request.documents.split(','):
taskqueue.add(
'{}/background_add_information'.format(document_id),
payload=pickle.dumps(data),
)
self._finishedAdd = True
return

View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
from Products.Five.browser import BrowserView
from collective.task import _
from collective.taskqueue import taskqueue
from plone import api
from plone.dexterity.browser.add import DefaultAddForm
from plone.dexterity.utils import createContentInContainer
from z3c.form import button
from z3c.relationfield.relation import RelationValue
from zope.component import getUtility
from zope.intid import IIntIds
class BackgroundAddLinkView(BrowserView):
def __call__(self):
base_document = self.context
folder_path = self.request.form['folder']
portal = api.portal.get()
folder = portal.restrictedTraverse(folder_path)
intids = getUtility(IIntIds)
relation = RelationValue(to_id=intids.getId(folder))
new_link = createContentInContainer(
base_document,
'pfwbgedlink',
folder=relation,
)
class AddLinks(DefaultAddForm):
"Add multiple pfwbgedlinks"
portal_type = "pfwbgedlink"
@property
def label(self):
return u"Classer dans un dossier"
@property
def action(self):
return self.request.getURL() + '?documents=' + self.request.documents
@button.buttonAndHandler(_('Add'), name='add')
def handleAdd(self, action):
data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
folder_path = data['folder'].absolute_url_path().lstrip("/")
for document_id in self.request.documents.split(','):
taskqueue.add(
'{}/background_add_link'.format(document_id),
params={'folder': folder_path},
)
self._finishedAdd = True
return

View File

@ -1,19 +1,53 @@
import pickle
from Products.Five.browser import BrowserView
from zope.i18n import translate
from collective.taskqueue import taskqueue
from copy import deepcopy
from plone import api
from plone.dexterity.browser.add import DefaultAddForm
from plone.dexterity.utils import addContentToContainer, getAdditionalSchemata
from plone.dexterity.utils import createContent
from z3c.form.interfaces import HIDDEN_MODE
from zope.annotation.interfaces import IAnnotations
import zope.event
import zope.lifecycleevent
from z3c.form import button
from pfwbged.policy import _
class BackgroundAskValidationView(BrowserView):
def __call__(self):
base_document = self.context
data = pickle.load(self.request.stdin)
for child in reversed(base_document.values()):
if child.portal_type == 'dmsmainfile':
last_version = child
break
_data = deepcopy(data)
_data['title'] = translate(
_(u"Validation application for version ${version}",
mapping={'version': last_version.Title()}),
context=self.request)
_data['ITarget.target'] = last_version
new_validation = createContent('validation', **_data)
addContentToContainer(base_document, new_validation)
# annotate the validation task with the related version, it can later
# be used to match the task against the correct version.
annotations = IAnnotations(new_validation)
annotations['related_version_id'] = last_version.id
# execute transition on version
api.content.transition(last_version, transition='submit')
last_version.reindexObject(idxs=['review_state'])
class AskValidations(DefaultAddForm):
"""Ask validation custom add form
"""
@ -49,34 +83,10 @@ class AskValidations(DefaultAddForm):
return
for document_id in self.request.documents.split(','):
base_document = api.content.get(str(document_id))
self.ask_validation(base_document, data)
taskqueue.add(
'{}/background_ask_validation'.format(document_id),
payload=pickle.dumps(data),
)
self._finishedAdd = True
return
def ask_validation(self, base_document, data):
for child in reversed(base_document.values()):
if child.portal_type == 'dmsmainfile':
last_version = child
break
_data = deepcopy(data)
_data['title'] = translate(
_(u"Validation application for version ${version}",
mapping={'version': last_version.Title()}),
context=self.request)
_data['ITarget.target'] = last_version
new_validation = self.create(_data)
zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(new_validation))
addContentToContainer(base_document, new_validation)
# annotate the validation task with the related version, it can later
# be used to match the task against the correct version.
annotations = IAnnotations(new_validation)
annotations['related_version_id'] = last_version.id
# execute transition on version
api.content.transition(last_version, transition='submit')
last_version.reindexObject(idxs=['review_state'])

View File

@ -64,6 +64,14 @@
permission="cmf.AddPortalContent"
/>
<browser:page
name="background_add_information"
class=".add_multi_information.BackgroundAddInformationView"
for="*"
permission="cmf.AddPortalContent"
layer="collective.taskqueue.interfaces.ITaskQueueLayer"
/>
<browser:page
name="multi_attribute_task"
for="*"
@ -71,6 +79,29 @@
permission="cmf.AddPortalContent"
/>
<browser:page
name="background_attribute_task"
class=".multi_attribute_task.BackgroundAttributeTaskView"
for="*"
permission="cmf.AddPortalContent"
layer="collective.taskqueue.interfaces.ITaskQueueLayer"
/>
<browser:page
name="add_multi_link"
for="*"
class=".add_multi_link.AddLinks"
permission="cmf.AddPortalContent"
/>
<browser:page
name="background_add_link"
class=".add_multi_link.BackgroundAddLinkView"
for="*"
permission="cmf.AddPortalContent"
layer="collective.taskqueue.interfaces.ITaskQueueLayer"
/>
<browser:page
name="ask_multi_validation"
for="*"
@ -78,6 +109,16 @@
permission="cmf.AddPortalContent"
/>
<browser:page
name="background_ask_validation"
class=".ask_multi_validation.BackgroundAskValidationView"
for="*"
permission="cmf.AddPortalContent"
layer="collective.taskqueue.interfaces.ITaskQueueLayer"
/>
<!-- Custom comments viewlet for IBaseTask -->
<browser:viewlet
name="plone.comments"
@ -174,4 +215,21 @@
permission="cmf.ManagePortal"
/>
<!-- Outgoing mail add form -->
<adapter
for="Products.CMFCore.interfaces.IFolderish
zope.publisher.interfaces.browser.IDefaultBrowserLayer
plone.dexterity.interfaces.IDexterityFTI"
provides="zope.publisher.interfaces.browser.IBrowserPage"
factory="pfwbged.policy.browser.create_outgoing_mail.AddView"
name="dmsoutgoingmail"
/>
<class class="pfwbged.policy.browser.create_outgoing_mail.AddView">
<require
permission="cmf.AddPortalContent"
interface="zope.publisher.interfaces.browser.IBrowserPage"
/>
</class>
</configure>

View File

@ -0,0 +1,80 @@
from five import grok
from pfwbged.basecontent.types import INoteForBoard
from plone import api
from zc.relation.interfaces import ICatalog
from zope.component import getUtility
from zope.intid.interfaces import IIntIds
class CanCreateBoardDecision(grok.View):
"""Create a board decision from a note for board"""
grok.name('can_create_board_decision')
grok.context(INoteForBoard)
grok.require("zope2.View")
def in_gestion_sg(self):
return any([group for group in api.group.get_groups(user=api.user.get_current()) if
group.id == 'Gestion-secretariat-general'])
def board_decision_created(self):
"""Return true if a board decision has been created for this note for board"""
intids = getUtility(IIntIds)
catalog = getUtility(ICatalog)
try:
note_intid = intids.getId(self.context)
except KeyError:
return False
else:
return any(catalog.findRelations({'to_id': note_intid,
'from_attribute': 'related_docs'}))
def render(self):
if not self.in_gestion_sg():
return False
if self.board_decision_created():
return False
return True
class CreateBoardDecision(grok.View):
"""Create a board decision from a note for board"""
grok.name('create_board_decision')
grok.context(INoteForBoard)
grok.require("zope2.View")
def render(self):
note = self.context
values_params = [
u'form.widgets.IBasic.title={}'.format(
note.title,
),
u'form.widgets.related_docs:list={}'.format(
u'/'.join(note.getPhysicalPath()),
),
]
list_fields = {
'treated_by': 'IPfwbDocument.treated_by',
'treating_groups': 'treating_groups',
'recipient_groups': 'recipient_groups',
'keywords': 'IPfwbDocument.keywords',
}
for field_id, field_param_id in list_fields.items():
field = getattr(note, field_id, []) or []
for item in field:
values_params.append(
u'form.widgets.{}:list={}'.format(field_param_id, item)
)
documents_folder_url = api.portal.get()['documents'].absolute_url()
encoded_params = "&".join(values_params).encode('utf-8')
url = '{0}/++add++pfwb.boarddecision?{1}'.format(
documents_folder_url,
encoded_params,
)
self.request.response.redirect(url)

View File

@ -1,18 +1,21 @@
import zope
from Acquisition import aq_parent
from five import grok
from plone import api
from collective.dms.mailcontent.dmsmail import IDmsIncomingMail
from collective.task.content.task import ITask
from pfwbged.basecontent.types import IBoardDecision
from plone.api.exc import InvalidParameterError
from plone.dexterity.browser import add
class NoIDmsIncomingMailFound(Exception):
"""No IDmsIncomingMail found"""
class CreateOutgoingMail(grok.View):
class CreateOutgoingMailFromTask(grok.View):
grok.name("create_outgoing_mail")
grok.context(ITask)
@ -59,9 +62,91 @@ form.widgets.treating_groups=%(treating_groups)s""" % values
values_url += '&' + 'form.widgets.recipient_groups:list=%s' % principal
folder_url = api.portal.get()['documents'].absolute_url()
if incomingmail.portal_type == 'pfwb.apfincomingmail':
outgoing_add_url = '/++add++pfwb.apfoutgoingmail?'
else:
outgoing_add_url = "/++add++dmsoutgoingmail?"
url = folder_url + outgoing_add_url + values_url.encode('utf-8')
url = folder_url + "/++add++dmsoutgoingmail?" + values_url.encode('utf-8')
self.request.response.redirect(url)
class CreateOutgoingMailFromBoardDecision(grok.View):
grok.name("create_outgoing_mail")
grok.context(IBoardDecision)
grok.require("zope2.View")
def render(self):
decision = self.context
values_params = [
u'form.widgets.related_docs:list={}'.format(
u'/'.join(decision.getPhysicalPath()),
),
]
list_fields = {
'treated_by': 'IPfwbDocument.treated_by',
'treating_groups': 'treating_groups',
'recipient_groups': 'recipient_groups',
'keywords': 'IPfwbDocument.keywords',
}
for field_id, field_param_id in list_fields.items():
field = getattr(decision, field_id, []) or []
for item in field:
values_params.append(
u'form.widgets.{}:list={}'.format(field_param_id, item)
)
documents_folder_url = api.portal.get()['documents'].absolute_url()
if self.request.form.get('follow_up'):
values_params.append('follow_up=1')
encoded_params = "&".join(values_params).encode('utf-8')
url = '{0}/++add++dmsoutgoingmail?{1}'.format(
documents_folder_url,
encoded_params,
)
self.request.response.redirect(url)
class AddForm(add.DefaultAddForm):
portal_type = "dmsoutgoingmail"
@property
def action(self):
base_action = super(AddForm, self).action
return '{}?follow_up=1'.format(base_action)
def createAndAdd(self, data):
obj = self.create(data)
zope.event.notify(zope.lifecycleevent.ObjectCreatedEvent(obj))
self.add(obj)
self.transition_board_decision() # addition to base method
return obj
def transition_board_decision(self):
"""
If the created mail is a follow-up (GET parameter),
search for board decisions in processing state among related documents,
transition the first one found to answered.
"""
follow_up = self.request.form.get('follow_up', False)
if not follow_up:
return
related_docs = getattr(
self.widgets.get('related_docs'),
'value',
[],
)
portal = api.portal.get()
for related_doc_path in related_docs:
related_doc = portal.restrictedTraverse(related_doc_path.split('/'))
if IBoardDecision.providedBy(related_doc) and api.content.get_state(related_doc) == 'processing':
try:
api.content.transition(related_doc, 'answer')
break
except InvalidParameterError: # answer transition is not available
pass
class AddView(add.DefaultAddView):
form = AddForm

View File

@ -1,21 +1,46 @@
# -*- coding: utf-8 -*-
import pickle
from copy import deepcopy
import z3c.form
from z3c.form import button
import zope.event
from zope.i18nmessageid.message import MessageFactory
from Products.CMFCore.utils import getToolByName
from Products.Five.browser import BrowserView
from plone import api
from plone.dexterity.browser.add import DefaultAddForm
from plone.dexterity.utils import createContentInContainer
from plone.stringinterp.adapters import _recursiveGetMembersFromIds
from collective.task import _
from collective.task.content.task import ITask
from collective.task.browser.attribute_task import find_nontask
from collective.taskqueue import taskqueue
class BackgroundAttributeTaskView(BrowserView):
def __call__(self):
base_task = self.context
data = pickle.load(self.request.stdin)
seen = {}
nontask = find_nontask(base_task)
portal_workflow = getToolByName(nontask, 'portal_workflow')
transitions = portal_workflow.getTransitionsFor(nontask)
transition_ids = [t['id'] for t in transitions]
for responsible in data['responsible']:
if responsible in seen:
continue
_data = deepcopy(data)
_data['responsible'] = [responsible]
new_task = createContentInContainer(base_task, 'task', **_data)
seen[responsible] = True
for responsible in data['responsible']:
nontask.manage_addLocalRoles(responsible, ['Editor',])
if 'attribute' in transition_ids:
portal_workflow.doActionFor(nontask, 'attribute')
nontask.reindexObjectSecurity()
nontask.reindexObject(idxs=['allowedRolesAndUsers'])
class AttributeTasks(DefaultAddForm):
@ -53,31 +78,11 @@ class AttributeTasks(DefaultAddForm):
self.status = self.formErrorsMessage
return
for task_id in self.request.documents.split(','):
base_task = api.content.get(str(task_id))
self.attribute_task(base_task, data)
for document_id in self.request.documents.split(','):
taskqueue.add(
'{}/background_attribute_task'.format(document_id),
payload=pickle.dumps(data),
)
self._finishedAdd = True
return
def attribute_task(self, base_task, data):
seen = {}
nontask = find_nontask(base_task)
portal_workflow = getToolByName(nontask, 'portal_workflow')
transitions = portal_workflow.getTransitionsFor(nontask)
transition_ids = [t['id'] for t in transitions]
for responsible in data['responsible']:
if responsible in seen:
continue
_data = deepcopy(data)
_data['responsible'] = [responsible]
new_task = createContentInContainer(base_task, 'task', **_data)
seen[responsible] = True
for responsible in data['responsible']:
nontask.manage_addLocalRoles(responsible, ['Editor',])
if 'attribute' in transition_ids:
portal_workflow.doActionFor(nontask, 'attribute')
nontask.reindexObjectSecurity()
nontask.reindexObject(idxs=['allowedRolesAndUsers'])

View File

@ -1,35 +1,38 @@
# -*- encoding: utf-8 -*-
import random
import logging
from datetime import datetime
from DateTime import DateTime
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email import encoders
from zope.interface import Interface
from zope import schema
from zope.component import createObject, queryUtility
from z3c.form import form, button
from z3c.form.field import Fields
from z3c.form.interfaces import HIDDEN_MODE
from zope.annotation.interfaces import IAnnotations
from Acquisition import aq_inner, aq_parent
from plone.z3cform.layout import FormWrapper
from Acquisition import aq_parent
from DateTime import DateTime
from Products.Five.browser import BrowserView
from Products.CMFCore.utils import getToolByName
from collective.select2.field import Select2MultiField
from pfwbged.policy import _
from plone import api
from plone.z3cform.layout import FormWrapper
from z3c.form import button
from z3c.form import form
from z3c.form.field import Fields
from zope import schema
from zope.annotation.interfaces import IAnnotations
from zope.interface import Interface
from .. import _
class IMail(Interface):
recipients = schema.Text(title=_(u"Recipients"), required=True,
description=_(u"Email addresses of the recipients, one per line"))
recipients = Select2MultiField(
title=_(u"Recipients"),
description=_(u"Email addresses of the recipients"),
search_view=lambda x: '{}/select2-ldap-emails-search'.format(x),
placeholder=_(u"Select a value here"),
required=True,
value_type=schema.TextLine(
title=_(u"Recipients"),
),
)
subject = schema.TextLine(title=_(u"Subject"), required=True)
comment = schema.Text(
title=_(u"Comment"),
@ -58,7 +61,7 @@ class MailForm(form.AddForm):
self._finishedAdd = True
subject = data['subject']
recipients = data['recipients'].splitlines()
recipients = data['recipients']
comment = data.get('comment')
msg = MIMEMultipart()

View File

@ -5,3 +5,7 @@
.template-view .overlay-ajax .empty.field {
display: block;
}
.select2-drop {
z-index: 11000 !important; /* only necessary for Firefox; no explanation found. */
}

View File

@ -7,7 +7,13 @@ $(document).ready(function(){
noform: function(el) {return jQuery.plonepopups.noformerrorshow(el, 'reload');},
config: {
closeOnClick: false,
closeOnEsc: false
closeOnEsc: false,
onLoad : function (e) {
$('.pb-ajax .select2-multi-widget').select2Widget({
multiple: true
});
return true;
}
}
});
$(document).find('.overlay-form-redirect').prepOverlay({

View File

@ -14,10 +14,6 @@
<div id="documents-table-view" tal:attributes="data-types view/document_types">
<div class="criteria-content" style="display:block;">
Rechercher : <input name="text-criteria-fulltext">
</div>
<div style="display: none;">
<input id="sort_on" value="created"/>
<input id="sort_order" type="checkbox" checked="checked"/>
@ -45,8 +41,6 @@ $.querywidget.updateSearch = function (stay_on_page) {
$.map($('#documents-table-view').data('types').split(';'), function(a) {
query += "query.v:records:list=" + a + "&";
});
query += "query.i:records=SearchableText&query.o:records=plone.app.querystring.operation.string.contains&query.v:records=";
query += $('input[name="text-criteria-fulltext"]').val();
query += '&sort_on=' + $('#sort_on').val();
if ($('#sort_order:checked').length > 0) {
query += '&sort_order=reverse';
@ -59,11 +53,6 @@ $.querywidget.updateSearch = function (stay_on_page) {
$(function() {
$.querywidget.updateSearch();
$('input[name="text-criteria-fulltext"]').on('keyup', function() {
$.querywidget.updateSearch();
});
});
</script>
</metal:main>

View File

@ -19,6 +19,7 @@
<grok:grok package="." />
<include package=".browser" />
<include package=".subscribers" />
<include package="collective.dms.basecontent" />
<include package="collective.dms.batchimport" />
@ -28,12 +29,14 @@
<include package="collective.task" />
<include package="collective.onlogin" />
<include package="collective.solr" />
<include package="collective.select2" />
<include package="pfwbged.basecontent" />
<include package="pfwbged.collection" />
<include package="pfwbged.folder" />
<include package="pfwbged.theme" />
<include package="plone.app.contenttypes" />
<include package="collective.impersonate" />
<!--
@ -82,4 +85,19 @@
replacement=".monkey.patchedNamedBlobFileInit"
/>
<include package="borg.localrole" />
<adapter
for="collective.dms.mailcontent.dmsmail.IDmsIncomingMail"
provides="borg.localrole.interfaces.ILocalRoleProvider"
factory=".localrole.LocalRoleAdapter"
/>
<adapter
for="collective.dms.mailcontent.dmsmail.IDmsOutgoingMail"
provides="borg.localrole.interfaces.ILocalRoleProvider"
factory=".localrole.LocalRoleAdapter"
/>
</configure>

View File

@ -54,7 +54,7 @@ msgstr "Actions"
#: ../browser/add_multi_information.py:25
#: ../browser/multi_attribute_task.py:41
msgid "Add"
msgstr ""
msgstr "Ajouter"
#: ../menu.py:332
msgid "Add ${title}"
@ -152,9 +152,9 @@ msgstr "Adresse du document : %s"
msgid "Document: %s"
msgstr "Document : %s"
#: ../browser/send_by_email.py:32
msgid "Email addresses of the recipients, one per line"
msgstr "Adresses électroniques des destinataires, une par ligne"
#: ../browser/send_by_email.py:35
msgid "Email addresses of the recipients"
msgstr "Adresses électroniques des destinataires"
#: ../browser/send_by_email.py:97
msgid "Error sending email"
@ -176,10 +176,18 @@ msgstr "Dossiers"
msgid "Installs the pfwbged.policy add-on."
msgstr ""
#: ../upgrades/registry.py:17
msgid "Mail reader user group"
msgstr "Groupe de lecture du courrier"
#: ../menu.py:205
msgid "Mark document as read"
msgstr "Marquer le document comme lu"
#: ../upgrades/registry.py:18
msgid "Members of this group can read all incoming and outgoing mails on the platform."
msgstr "Les membres de ce groupe ont un accès en lecture sur tous les courriers entrants et sortants"
#: ../customize.py:42
msgid "My Tasks"
msgstr "Mes tâches"
@ -200,7 +208,11 @@ msgstr "Note :"
msgid "One document is not mentioned for your information anymore"
msgstr "Une note pour information vous concernant a été supprimée"
#: ../subscribers/document.py:549
#: ../subscribers/document.py:429
msgid "One of the tasks you requested has been marked as done"
msgstr "Une tâche demandée par vous a été marquée comme faite"
#: ../subscribers/document.py:625
msgid "One of your tasks has been cancelled"
msgstr "Une de vos tâches a été annulée"
@ -253,7 +265,11 @@ msgstr "Enregistrer"
msgid "Save As..."
msgstr "Enregistrer sous…"
#: ../browser/send_by_email.py:52
#: ../browser/send_by_email.py:29
msgid "Select a value here"
msgstr "Sélectionner une valeur ici"
#: ../browser/send_by_email.py:55
msgid "Send"
msgstr "Envoyer"

View File

@ -16,12 +16,10 @@ msgstr ""
"X-is-fallback-for: fr-fr fr-be fr-ca\n"
"X-Generator: Poedit 1.5.4\n"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Answer"
msgstr "Répondre"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Answered"
msgstr "Répondu"
@ -34,12 +32,10 @@ msgstr "Demander un avis"
msgid "Ask validation"
msgstr "Demander une validation"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Assigning"
msgstr "À attribuer"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Back to assigning"
msgstr "Retour à attribuer"
@ -48,7 +44,10 @@ msgstr "Retour à attribuer"
msgid "Back to draft"
msgstr "Retour à la rédaction"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Back to private"
msgstr ""
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Back to registering"
msgstr "Retour à indicater"
@ -84,7 +83,6 @@ msgstr "Annuler le refus"
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
msgid "Considered"
@ -135,7 +133,6 @@ msgstr "Rendre obsolète"
msgid "My Folder"
msgstr "Mon dossier"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "No action"
@ -145,6 +142,10 @@ msgstr "Sans suite"
msgid "Obsolete"
msgstr "Obsolète"
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Private"
msgstr ""
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "Process"
msgstr "Traiter"
@ -153,13 +154,17 @@ msgstr "Traiter"
msgid "Processed"
msgstr "Traité"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "Processing"
msgstr "En cours de traitement"
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Publish"
msgstr ""
#: ../profiles/default/workflows/appendix_workflow/definition.xml
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Published"
msgstr "Publiée"
@ -194,12 +199,10 @@ msgstr "Envoyer à la corbeille"
msgid "Sent"
msgstr "Envoyé"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "To assign"
msgstr "À attribuer"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "To process"

View File

@ -154,8 +154,8 @@ msgstr ""
msgid "Document: %s"
msgstr ""
#: ../browser/send_by_email.py:32
msgid "Email addresses of the recipients, one per line"
#: ../browser/send_by_email.py:35
msgid "Email addresses of the recipients"
msgstr ""
#: ../browser/send_by_email.py:97
@ -178,10 +178,18 @@ msgstr ""
msgid "Installs the pfwbged.policy add-on."
msgstr ""
#: ../upgrades/registry.py:17
msgid "Mail reader user group"
msgstr ""
#: ../menu.py:205
msgid "Mark document as read"
msgstr ""
#: ../upgrades/registry.py:18
msgid "Members of this group can read all incoming and outgoing mails on the platform."
msgstr ""
#: ../customize.py:42
msgid "My Tasks"
msgstr ""
@ -202,7 +210,11 @@ msgstr ""
msgid "One document is not mentioned for your information anymore"
msgstr ""
#: ../subscribers/document.py:549
#: ../subscribers/document.py:429
msgid "One of the tasks you requested has been marked as done"
msgstr ""
#: ../subscribers/document.py:625
msgid "One of your tasks has been cancelled"
msgstr ""
@ -255,7 +267,11 @@ msgstr ""
msgid "Save As..."
msgstr ""
#: ../browser/send_by_email.py:52
#: ../browser/send_by_email.py:29
msgid "Select a value here"
msgstr ""
#: ../browser/send_by_email.py:55
msgid "Send"
msgstr ""

View File

@ -17,12 +17,10 @@ msgstr ""
"Preferred-Encodings: utf-8 latin1\n"
"Domain: plone\n"
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Answer"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Answered"
msgstr ""
@ -35,12 +33,10 @@ msgstr ""
msgid "Ask validation"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Assigning"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Back to assigning"
msgstr ""
@ -49,7 +45,10 @@ msgstr ""
msgid "Back to draft"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Back to private"
msgstr ""
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Back to registering"
msgstr ""
@ -74,7 +73,6 @@ msgstr ""
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
msgid "Considered"
@ -125,7 +123,6 @@ msgstr ""
msgid "My Folder"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "No action"
@ -135,6 +132,10 @@ msgstr ""
msgid "Obsolete"
msgstr ""
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Private"
msgstr ""
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "Process"
msgstr ""
@ -143,13 +144,17 @@ msgstr ""
msgid "Processed"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "Processing"
msgstr ""
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Publish"
msgstr ""
#: ../profiles/default/workflows/appendix_workflow/definition.xml
#: ../profiles/default/workflows/private_public_workflow/definition.xml
msgid "Published"
msgstr ""
@ -161,7 +166,6 @@ msgstr ""
msgid "Refuse"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "Registering"
msgstr ""
@ -184,12 +188,10 @@ msgstr ""
msgid "Sent"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
msgid "To assign"
msgstr ""
#: ../profiles/default/workflows/incomingapfmail_workflow/definition.xml
#: ../profiles/default/workflows/incomingmail_workflow/definition.xml
#: ../profiles/default/workflows/pfwbgeddocument_workflow/definition.xml
msgid "To process"

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from borg.localrole.interfaces import ILocalRoleProvider
from plone import api
from zope.interface import implements
class LocalRoleAdapter(object):
implements(ILocalRoleProvider)
def __init__(self, context):
self.context = context
@property
def accepted_group(self):
try:
return api.portal.get_registry_record('pfwbged.mail_reader_group')
except api.exc.InvalidParameterError:
return ''
def getRoles(self, principal):
"""Grant permission for principal"""
if principal == self.accepted_group:
return ('Reader',)
else:
return ()
def getAllRoles(self):
"""Grant permissions"""
return [(self.accepted_group, ('Reader',))]

View File

@ -446,11 +446,13 @@ class CustomMenu(menu.WorkflowMenu):
action['title'] = _(u"Create signed version for version ${version}",
mapping={'version': context.Title()})
cssClass += " overlay-form-reload"
elif action['id'] == 'create_outgoing_mail':
elif action['id'] in (
'create_outgoing_mail',
'create_board_decision',
'delete',
):
# make it overlay !
cssClass += ' overlay-form-redirect'
elif action['id'] == 'delete':
cssClass += " overlay-form-redirect"
if action['allowed']:
aid = action['id']
@ -563,14 +565,6 @@ class CustomMenu(menu.WorkflowMenu):
# wf actions on informations
actions.extend(self.getWorkflowActionsForType(context, request, 'information'))
if context.portal_type in ('pfwb.boarddecision',):
# ideally this condition would be part of the task action
# condition_expr, but that would require to get down from there
# back to the document; it's much easier to just hide it from
# here.
actions = [x for x in actions if \
x.get('extra').get('id') != 'plone-contentmenu-actions-create_outgoing_mail']
if ICollection.providedBy(context):
# edition of collections is done inline, remove edit action but add
# save and save as

View File

@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
<version>5</version>
<version>9</version>
<dependencies>
<dependency>profile-collective.dms.basecontent:default</dependency>
<dependency>profile-collective.dms.batchimport:default</dependency>
@ -18,5 +18,6 @@
<dependency>profile-plone.app.contenttypes:default</dependency>
<dependency>profile-plone.app.dexterity:default</dependency>
<dependency>profile-Products.AROfficeTransforms:default</dependency>
<dependency>profile-collective.impersonate:default</dependency>
</dependencies>
</metadata>

View File

@ -99,4 +99,12 @@
</field>
<value>False</value>
</record>
<record name="pfwbged.mail_reader_group">
<field type="plone.registry.field.ASCIILine">
<title>Mail reader user group</title>
<description>Members of this group can read all incoming and outgoing mails on the platform.</description>
<required>False</required>
</field>
<value>lecture-courriers</value>
</record>
</registry>

View File

@ -2,7 +2,6 @@
<rolemap>
<roles>
<role name="Greffier"/>
<role name="SecretariatGeneralAPF"/>
</roles>
<permissions>
<permission name="Add portal content" acquire="True">
@ -30,7 +29,6 @@
<role name="Reviewer"/>
<role name="Site Administrator"/>
<role name="Greffier"/>
<role name="SecretariatGeneralAPF"/>
</permission>
</permissions>
</rolemap>

View File

@ -4,11 +4,11 @@
name="title">Contains workflow definitions for your portal</property>
<object name="appendix_workflow" meta_type="Workflow"/>
<object name="incomingmail_workflow" meta_type="Workflow"/>
<object name="incomingapfmail_workflow" meta_type="Workflow"/>
<object name="outgoingmail_workflow" meta_type="Workflow"/>
<object name="pfwbgeddocument_workflow" meta_type="Workflow"/>
<object name="pfwbgedfolder_workflow" meta_type="Workflow"/>
<object name="pfwbgedmainfolder_workflow" meta_type="Workflow"/>
<object name="private_public_workflow" meta_type="Workflow"/>
<object name="versionnote_workflow" meta_type="Workflow"/>
<bindings>
<default>
@ -18,7 +18,7 @@
<bound-workflow workflow_id=""/>
</type>
<type type_id="pfwbgedcollection">
<bound-workflow workflow_id="one_state_workflow"/>
<bound-workflow workflow_id="private_public_workflow"/>
</type>
<type type_id="pfwbgedlink">
<bound-workflow workflow_id=""/>
@ -53,17 +53,26 @@
<type type_id="pfwb.boarddecision">
<bound-workflow workflow_id="incomingmail_workflow"/>
</type>
<type type_id="pfwb.apfincomingmail">
<bound-workflow workflow_id="incomingapfmail_workflow"/>
</type>
<type type_id="pfwb.apfoutgoingmail">
<bound-workflow workflow_id="outgoingmail_workflow"/>
</type>
<type type_id="pfwb.medicalcertificate">
<bound-workflow workflow_id="incomingmail_workflow"/>
</type>
<type type_id="pfwb.invoice">
<bound-workflow workflow_id="incomingmail_workflow"/>
</type>
<type type_id="pfwb.memorandum">
<bound-workflow workflow_id="outgoingmail_workflow"/>
</type>
<type type_id="pfwb.notice">
<bound-workflow workflow_id="outgoingmail_workflow"/>
</type>
<type type_id="pfwb.informationnote">
<bound-workflow workflow_id="outgoingmail_workflow"/>
</type>
<type type_id="pfwb.copnote">
<bound-workflow workflow_id="outgoingmail_workflow"/>
</type>
<type type_id="pfwb.copminutes">
<bound-workflow workflow_id="outgoingmail_workflow"/>
</type>
</bindings>
</object>

View File

@ -1,290 +0,0 @@
<?xml version="1.0"?>
<dc-workflow workflow_id="incomingmail_workflow"
title="Workflow for Incoming Mail"
description=""
state_variable="review_state"
initial_state="registering"
manager_bypass="True"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="plone">
<permission>Access contents information</permission>
<permission>Delete objects</permission>
<permission>Modify portal content</permission>
<permission>View</permission>
<state state_id="answered" title="Answered" i18n:attributes="title">
<permission-map name="Access contents information" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
</state>
<state state_id="assigning" title="Assigning" i18n:attributes="title">
<exit-transition transition_id="back_to_registering"/>
<exit-transition transition_id="directly_noaction"/>
<exit-transition transition_id="to_process"/>
<permission-map name="Access contents information" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>SecretariatGeneralApf</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>SecretariatGeneralApf</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>SecretariatGeneralApf</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
</state>
<state state_id="noaction" title="No action" i18n:attributes="title">
<exit-transition transition_id="back_to_assigning"/>
<permission-map name="Access contents information" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
</state>
<state state_id="considered" title="Considered" i18n:attributes="title">
<exit-transition transition_id="back_to_assigning"/>
<permission-map name="Access contents information" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
</state>
<state state_id="processing" title="Processing" i18n:attributes="title">
<exit-transition transition_id="answer"/>
<exit-transition transition_id="to_noaction"/>
<exit-transition transition_id="to_considered"/>
<permission-map name="Access contents information" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
</state>
<state state_id="registering" title="Registering" i18n:attributes="title">
<exit-transition transition_id="to_assign"/>
<permission-map name="Access contents information" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>SecretariatGeneralApf</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
<permission-map name="View" acquired="False">
<permission-role>Editor</permission-role>
<permission-role>SecretariatGeneralApf</permission-role>
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
<permission-role>Reader</permission-role>
<permission-role>Reviewer</permission-role>
<permission-role>Site Administrator</permission-role>
</permission-map>
</state>
<transition transition_id="answer" title="" new_state="answered" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=answer" category="workflow" icon="" i18n:translate="">Answer</action>
<guard>
<guard-role>Editor</guard-role>
<guard-role>Manager</guard-role>
<guard-expression>here/@@can_answer</guard-expression>
</guard>
</transition>
<transition transition_id="back_to_assigning" title="" new_state="assigning" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=back_to_assigning" category="workflow" icon="" i18n:translate="">Back to assigning</action>
<guard>
<guard-role>Reviewer</guard-role>
<guard-role>Manager</guard-role>
</guard>
</transition>
<transition transition_id="back_to_registering" title="" new_state="registering" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=back_to_registering" category="workflow" icon="" i18n:translate="">Back to registering</action>
<guard>
<guard-expression>here/@@can_return_to_registering_or_process</guard-expression>
</guard>
</transition>
<transition transition_id="directly_noaction" title="" new_state="noaction" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=directly_noaction" category="workflow" icon="" i18n:translate="">No action</action>
<guard>
<guard-role>Reviewer</guard-role>
<guard-role>Manager</guard-role>
</guard>
</transition>
<transition transition_id="to_assign" title="" new_state="assigning" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=to_assign" category="workflow" icon="" i18n:translate="">To assign</action>
<guard>
<guard-role>Editor</guard-role>
<guard-role>Manager</guard-role>
<guard-role>Owner</guard-role>
<guard-role>Reviewer</guard-role>
</guard>
</transition>
<transition transition_id="to_noaction" title="" new_state="noaction" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=to_noaction" category="workflow" icon="" i18n:translate="">No action</action>
<guard>
<guard-role>Editor</guard-role>
<guard-role>Manager</guard-role>
</guard>
</transition>
<transition transition_id="to_considered" title="" new_state="considered" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/content_status_modify?workflow_action=to_considered" category="workflow" icon="" i18n:translate="">Considered</action>
<guard>
<guard-role>Editor</guard-role>
<guard-role>Manager</guard-role>
</guard>
</transition>
<transition transition_id="to_process" title="" new_state="processing" trigger="USER" before_script="" after_script="">
<action url="%(content_url)s/@@to_process?workflow_action=to_process" category="workflow" icon="" i18n:translate="">To process</action>
<guard>
<guard-role>SecretariatGeneralApf</guard-role>
<guard-role>Manager</guard-role>
<guard-role>Reviewer</guard-role>
</guard>
</transition>
<variable variable_id="action" for_catalog="False" for_status="True" update_always="True">
<description>Previous transition</description>
<default>
<expression>transition/getId|nothing</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="actor" for_catalog="False" for_status="True" update_always="True">
<description>The ID of the user who performed the previous transition</description>
<default>
<expression>user/getId</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="comments" for_catalog="False" for_status="True" update_always="True">
<description>Comment about the last transition</description>
<default>
<expression>python:state_change.kwargs.get('comment', '')</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="review_history" for_catalog="False" for_status="False" update_always="False">
<description>Provides access to workflow history</description>
<default>
<expression>state_change/getHistory</expression>
</default>
<guard>
<guard-permission>Request review</guard-permission>
<guard-permission>Review portal content</guard-permission>
</guard>
</variable>
<variable variable_id="time" for_catalog="False" for_status="True" update_always="True">
<description>When the previous transition was performed</description>
<default>
<expression>state_change/getDateTime</expression>
</default>
<guard>
</guard>
</variable>
</dc-workflow>

View File

@ -0,0 +1,106 @@
<?xml version="1.0"?>
<dc-workflow workflow_id="private_public_workflow"
title="Private Public Workflow"
description="Accessible by owner and managers when private, by everyone when published"
state_variable="review_state"
initial_state="private"
manager_bypass="True"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="plone">
<permission>Access contents information</permission>
<permission>Delete objects</permission>
<permission>Modify portal content</permission>
<permission>View</permission>
<state state_id="private" title="Private" i18n:attributes="title">
<exit-transition transition_id="publish"/>
<permission-map name="View" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="Access contents information" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
</state>
<state state_id="published" title="Published" i18n:attributes="title">
<exit-transition transition_id="back_to_private"/>
<permission-map name="View" acquired="False">
<permission-role>Anonymous</permission-role>
</permission-map>
<permission-map name="Access contents information" acquired="False">
<permission-role>Anonymous</permission-role>
</permission-map>
<permission-map name="Modify portal content" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
<permission-map name="Delete objects" acquired="False">
<permission-role>Manager</permission-role>
<permission-role>Owner</permission-role>
</permission-map>
</state>
<transition transition_id="back_to_private" title="Back to private" new_state="private" trigger="USER" before_script="" after_script="" i18n:attributes="title">
<action url="" category="workflow" icon="" i18n:translate="">Back to private</action>
<guard>
<guard-role>Manager</guard-role>
<guard-role>Owner</guard-role>
</guard>
</transition>
<transition transition_id="publish" title="Publish" new_state="published" trigger="USER" before_script="" after_script="" i18n:attributes="title">
<action url="" category="workflow" icon="" i18n:translate="">Publish</action>
<guard>
<guard-role>Manager</guard-role>
<guard-role>Owner</guard-role>
</guard>
</transition>
<variable variable_id="action" for_catalog="False" for_status="True" update_always="True">
<description>Previous transition</description>
<default>
<expression>transition/getId|nothing</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="actor" for_catalog="False" for_status="True" update_always="True">
<description>The ID of the user who performed the last transition</description>
<default>
<expression>user/getId</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="comments" for_catalog="False" for_status="True" update_always="True">
<description>Comment about the last transition</description>
<default>
<expression>python:state_change.kwargs.get('comment', '')</expression>
</default>
<guard>
</guard>
</variable>
<variable variable_id="review_history" for_catalog="False" for_status="False" update_always="False">
<description>Provides access to workflow history</description>
<default>
<expression>state_change/getHistory</expression>
</default>
<guard>
<guard-permission>Request review</guard-permission>
<guard-permission>Review portal content</guard-permission>
</guard>
</variable>
<variable variable_id="time" for_catalog="False" for_status="True" update_always="True">
<description>When the previous transition was performed</description>
<default>
<expression>state_change/getDateTime</expression>
</default>
<guard>
</guard>
</variable>
</dc-workflow>

View File

@ -94,6 +94,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
role = u'responsible'
id = '%s-%s' % (type, role)
if id not in container:
@ -108,6 +110,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
# opinions
type = u'opinion'
role = u'enquirer'
@ -124,6 +128,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
role = u'responsible'
id = '%s-%s' % (type, role)
if id not in container:
@ -138,6 +144,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
# validations
type = u'validation'
role = u'enquirer'
@ -154,6 +162,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
role = u'responsible'
id = '%s-%s' % (type, role)
if id not in container:
@ -168,6 +178,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
# informations
sort_on = u'created'
type = u'information'
@ -185,6 +197,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
role = u'responsible'
id = '%s-%s' % (type, role)
if id not in container:
@ -199,6 +213,8 @@ def create_tasks_collections(context):
item_count=item_count,
query=query,
)
api.content.transition(collection, 'publish')
def setup_folder_portlets(folder):

View File

@ -0,0 +1,16 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:plone="http://namespaces.plone.org/plone"
i18n_domain="pfwbged.policy">
<browser:page
name="background_delete_tasks"
class=".document.BackgroundDeleteTasksView"
permission="zope2.View"
for="*"
layer="collective.taskqueue.interfaces.ITaskQueueLayer"
/>
</configure>

View File

@ -1,7 +1,12 @@
import logging
import os
import datetime
import pickle
from Acquisition import aq_chain, aq_parent
from Products.Five import BrowserView
from collective.taskqueue import taskqueue
from collective.task.indexers import get_document
from five import grok
from DateTime import DateTime
@ -45,6 +50,24 @@ except ImportError:
IAsyncService = None
def build_absolute_url(obj):
"""
Used within taskqueue requests,
because they don't have a good SERVER_URL,
and thus absolute_url calls are wrong.
"""
root_url = os.getenv("ROOT_URL", None)
if root_url:
portal_path = api.portal.get().getPhysicalPath()
obj_path = obj.getPhysicalPath()
relative_path = obj_path[len(portal_path):]
absolute_path = (root_url,) + relative_path
return "/".join(absolute_path)
else:
return obj.absolute_url() # no choice left
def has_pfwbgeddocument_workflow(obj):
wtool = api.portal.get_tool('portal_workflow')
return 'pfwbgeddocument_workflow' in wtool.getChainFor(obj)
@ -55,8 +78,6 @@ def has_incomingmail_workflow(obj):
chain = wtool.getChainFor(obj)
if 'incomingmail_workflow' in chain:
return True
if 'incomingapfmail_workflow' in chain:
return True
return False
@ -130,11 +151,28 @@ def delete_tasks(context, event):
query = {'to_id': version_intid,
'from_interfaces_flattened': IBaseTask,
'from_attribute': 'target'}
for rv in catalog.findRelations(query):
obj = rv.from_object
#obj.aq_parent.manage_delObjects([obj.getId()]) # we don't want to verify Delete object permission on object
del aq_parent(obj)[obj.getId()]
reindex_after_version_changes(aq_parent(context))
task_paths = [rv.from_path for rv in catalog.findRelations(query)]
taskqueue.add(
'{}/background_delete_tasks'.format(api.portal.get().absolute_url_path()),
payload=pickle.dumps(task_paths),
)
class BackgroundDeleteTasksView(BrowserView):
def __call__(self):
portal = api.portal.get()
document = None
for task_path in pickle.load(self.request.stdin):
try:
task = portal.unrestrictedTraverse(task_path)
except KeyError:
continue
if task:
document = get_document(task)
del document[task.getId()]
if document:
reindex_after_version_changes(document)
@grok.subscribe(IDmsFile, IObjectAddedEvent)
@ -230,11 +268,15 @@ def version_note_finished(context, event):
portal_catalog = api.portal.get_tool('portal_catalog')
document = context.getParentNode()
state = api.content.get_state(obj=document)
# if parent is an outgoing mail, change its state to ready_to_send
if document.portal_type in ('dmsoutgoingmail', 'pfwb.apfoutgoingmail') and state == 'writing':
# if parent is an outgoing mail, memorandum or notice,
# change its state to ready_to_send
types_to_finish = ['dmsoutgoingmail', 'pfwb.memorandum', 'pfwb.notice']
if document.portal_type in types_to_finish and state == 'writing':
with api.env.adopt_user('admin'):
api.content.transition(obj=document, transition='finish')
document.reindexObject(idxs=['review_state'])
elif IPfwbDocument.providedBy(document) and has_pfwbgeddocument_workflow(document):
if state == 'processing':
with api.env.adopt_user('admin'):
@ -382,6 +424,53 @@ def email_notification_of_tasks_sync(context, event, document, absolute_url, tar
log.exception(e)
@grok.subscribe(ITask, IAfterTransitionEvent)
def email_notification_of_done_tasks(context, event):
if event.transition and event.transition.id == 'mark-as-done':
document = None
for obj in aq_chain(context):
obj = aq_parent(obj)
if IDmsDocument.providedBy(obj):
document = obj
break
if not document:
return
absolute_url = build_absolute_url(document)
recipient_emails = []
for recipient in _recursiveGetMembersFromIds(api.portal.get(), (context.enquirer or [])):
email = recipient.getProperty('email', None)
if email:
recipient_emails.append(email)
if not recipient_emails:
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 = translate(_('One of the tasks you requested has been marked as done'), context=context.REQUEST) + \
'\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) % absolute_url + \
'\n\n\n\n-- \n' + \
translate(_('Sent by GED'))
body = body.encode('utf-8')
for recipient_email in recipient_emails:
try:
context.MailHost.send(body, recipient_email, 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(IBaseTask, IObjectAddedEvent)
def email_notification_of_tasks(context, event):
# go up in the acquisition chain to find the document, this cannot be done
@ -395,7 +484,7 @@ def email_notification_of_tasks(context, event):
break
if not document:
return
absolute_url = document.absolute_url()
absolute_url = build_absolute_url(document)
# request is also required to get the target language
target_language = negotiate(context.REQUEST)
@ -438,6 +527,7 @@ def email_notification_of_validation_reversal(context, event):
break
if not document:
return
absolute_url = build_absolute_url(document)
email_enquirer = None
for enquirer in (context.enquirer or []):
@ -460,7 +550,7 @@ def email_notification_of_validation_reversal(context, event):
'\n\n' + \
translate(_('Document: %s'), context=context.REQUEST) % document.title + \
'\n\n' + \
translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url()
translate(_('Document Address: %s'), context=context.REQUEST) % absolute_url
body += '\n\n\n-- \n' + translate(_('Sent by GED'))
body = body.encode('utf-8')
@ -487,6 +577,7 @@ def email_notification_of_refused_task(context, event):
break
if not document:
return
absolute_url = build_absolute_url(document)
email_enquirer = None
for enquirer in (context.enquirer or []):
@ -509,7 +600,7 @@ def email_notification_of_refused_task(context, event):
'\n\n' + \
translate(_('Document: %s'), context=context.REQUEST) % document.title + \
'\n\n' + \
translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url() + \
translate(_('Document Address: %s'), context=context.REQUEST) % absolute_url + \
'\n\n'
conversation = IConversation(context)
@ -540,7 +631,7 @@ def email_notification_of_canceled_subtask(context, event):
break
if not document:
return
absolute_url = document.absolute_url()
absolute_url = build_absolute_url(document)
recipient_emails = []
for recipient in _recursiveGetMembersFromIds(api.portal.get(), (context.responsible or [])):
@ -562,7 +653,7 @@ def email_notification_of_canceled_subtask(context, event):
'\n\n' + \
translate(_('Document: %s'), context=context.REQUEST) % document.title + \
'\n\n' + \
translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url() + \
translate(_('Document Address: %s'), context=context.REQUEST) % absolute_url + \
'\n\n\n\n-- \n' + \
translate(_('Sent by GED'))
body = body.encode('utf-8')
@ -586,7 +677,7 @@ def email_notification_of_canceled_information(context, event):
break
if not document:
return
absolute_url = document.absolute_url()
absolute_url = build_absolute_url(document)
responsible = context.responsible[0]
principal = api.user.get(responsible)
@ -604,7 +695,7 @@ def email_notification_of_canceled_information(context, event):
'\n\n' + \
translate(_('Document: %s'), context=context.REQUEST) % document.title + \
'\n\n' + \
translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url() + \
translate(_('Document Address: %s'), context=context.REQUEST) % absolute_url + \
'\n\n\n\n-- \n' + \
translate(_('Sent by GED'))
body = body.encode('utf-8')
@ -627,7 +718,7 @@ def email_notification_of_canceled_validation(context, event):
break
if not document:
return
absolute_url = document.absolute_url()
absolute_url = build_absolute_url(document)
recipient_emails = []
for recipient in _recursiveGetMembersFromIds(api.portal.get(), (context.responsible or [])):
@ -655,7 +746,7 @@ def email_notification_of_canceled_validation(context, event):
'\n\n' + \
translate(_('Document: %s'), context=context.REQUEST) % document.title + \
'\n\n' + \
translate(_('Document Address: %s'), context=context.REQUEST) % document.absolute_url() + \
translate(_('Document Address: %s'), context=context.REQUEST) % absolute_url + \
'\n\n\n\n-- \n' + \
translate(_('Sent by GED'))
body = body.encode('utf-8')

View File

@ -54,4 +54,48 @@
</genericsetup:upgradeSteps>
<genericsetup:upgradeStep
title="Refresh addable types in Documents folder"
description=""
source="5"
destination="6"
profile="pfwbged.policy:default"
handler=".types.refresh_documents_addable_types"
/>
<genericsetup:upgradeStep
title="Remove APF content types"
description=""
source="6"
destination="7"
profile="pfwbged.policy:default"
handler=".types.remove_apf_content_types"
/>
<genericsetup:upgradeStep
title="Setup mail reader group"
description=""
source="7"
destination="8"
profile="pfwbged.policy:default"
handler=".registry.setup_mail_reader_group"
/>
<genericsetup:upgradeSteps
source="8"
destination="9"
profile="pfwbged.policy:default">
<genericsetup:upgradeStep
title="Update saved search workflow"
description=""
handler=".workflow.update_saved_search_workflow"
/>
<genericsetup:upgradeDepends
title="Reimport workflows"
import_steps="workflow" />
</genericsetup:upgradeSteps>
</configure>

View File

@ -0,0 +1,25 @@
from pfwbged.policy import _
from plone.registry.interfaces import IRegistry
from plone.registry import field
from plone.registry import Record
from plone import api
from zope.component import getUtility
def setup_mail_reader_group(context):
record_id = u'pfwbged.mail_reader_group'
group_id = "lecture-courriers"
group_name = u"Lecture courriers"
registry = getUtility(IRegistry)
if record_id not in registry.records:
group = field.ASCIILine(
title=_(u"Mail reader user group"),
description=_(u"Members of this group can read all incoming and outgoing mails on the platform."),
required=True,
default=group_id,
)
registry.records[record_id] = Record(group)
if not api.group.get(group_id):
api.group.create(group_id, group_name)

View File

@ -0,0 +1,40 @@
from Products.CMFCore.utils import getToolByName
from pfwbged.policy.setuphandlers import setup_constrains
from plone import api
def setup_constrains_on_documents_folder():
portal = api.portal.get()
types_tool = getToolByName(portal, 'portal_types')
fti = types_tool.getTypeInfo('pfwbgedfolder')
setup_constrains(
portal['documents'],
[
x for x in fti.allowed_content_types
if x not in ('pfwbgedfolder', 'pfwbgedlink',)
]
)
def refresh_documents_addable_types(context):
context.runImportStepFromProfile('profile-pfwbged.basecontent:default',
'typeinfo')
context.runImportStepFromProfile('profile-pfwbged.folder:default',
'typeinfo')
context.runImportStepFromProfile('profile-pfwbged.policy:default',
'workflow')
setup_constrains_on_documents_folder()
def remove_apf_content_types(context):
context.runImportStepFromProfile('profile-pfwbged.basecontent:default',
'typeinfo')
context.runImportStepFromProfile('profile-pfwbged.folder:default',
'typeinfo')
context.runImportStepFromProfile('profile-pfwbged.policy:default',
'workflow')
context.runImportStepFromProfile('profile-pfwbged.policy:default',
'rolemap')
setup_constrains_on_documents_folder()

View File

@ -76,25 +76,44 @@ def update_refused_version_state(context):
version.reindexObject(idxs=['allowedRolesAndUsers', 'review_state'])
def refresh_workflow_permissions(context, workflow_id):
def refresh_workflow_permissions(context, workflow_id, folder_path=None):
if not folder_path:
folder_path = '/'.join(api.portal.get().getPhysicalPath())
portal_workflow = api.portal.get_tool('portal_workflow')
portal_catalog = api.portal.get_tool('portal_catalog')
workflow = portal_workflow.getWorkflowById(workflow_id)
portal = api.portal.get()
folder_path = '/'.join(portal['documents'].getPhysicalPath())
for dx_type, wf_ids in portal_workflow._chains_by_type.items():
if workflow_id in wf_ids:
query = {'path': {
'query': folder_path},
'portal_type': dx_type}
query = {
'path': {'query': folder_path},
'portal_type': dx_type,
}
results = portal_catalog.unrestrictedSearchResults(query)
for brain in results:
obj = brain.getObject()
workflow.updateRoleMappingsFor(obj)
obj.reindexObjectSecurity()
obj.reindexObject(idxs=['allowedRolesAndUsers'])
obj.reindexObject(idxs=['allowedRolesAndUsers', 'review_state'])
def incomingmail_deletion_permissions(context):
refresh_workflow_permissions(context, "incomingmail_workflow")
folder_path = '/'.join(api.portal.get()['documents'].getPhysicalPath())
refresh_workflow_permissions(context, "incomingmail_workflow", folder_path)
def update_saved_search_workflow(context):
refresh_workflow_permissions(context, "private_public_workflow")
# publish the communal searches in /Members
query = {
'path': {
'query': '/'.join(api.portal.get().Members.getPhysicalPath()),
'depth': 1,
},
'portal_type': 'pfwbgedcollection',
'review_state': 'private',
}
portal_catalog = api.portal.get_tool('portal_catalog')
for brain in portal_catalog.unrestrictedSearchResults(query):
api.content.transition(brain.getObject(), 'publish')