imio.pm.wsclient 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- imio/pm/wsclient/__init__.py +11 -0
- imio/pm/wsclient/browser/__init__.py +1 -0
- imio/pm/wsclient/browser/annexes.py +60 -0
- imio/pm/wsclient/browser/configure.zcml +143 -0
- imio/pm/wsclient/browser/forms.py +441 -0
- imio/pm/wsclient/browser/images/annex.png +0 -0
- imio/pm/wsclient/browser/images/doc.png +0 -0
- imio/pm/wsclient/browser/images/docx.png +0 -0
- imio/pm/wsclient/browser/images/odt.png +0 -0
- imio/pm/wsclient/browser/images/pdf.png +0 -0
- imio/pm/wsclient/browser/images/rtf.png +0 -0
- imio/pm/wsclient/browser/images/send_to_plonemeeting.png +0 -0
- imio/pm/wsclient/browser/javascripts/popups.js +71 -0
- imio/pm/wsclient/browser/redirect.py +43 -0
- imio/pm/wsclient/browser/settings.py +730 -0
- imio/pm/wsclient/browser/templates/contentprovider-widget.pt +2 -0
- imio/pm/wsclient/browser/templates/display_data_to_send.pt +8 -0
- imio/pm/wsclient/browser/templates/plonemeeting_infos.pt +71 -0
- imio/pm/wsclient/browser/templates/show_errors_in_overlay_form.pt +16 -0
- imio/pm/wsclient/browser/viewlets.py +159 -0
- imio/pm/wsclient/browser/views.py +121 -0
- imio/pm/wsclient/browser/vocabularies.py +355 -0
- imio/pm/wsclient/config.py +63 -0
- imio/pm/wsclient/configure.zcml +48 -0
- imio/pm/wsclient/events.py +28 -0
- imio/pm/wsclient/interfaces.py +77 -0
- imio/pm/wsclient/locales/PloneMeeting.pot +27 -0
- imio/pm/wsclient/locales/en/LC_MESSAGES/PloneMeeting.mo +0 -0
- imio/pm/wsclient/locales/en/LC_MESSAGES/PloneMeeting.po +29 -0
- imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.mo +0 -0
- imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po +352 -0
- imio/pm/wsclient/locales/fr/LC_MESSAGES/PloneMeeting.mo +0 -0
- imio/pm/wsclient/locales/fr/LC_MESSAGES/PloneMeeting.po +29 -0
- imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.mo +0 -0
- imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po +352 -0
- imio/pm/wsclient/locales/imio.pm.wsclient.pot +310 -0
- imio/pm/wsclient/locales/sync_pos.sh +11 -0
- imio/pm/wsclient/profiles/default/browserlayer.xml +7 -0
- imio/pm/wsclient/profiles/default/controlpanel.xml +20 -0
- imio/pm/wsclient/profiles/default/imio_pm_wsclient_marker.txt +4 -0
- imio/pm/wsclient/profiles/default/import_steps.xml +11 -0
- imio/pm/wsclient/profiles/default/jsregistry.xml +11 -0
- imio/pm/wsclient/profiles/default/metadata.xml +8 -0
- imio/pm/wsclient/profiles/default/registry.xml +4 -0
- imio/pm/wsclient/profiles/default/rolemap.xml +19 -0
- imio/pm/wsclient/profiles/testing/imio_pm_wsclient_testing_marker.txt +4 -0
- imio/pm/wsclient/profiles/testing/metadata.xml +8 -0
- imio/pm/wsclient/setuphandlers.py +27 -0
- imio/pm/wsclient/subscribers.zcml +17 -0
- imio/pm/wsclient/testing-settings.zcml +14 -0
- imio/pm/wsclient/testing.py +57 -0
- imio/pm/wsclient/testing.zcml +17 -0
- imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py +185 -0
- imio/pm/wsclient/tests/__init__.py +1 -0
- imio/pm/wsclient/tests/testEvents.py +104 -0
- imio/pm/wsclient/tests/testForms.py +285 -0
- imio/pm/wsclient/tests/testRESTMethods.py +503 -0
- imio/pm/wsclient/tests/testSettings.py +177 -0
- imio/pm/wsclient/tests/testViewlets.py +284 -0
- imio/pm/wsclient/tests/testViews.py +166 -0
- imio/pm/wsclient/tests/testVocabularies.py +148 -0
- imio/pm/wsclient/tests/vocabularies.py +24 -0
- imio/pm/wsclient/upgrade.py +31 -0
- imio.pm.wsclient-2.0.4-py3.13-nspkg.pth +2 -0
- imio_pm_wsclient-2.0.4.dist-info/METADATA +249 -0
- imio_pm_wsclient-2.0.4.dist-info/RECORD +69 -0
- imio_pm_wsclient-2.0.4.dist-info/WHEEL +5 -0
- imio_pm_wsclient-2.0.4.dist-info/namespace_packages.txt +2 -0
- imio_pm_wsclient-2.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from zope.i18nmessageid import MessageFactory
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
WS4PMClientMessageFactory = MessageFactory("imio.pm.wsclient")
|
|
7
|
+
PMMessageFactory = MessageFactory("PloneMeeting")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def initialize(context):
|
|
11
|
+
"""Initializer called when used as a Zope 2 product."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# I'm a python package
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from zope.filerepresentation.interfaces import IRawReadFile
|
|
4
|
+
from zope.interface import implements
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ATRawReadFile(object):
|
|
8
|
+
"""
|
|
9
|
+
"""
|
|
10
|
+
implements(IRawReadFile)
|
|
11
|
+
|
|
12
|
+
encoding = 'utf-8'
|
|
13
|
+
name = None
|
|
14
|
+
|
|
15
|
+
def __init__(self, context):
|
|
16
|
+
self.context = context
|
|
17
|
+
self._size = 0
|
|
18
|
+
|
|
19
|
+
def size(self):
|
|
20
|
+
stream = self._getStream()
|
|
21
|
+
pos = stream.tell()
|
|
22
|
+
stream.seek(0, 2)
|
|
23
|
+
size = stream.tell()
|
|
24
|
+
stream.seek(pos)
|
|
25
|
+
return size
|
|
26
|
+
|
|
27
|
+
def read(self, size=None):
|
|
28
|
+
if size is not None:
|
|
29
|
+
return self._getStream().read(size)
|
|
30
|
+
else:
|
|
31
|
+
return self._getStream().read()
|
|
32
|
+
|
|
33
|
+
def readline(self, size=None):
|
|
34
|
+
if size is None:
|
|
35
|
+
return self._getStream().readline()
|
|
36
|
+
else:
|
|
37
|
+
return self._getStream().readline(size)
|
|
38
|
+
|
|
39
|
+
def readlines(self, sizehint=None):
|
|
40
|
+
if sizehint is None:
|
|
41
|
+
return self._getStream().readlines()
|
|
42
|
+
else:
|
|
43
|
+
return self._getStream().readlines(sizehint)
|
|
44
|
+
|
|
45
|
+
def _getStream(self):
|
|
46
|
+
return self.context.getFile().getIterator()
|
|
47
|
+
|
|
48
|
+
def __iter__(self):
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def next(self):
|
|
52
|
+
return self._getStream().next()
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def encoding(self):
|
|
56
|
+
return self._getMessage().get_charset() or 'utf-8'
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def name(self):
|
|
60
|
+
return self.context.getFilename()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:five="http://namespaces.zope.org/five"
|
|
4
|
+
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
|
|
5
|
+
xmlns:browser="http://namespaces.zope.org/browser"
|
|
6
|
+
i18n_domain="imio.pm.wsclient">
|
|
7
|
+
|
|
8
|
+
<!-- Include z3c.form as dependency -->
|
|
9
|
+
<include package="plone.app.z3cform" />
|
|
10
|
+
|
|
11
|
+
<adapter
|
|
12
|
+
for="zope.publisher.interfaces.browser.IBrowserRequest"
|
|
13
|
+
provides="imio.pm.wsclient.interfaces.IRedirect"
|
|
14
|
+
factory=".redirect.Redirect"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<adapter
|
|
18
|
+
for="Products.ATContentTypes.interfaces.file.IATFile"
|
|
19
|
+
factory=".annexes.ATRawReadFile"
|
|
20
|
+
provides="zope.filerepresentation.interfaces.IRawReadFile"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<!-- special view that make it possible to redirect from an overlay and show status messages afterward -->
|
|
24
|
+
<browser:page
|
|
25
|
+
for="*"
|
|
26
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
27
|
+
name="redirect_view"
|
|
28
|
+
class=".redirect.RedirectView"
|
|
29
|
+
permission="zope2.View"
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
<!-- Control panel -->
|
|
33
|
+
<browser:page
|
|
34
|
+
name="ws4pmclient-settings"
|
|
35
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
36
|
+
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
|
37
|
+
class=".settings.WS4PMClientSettings"
|
|
38
|
+
permission="cmf.ManagePortal"
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<!-- View that sends the element to PloneMeeting -->
|
|
42
|
+
<browser:page
|
|
43
|
+
for="*"
|
|
44
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
45
|
+
name="send_to_plonemeeting_form"
|
|
46
|
+
class=".forms.SendToPloneMeetingWrapper"
|
|
47
|
+
permission="ws.client.Send"
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
<!-- define a specific template for rendering ContentProviders in z3c.form until it is in plone.app.z3cform... -->
|
|
51
|
+
<!-- browser:page
|
|
52
|
+
name="ploneform-render-widget"
|
|
53
|
+
for="zope.contentprovider.interfaces.IContentProvider"
|
|
54
|
+
template="templates/contentprovider-widget.pt"
|
|
55
|
+
layer="plone.app.z3cform.interfaces.IPloneFormLayer"
|
|
56
|
+
permission="zope.Public"
|
|
57
|
+
/-->
|
|
58
|
+
|
|
59
|
+
<!-- View that generates a document of an item -->
|
|
60
|
+
<browser:page
|
|
61
|
+
for="*"
|
|
62
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
63
|
+
name="generate_document_from_plonemeeting"
|
|
64
|
+
class=".views.GenerateItemTemplateView"
|
|
65
|
+
permission="ws.client.Access"
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
<!-- View that downaload an annex of an item -->
|
|
69
|
+
<browser:page
|
|
70
|
+
for="*"
|
|
71
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
72
|
+
name="download_annex_from_plonemeeting"
|
|
73
|
+
class=".views.DownloadAnnexFromItemView"
|
|
74
|
+
permission="ws.client.Access"
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<!-- Viewlets -->
|
|
78
|
+
<browser:viewlet
|
|
79
|
+
name="imio.pm.wsclient.pminfos"
|
|
80
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
81
|
+
manager="plone.app.layout.viewlets.interfaces.IBelowContentTitle"
|
|
82
|
+
class=".viewlets.PloneMeetingInfosViewlet"
|
|
83
|
+
permission="ws.client.Access"
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
<!-- Vocabularies -->
|
|
87
|
+
<utility
|
|
88
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
89
|
+
component=".vocabularies.pm_meeting_config_id_vocabularyFactory"
|
|
90
|
+
name="imio.pm.wsclient.pm_meeting_config_id_vocabulary"
|
|
91
|
+
/>
|
|
92
|
+
|
|
93
|
+
<utility
|
|
94
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
95
|
+
component=".vocabularies.possible_permissions_vocabularyFactory"
|
|
96
|
+
name="imio.pm.wsclient.possible_permissions_vocabulary"
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
<utility
|
|
100
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
101
|
+
component=".vocabularies.pm_item_data_vocabularyFactory"
|
|
102
|
+
name="imio.pm.wsclient.pm_item_data_vocabulary"
|
|
103
|
+
/>
|
|
104
|
+
|
|
105
|
+
<utility
|
|
106
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
107
|
+
component=".vocabularies.proposing_groups_for_user_vocabularyFactory"
|
|
108
|
+
name="imio.pm.wsclient.proposing_groups_for_user_vocabulary"
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<utility
|
|
112
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
113
|
+
component=".vocabularies.categories_for_user_vocabularyFactory"
|
|
114
|
+
name="imio.pm.wsclient.categories_for_user_vocabulary"
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
<utility
|
|
118
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
119
|
+
component=".vocabularies.desired_meetingdates_vocabularyFactory"
|
|
120
|
+
name="imio.pm.wsclient.possible_meetingdates_vocabulary"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<utility
|
|
124
|
+
provides="zope.schema.interfaces.IVocabularyFactory"
|
|
125
|
+
component=".vocabularies.annexes_for_user_vocabularyFactory"
|
|
126
|
+
name="imio.pm.wsclient.annexes_for_user_vocabulary"
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
<!-- Resource directory for images -->
|
|
130
|
+
<browser:resourceDirectory
|
|
131
|
+
name="imio.pm.wsclient.images"
|
|
132
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
133
|
+
directory="images"
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<!-- Resource directory for js -->
|
|
137
|
+
<browser:resourceDirectory
|
|
138
|
+
name="imio.pm.wsclient.javascripts"
|
|
139
|
+
layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer"
|
|
140
|
+
directory="javascripts"
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
</configure>
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from AccessControl import Unauthorized
|
|
4
|
+
from imio.pm.wsclient import PMMessageFactory as _PM
|
|
5
|
+
from imio.pm.wsclient import WS4PMClientMessageFactory as _
|
|
6
|
+
from imio.pm.wsclient.config import ALREADY_SENT_TO_PM_ERROR
|
|
7
|
+
from imio.pm.wsclient.config import CORRECTLY_SENT_TO_PM_INFO
|
|
8
|
+
from imio.pm.wsclient.config import NO_PROPOSING_GROUP_ERROR
|
|
9
|
+
from imio.pm.wsclient.config import NO_USER_INFOS_ERROR
|
|
10
|
+
from imio.pm.wsclient.config import SEND_WITHOUT_SUFFICIENT_FIELD_MAPPINGS_DEFINED_WARNING
|
|
11
|
+
from imio.pm.wsclient.config import TAL_EVAL_FIELD_ERROR
|
|
12
|
+
from imio.pm.wsclient.config import UNABLE_TO_CONNECT_ERROR
|
|
13
|
+
from imio.pm.wsclient.config import WS4PMCLIENT_ANNOTATION_KEY
|
|
14
|
+
from imio.pm.wsclient.events import SentToPMEvent
|
|
15
|
+
from imio.pm.wsclient.events import WillbeSendToPMEvent
|
|
16
|
+
from imio.pm.wsclient.interfaces import IRedirect
|
|
17
|
+
from plone import api
|
|
18
|
+
from plone.z3cform.layout import wrap_form
|
|
19
|
+
from Products.CMFPlone.utils import safe_unicode
|
|
20
|
+
from Products.statusmessages.interfaces import IStatusMessage
|
|
21
|
+
from unidecode import unidecode
|
|
22
|
+
from z3c.form import button
|
|
23
|
+
from z3c.form import field
|
|
24
|
+
from z3c.form import form
|
|
25
|
+
from z3c.form.browser.checkbox import CheckBoxFieldWidget
|
|
26
|
+
from z3c.form.contentprovider import ContentProviders
|
|
27
|
+
from z3c.form.interfaces import HIDDEN_MODE
|
|
28
|
+
from z3c.form.interfaces import IFieldsAndContentProvidersForm
|
|
29
|
+
from zope import interface
|
|
30
|
+
from zope import schema
|
|
31
|
+
from zope.annotation import IAnnotations
|
|
32
|
+
from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
|
|
33
|
+
from zope.component import getAdapter
|
|
34
|
+
from zope.component import getMultiAdapter
|
|
35
|
+
from zope.component import queryUtility
|
|
36
|
+
from zope.component.hooks import getSite
|
|
37
|
+
from zope.contentprovider.provider import ContentProviderBase
|
|
38
|
+
from zope.event import notify
|
|
39
|
+
from zope.filerepresentation.interfaces import IRawReadFile
|
|
40
|
+
from zope.i18n import translate
|
|
41
|
+
from zope.interface import implements
|
|
42
|
+
from zope.schema.interfaces import IVocabularyFactory
|
|
43
|
+
|
|
44
|
+
import base64
|
|
45
|
+
import logging
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
from collections import OrderedDict
|
|
50
|
+
except ImportError:
|
|
51
|
+
# Python < 2.7 compatibility
|
|
52
|
+
from ordereddict import OrderedDict
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger('imio.pm.wsclient')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ISendToPloneMeeting(interface.Interface):
|
|
59
|
+
meetingConfigId = schema.TextLine(title=_(u"Meeting config id"))
|
|
60
|
+
proposingGroup = schema.Choice(title=_PM(u"PloneMeeting_label_proposingGroup"),
|
|
61
|
+
description=_(u"Select the proposing group to use "
|
|
62
|
+
"for the created item in PloneMeeting"),
|
|
63
|
+
required=True,
|
|
64
|
+
vocabulary=u'imio.pm.wsclient.proposing_groups_for_user_vocabulary')
|
|
65
|
+
category = schema.Choice(title=_PM(u"PloneMeeting_label_category"),
|
|
66
|
+
description=_(u"Select the category to use for the created item item in PloneMeeting"),
|
|
67
|
+
required=True,
|
|
68
|
+
vocabulary=u'imio.pm.wsclient.categories_for_user_vocabulary')
|
|
69
|
+
preferredMeeting = schema.Choice(title=_PM(u"PloneMeeting_label_preferredMeeting"),
|
|
70
|
+
description=_(u"Select the desired meeting date for the created "
|
|
71
|
+
u"item in PloneMeeting"),
|
|
72
|
+
required=False,
|
|
73
|
+
vocabulary=u'imio.pm.wsclient.possible_meetingdates_vocabulary')
|
|
74
|
+
annexes = schema.List(title=_PM(u"annexes"),
|
|
75
|
+
description=_(u"Select the annexes to send"),
|
|
76
|
+
required=False,
|
|
77
|
+
value_type=schema.Choice(vocabulary=u'imio.pm.wsclient.annexes_for_user_vocabulary'))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class DisplayDataToSendProvider(ContentProviderBase):
|
|
81
|
+
"""
|
|
82
|
+
"""
|
|
83
|
+
template = ViewPageTemplateFile('templates/display_data_to_send.pt')
|
|
84
|
+
|
|
85
|
+
def __init__(self, context, request, view):
|
|
86
|
+
super(DisplayDataToSendProvider, self).__init__(context, request, view)
|
|
87
|
+
self.__parent__ = view
|
|
88
|
+
|
|
89
|
+
def getDisplayableData(self):
|
|
90
|
+
"""
|
|
91
|
+
Returns data to be displayed in the resume form
|
|
92
|
+
Do not display :
|
|
93
|
+
- externalIdentifier;
|
|
94
|
+
- empty values.
|
|
95
|
+
Prepare extraAttrs and annexes to be displayed correctly.
|
|
96
|
+
"""
|
|
97
|
+
data = self.__parent__.form._buildDataDict()
|
|
98
|
+
for data_elem in ('externalIdentifier', 'annexes', 'ignore_validation_for', 'ignore_not_used_data', '__children__'):
|
|
99
|
+
if data_elem in data:
|
|
100
|
+
data.pop(data_elem)
|
|
101
|
+
for elt in data:
|
|
102
|
+
# remove empty data but keep category and proposingGroup even if empty
|
|
103
|
+
value = isinstance(data[elt], str) and data[elt].strip() or data[elt]
|
|
104
|
+
if not value and elt not in ['category', 'proposingGroup', ]:
|
|
105
|
+
data.pop(elt)
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if elt == 'extraAttrs':
|
|
109
|
+
res = ['<fieldset><legend>{0}</legend>{1}</fieldset>'.format(
|
|
110
|
+
translate('PloneMeeting_label_' + extraAttr['key'],
|
|
111
|
+
domain="PloneMeeting",
|
|
112
|
+
context=self.request).encode("utf-8"),
|
|
113
|
+
extraAttr['value']) for extraAttr in data[elt]]
|
|
114
|
+
data[elt] = '<br />'.join(res)
|
|
115
|
+
|
|
116
|
+
if 'title' not in data:
|
|
117
|
+
IStatusMessage(self.request).addStatusMessage(_(SEND_WITHOUT_SUFFICIENT_FIELD_MAPPINGS_DEFINED_WARNING),
|
|
118
|
+
'warning')
|
|
119
|
+
return data
|
|
120
|
+
|
|
121
|
+
def render(self):
|
|
122
|
+
return self.template()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SendToPloneMeetingForm(form.Form):
|
|
126
|
+
implements(IFieldsAndContentProvidersForm)
|
|
127
|
+
|
|
128
|
+
fields = field.Fields(ISendToPloneMeeting)
|
|
129
|
+
fields['annexes'].widgetFactory = CheckBoxFieldWidget
|
|
130
|
+
|
|
131
|
+
ignoreContext = True # don't use context to get widget data
|
|
132
|
+
|
|
133
|
+
contentProviders = ContentProviders()
|
|
134
|
+
contentProviders['dataToSend'] = DisplayDataToSendProvider
|
|
135
|
+
# put the 'dataToSend' in last position
|
|
136
|
+
contentProviders['dataToSend'].position = 3
|
|
137
|
+
label = _(u"Send to PloneMeeting")
|
|
138
|
+
description = u''
|
|
139
|
+
_finishedSent = False
|
|
140
|
+
_displayErrorsInOverlay = False
|
|
141
|
+
|
|
142
|
+
def __init__(self, context, request):
|
|
143
|
+
self.context = context
|
|
144
|
+
self.request = request
|
|
145
|
+
self.meetingConfigId = self._findMeetingConfigId()
|
|
146
|
+
self.proposingGroupId = self.request.form.get('form.widgets.proposingGroupId', [''])[0]
|
|
147
|
+
self.portal_state = getMultiAdapter((self.context, self.request), name=u'plone_portal_state')
|
|
148
|
+
self.portal = self.portal_state.portal()
|
|
149
|
+
self.ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings')
|
|
150
|
+
# manage the label to display to wich meetingConfig we are sending the element...
|
|
151
|
+
self.label = translate(
|
|
152
|
+
_(
|
|
153
|
+
'Send to ${meetingConfigTitle}',
|
|
154
|
+
mapping={
|
|
155
|
+
'meetingConfigTitle': self.ws4pmSettings.getMeetingConfigTitle(self.meetingConfigId),
|
|
156
|
+
},
|
|
157
|
+
),
|
|
158
|
+
domain='imio.pm.wsclient',
|
|
159
|
+
context=self.request,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@button.buttonAndHandler(_('Send'), name='send_to_plonemeeting')
|
|
163
|
+
def handleSendToPloneMeeting(self, action):
|
|
164
|
+
data, errors = self.extractData()
|
|
165
|
+
if errors:
|
|
166
|
+
self.status = self.formErrorsMessage
|
|
167
|
+
return
|
|
168
|
+
self.proposingGroupId = self.request.form.get('form.widgets.proposingGroup')[0]
|
|
169
|
+
# do send to PloneMeeting
|
|
170
|
+
self._doSendToPloneMeeting()
|
|
171
|
+
|
|
172
|
+
@button.buttonAndHandler(_('Cancel'), name='cancel')
|
|
173
|
+
def handleCancel(self, action):
|
|
174
|
+
self._finishedSent = True
|
|
175
|
+
|
|
176
|
+
def update(self):
|
|
177
|
+
""" """
|
|
178
|
+
# use checkAlreadySentToPloneMeeting that will return :
|
|
179
|
+
# None if could not connect
|
|
180
|
+
# True if already sent
|
|
181
|
+
# False if not already sent, in this case we can proceed...
|
|
182
|
+
alreadySent = self.ws4pmSettings.checkAlreadySentToPloneMeeting(self.context, (self.meetingConfigId,))
|
|
183
|
+
settings = self.ws4pmSettings.settings()
|
|
184
|
+
if alreadySent and settings.only_one_sending:
|
|
185
|
+
IStatusMessage(self.request).addStatusMessage(_(ALREADY_SENT_TO_PM_ERROR), "error")
|
|
186
|
+
self._changeFormForErrors()
|
|
187
|
+
return
|
|
188
|
+
else:
|
|
189
|
+
# None means that it was already sent but that it could not connect to PloneMeeting
|
|
190
|
+
# False means that is was not sent, so no connection test is made to PloneMeeting for performance reason
|
|
191
|
+
if alreadySent is not None:
|
|
192
|
+
# now connect to PloneMeeting
|
|
193
|
+
client = self.ws4pmSettings._rest_connectToPloneMeeting()
|
|
194
|
+
if alreadySent is None or not client:
|
|
195
|
+
IStatusMessage(self.request).addStatusMessage(_(UNABLE_TO_CONNECT_ERROR), "error")
|
|
196
|
+
self._changeFormForErrors()
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# do not go further if current user can not create an item in
|
|
200
|
+
# PloneMeeting with any proposingGroup
|
|
201
|
+
userInfos = self.ws4pmSettings._rest_getUserInfos(showGroups=True, suffix='creators')
|
|
202
|
+
if not userInfos or 'extra_include_groups' not in userInfos:
|
|
203
|
+
userThatWillCreate = self.ws4pmSettings._getUserIdToUseInTheNameOfWith()
|
|
204
|
+
if not userInfos:
|
|
205
|
+
IStatusMessage(self.request).addStatusMessage(
|
|
206
|
+
_(NO_USER_INFOS_ERROR, mapping={'userId': userThatWillCreate}), "error")
|
|
207
|
+
else:
|
|
208
|
+
IStatusMessage(self.request).addStatusMessage(
|
|
209
|
+
_(NO_PROPOSING_GROUP_ERROR, mapping={'userId': userThatWillCreate}), "error")
|
|
210
|
+
self._changeFormForErrors()
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# now that we can connect to the webservice, check that the user can actually trigger that action
|
|
214
|
+
# indeed parameters are sent thru the request, and so someone could do nasty things...
|
|
215
|
+
# check that the real currentUrl is on available in object_buttons actions for the user
|
|
216
|
+
availableActions = self.portal.portal_actions.listFilteredActionsFor(self.context).get('object_buttons', [])
|
|
217
|
+
# rebuild real url called by the action
|
|
218
|
+
currentUrl = unicode(self.request['ACTUAL_URL'] + '?meetingConfigId=' + self.meetingConfigId, 'utf-8')
|
|
219
|
+
# now check if this url is available in the actions for the user
|
|
220
|
+
mayDoAction = False
|
|
221
|
+
for action in availableActions:
|
|
222
|
+
if action['url'] == currentUrl:
|
|
223
|
+
mayDoAction = True
|
|
224
|
+
break
|
|
225
|
+
if not mayDoAction:
|
|
226
|
+
raise Unauthorized
|
|
227
|
+
|
|
228
|
+
super(SendToPloneMeetingForm, self).update()
|
|
229
|
+
# while calling parent's update, vocabularies are initialized
|
|
230
|
+
# if there where errors in the vocabularies, hide the form
|
|
231
|
+
if self.request.get('error_in_vocabularies', False):
|
|
232
|
+
self._changeFormForErrors()
|
|
233
|
+
# after calling parent's update, self.actions are available
|
|
234
|
+
self.actions.get('cancel').addClass('standalone')
|
|
235
|
+
|
|
236
|
+
def updateWidgets(self):
|
|
237
|
+
portal = getSite()
|
|
238
|
+
# this is also called by the kss inline_validation, avoid too much work...
|
|
239
|
+
if not portal.__module__ == 'Products.CMFPlone.Portal':
|
|
240
|
+
return
|
|
241
|
+
# hide the meetingConfigId field
|
|
242
|
+
self.fields.get('meetingConfigId').mode = HIDDEN_MODE
|
|
243
|
+
# hide the widget if the linked vocabulary is empty, it means that
|
|
244
|
+
# the linked meetingConfig does not use categories...
|
|
245
|
+
# hide it and set it to required=False
|
|
246
|
+
category_field = self.fields.get('category')
|
|
247
|
+
category_field.mode = not bool(self._getCategoriesVocab()) and HIDDEN_MODE or None
|
|
248
|
+
# as category is a required field, hidding it is not enough...
|
|
249
|
+
category_field.field.required = bool(self._getCategoriesVocab()) and True or False
|
|
250
|
+
# XXX manipulate self.fields BEFORE doing form.Form.updateWidgets
|
|
251
|
+
form.Form.updateWidgets(self)
|
|
252
|
+
# hide annexes if nothing in vocabulary
|
|
253
|
+
annexes_wdg = self.widgets.get('annexes')
|
|
254
|
+
if len(annexes_wdg.terms) == 0:
|
|
255
|
+
annexes_wdg.mode = HIDDEN_MODE
|
|
256
|
+
else:
|
|
257
|
+
annexes_wdg.value = [term.token for term in annexes_wdg.terms]
|
|
258
|
+
# add a 'Choose a value...'
|
|
259
|
+
self.widgets.get('proposingGroup').prompt = True
|
|
260
|
+
self.widgets.get('category').prompt = True
|
|
261
|
+
# initialize the value of the meetingConfigId field to what is found in the request
|
|
262
|
+
self.widgets.get('meetingConfigId').value = self._findMeetingConfigId()
|
|
263
|
+
# add the class 'standalone' on the 'Cancel' button
|
|
264
|
+
|
|
265
|
+
def render(self):
|
|
266
|
+
if self._finishedSent:
|
|
267
|
+
IRedirect(self.request).redirect(self.context.absolute_url())
|
|
268
|
+
return ""
|
|
269
|
+
return super(SendToPloneMeetingForm, self).render()
|
|
270
|
+
|
|
271
|
+
def _findMeetingConfigId(self):
|
|
272
|
+
"""
|
|
273
|
+
Find the meetingConfigId wherever it is...
|
|
274
|
+
"""
|
|
275
|
+
return self.request.get('meetingConfigId', '') or \
|
|
276
|
+
self.request.form.get('form.widgets.meetingConfigId')
|
|
277
|
+
|
|
278
|
+
def _doSendToPloneMeeting(self):
|
|
279
|
+
"""
|
|
280
|
+
The method actually called while sending to PloneMeeting
|
|
281
|
+
"""
|
|
282
|
+
# check again if already sent before sending
|
|
283
|
+
# this avoid double sent from 2 opened form to send
|
|
284
|
+
settings = self.ws4pmSettings.settings()
|
|
285
|
+
if self.ws4pmSettings.checkAlreadySentToPloneMeeting(self.context, (self.meetingConfigId,)) and \
|
|
286
|
+
settings.only_one_sending:
|
|
287
|
+
return False
|
|
288
|
+
# build the creationData
|
|
289
|
+
client = self.ws4pmSettings._rest_connectToPloneMeeting()
|
|
290
|
+
creation_data = self._getCreationData(client)
|
|
291
|
+
|
|
292
|
+
notify(WillbeSendToPMEvent(self.context))
|
|
293
|
+
|
|
294
|
+
# call the REST method actually creating the item
|
|
295
|
+
res = self.ws4pmSettings._rest_createItem(self.meetingConfigId,
|
|
296
|
+
self.proposingGroupId,
|
|
297
|
+
creation_data)
|
|
298
|
+
if res:
|
|
299
|
+
uid, warnings = res
|
|
300
|
+
self.request.set('show_send_to_pm_form', False)
|
|
301
|
+
self.portal.plone_utils.addPortalMessage(_(CORRECTLY_SENT_TO_PM_INFO), 'info')
|
|
302
|
+
if warnings:
|
|
303
|
+
for warning in warnings:
|
|
304
|
+
# show warnings in the web interface and add it to the Zope log
|
|
305
|
+
logger.warning(warning)
|
|
306
|
+
IStatusMessage(self.request).addStatusMessage(_(warning), 'warning')
|
|
307
|
+
# finally save in the self.context annotation that the item has been sent
|
|
308
|
+
annotations = IAnnotations(self.context)
|
|
309
|
+
if WS4PMCLIENT_ANNOTATION_KEY not in annotations:
|
|
310
|
+
annotations[WS4PMCLIENT_ANNOTATION_KEY] = [self.meetingConfigId, ]
|
|
311
|
+
else:
|
|
312
|
+
# do not use .append directly on the annotations or it does not save
|
|
313
|
+
# correctly and when Zope restarts, the added annotation is lost???
|
|
314
|
+
existingAnnotations = list(annotations[WS4PMCLIENT_ANNOTATION_KEY])
|
|
315
|
+
existingAnnotations.append(self.meetingConfigId)
|
|
316
|
+
annotations[WS4PMCLIENT_ANNOTATION_KEY] = existingAnnotations
|
|
317
|
+
self._finishedSent = True
|
|
318
|
+
|
|
319
|
+
notify(SentToPMEvent(self.context))
|
|
320
|
+
|
|
321
|
+
return True
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def _getCreationData(self, client):
|
|
325
|
+
"""
|
|
326
|
+
Build creationData dict that will be used to actually create
|
|
327
|
+
the item in PloneMeeting thru REST createItem call
|
|
328
|
+
"""
|
|
329
|
+
data = self._buildDataDict()
|
|
330
|
+
# now that every values are evaluated, build the CreationData
|
|
331
|
+
creation_data = {
|
|
332
|
+
k: "" for k in self.ws4pmSettings._rest_getItemCreationAvailableData()
|
|
333
|
+
if k in data.keys()
|
|
334
|
+
}
|
|
335
|
+
for elt in data:
|
|
336
|
+
# proposingGroup is managed apart
|
|
337
|
+
if elt == u'proposingGroup':
|
|
338
|
+
continue
|
|
339
|
+
if isinstance(data[elt], str):
|
|
340
|
+
data[elt] = unicode(data[elt], 'utf-8')
|
|
341
|
+
if elt == 'extraAttrs':
|
|
342
|
+
for attr in data[elt]:
|
|
343
|
+
attr['value'] = safe_unicode(attr['value'])
|
|
344
|
+
creation_data[elt] = data[elt]
|
|
345
|
+
# initialize the externalIdentifier to the context UID
|
|
346
|
+
creation_data['externalIdentifier'] = self.context.UID()
|
|
347
|
+
|
|
348
|
+
return creation_data
|
|
349
|
+
|
|
350
|
+
def _buildDataDict(self):
|
|
351
|
+
"""
|
|
352
|
+
"""
|
|
353
|
+
data = OrderedDict()
|
|
354
|
+
settings = self.ws4pmSettings.settings()
|
|
355
|
+
# initialize annexes field from the form, not the field_mappings
|
|
356
|
+
data['__children__'] = self._buildAnnexesData()
|
|
357
|
+
data['preferredMeeting'] = self.request.form.get('form.widgets.preferredMeeting', [u'', ])[0]
|
|
358
|
+
# if preferredMeeting is '--NOVALUE--', remove it from data to send
|
|
359
|
+
if data['preferredMeeting'] in ('--NOVALUE--', ''):
|
|
360
|
+
data.pop('preferredMeeting')
|
|
361
|
+
# initialize category field in case it is not defined in field_mappings
|
|
362
|
+
data['category'] = self.request.form.get('form.widgets.category', [u'', ])[0]
|
|
363
|
+
# if category is '--NOVALUE--', consider it empty
|
|
364
|
+
if data['category'] == '--NOVALUE--':
|
|
365
|
+
data['category'] = ''
|
|
366
|
+
for availableData in settings.field_mappings:
|
|
367
|
+
field_name = availableData['field_name']
|
|
368
|
+
if field_name == 'category':
|
|
369
|
+
# check that the meetingConfig we want to send the item to
|
|
370
|
+
# actually use categories
|
|
371
|
+
if not self._getCategoriesVocab():
|
|
372
|
+
data[field_name] = u''
|
|
373
|
+
else:
|
|
374
|
+
continue
|
|
375
|
+
expr = availableData['expression']
|
|
376
|
+
# make the meetingConfigId available in the expression
|
|
377
|
+
vars = {}
|
|
378
|
+
vars['meetingConfigId'] = self.meetingConfigId
|
|
379
|
+
vars['proposingGroupId'] = self.proposingGroupId
|
|
380
|
+
# evaluate the expression
|
|
381
|
+
try:
|
|
382
|
+
data[field_name] = self.ws4pmSettings.renderTALExpression(self.context,
|
|
383
|
+
self.portal,
|
|
384
|
+
expr,
|
|
385
|
+
vars)
|
|
386
|
+
except Exception, e:
|
|
387
|
+
IStatusMessage(self.request).addStatusMessage(
|
|
388
|
+
_(TAL_EVAL_FIELD_ERROR, mapping={'expr': expr,
|
|
389
|
+
'field_name': field_name,
|
|
390
|
+
'error': e}),
|
|
391
|
+
"error")
|
|
392
|
+
return self.request.RESPONSE.redirect(self.context.absolute_url())
|
|
393
|
+
return data
|
|
394
|
+
|
|
395
|
+
def _buildAnnexesData(self):
|
|
396
|
+
catalog = api.portal.get_tool('portal_catalog')
|
|
397
|
+
annexes_data = []
|
|
398
|
+
selected_annexes = self.request.form.get('form.widgets.annexes', [])
|
|
399
|
+
for selected_annex in selected_annexes:
|
|
400
|
+
annex_brains = catalog.unrestrictedSearchResults(UID=selected_annex)
|
|
401
|
+
if annex_brains:
|
|
402
|
+
annex = annex_brains[0].getObject()
|
|
403
|
+
annex_file = getAdapter(annex, IRawReadFile)
|
|
404
|
+
annex_title = type(annex.title) in [unicode] and annex.title or annex.title.decode('utf-8')
|
|
405
|
+
annex_name = type(annex_file.name) in [unicode] and annex_file.name or annex_file.name.decode('utf-8')
|
|
406
|
+
annexes_data.append(
|
|
407
|
+
{
|
|
408
|
+
"@type": "annex",
|
|
409
|
+
"title": unidecode(annex_title),
|
|
410
|
+
"file": {
|
|
411
|
+
"filename": unidecode(annex_name),
|
|
412
|
+
"data": base64.b64encode(annex_file.read()),
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
return annexes_data
|
|
417
|
+
|
|
418
|
+
def _getCategoriesVocab(self):
|
|
419
|
+
"""Return the vocabulary used for the categories field"""
|
|
420
|
+
portal = getSite()
|
|
421
|
+
factory = queryUtility(IVocabularyFactory, u'imio.pm.wsclient.categories_for_user_vocabulary')
|
|
422
|
+
return factory(portal)
|
|
423
|
+
|
|
424
|
+
def _getProposingGroupsVocab(self):
|
|
425
|
+
"""Return the vocabulary used for the proposingGroup field"""
|
|
426
|
+
portal = getSite()
|
|
427
|
+
factory = queryUtility(IVocabularyFactory, u'imio.pm.wsclient.proposing_groups_for_user_vocabulary')
|
|
428
|
+
return factory(portal)
|
|
429
|
+
|
|
430
|
+
def _changeFormForErrors(self):
|
|
431
|
+
"""
|
|
432
|
+
"""
|
|
433
|
+
# if we use an overlay popup do some nasty things...
|
|
434
|
+
if 'ajax_load' in self.request.form:
|
|
435
|
+
self.template = ViewPageTemplateFile('templates/show_errors_in_overlay_form.pt').__get__(self, '')
|
|
436
|
+
else:
|
|
437
|
+
self._finishedSent = True
|
|
438
|
+
super(SendToPloneMeetingForm, self).update()
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
SendToPloneMeetingWrapper = wrap_form(SendToPloneMeetingForm)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|