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.
Files changed (69) hide show
  1. imio/pm/wsclient/__init__.py +11 -0
  2. imio/pm/wsclient/browser/__init__.py +1 -0
  3. imio/pm/wsclient/browser/annexes.py +60 -0
  4. imio/pm/wsclient/browser/configure.zcml +143 -0
  5. imio/pm/wsclient/browser/forms.py +441 -0
  6. imio/pm/wsclient/browser/images/annex.png +0 -0
  7. imio/pm/wsclient/browser/images/doc.png +0 -0
  8. imio/pm/wsclient/browser/images/docx.png +0 -0
  9. imio/pm/wsclient/browser/images/odt.png +0 -0
  10. imio/pm/wsclient/browser/images/pdf.png +0 -0
  11. imio/pm/wsclient/browser/images/rtf.png +0 -0
  12. imio/pm/wsclient/browser/images/send_to_plonemeeting.png +0 -0
  13. imio/pm/wsclient/browser/javascripts/popups.js +71 -0
  14. imio/pm/wsclient/browser/redirect.py +43 -0
  15. imio/pm/wsclient/browser/settings.py +730 -0
  16. imio/pm/wsclient/browser/templates/contentprovider-widget.pt +2 -0
  17. imio/pm/wsclient/browser/templates/display_data_to_send.pt +8 -0
  18. imio/pm/wsclient/browser/templates/plonemeeting_infos.pt +71 -0
  19. imio/pm/wsclient/browser/templates/show_errors_in_overlay_form.pt +16 -0
  20. imio/pm/wsclient/browser/viewlets.py +159 -0
  21. imio/pm/wsclient/browser/views.py +121 -0
  22. imio/pm/wsclient/browser/vocabularies.py +355 -0
  23. imio/pm/wsclient/config.py +63 -0
  24. imio/pm/wsclient/configure.zcml +48 -0
  25. imio/pm/wsclient/events.py +28 -0
  26. imio/pm/wsclient/interfaces.py +77 -0
  27. imio/pm/wsclient/locales/PloneMeeting.pot +27 -0
  28. imio/pm/wsclient/locales/en/LC_MESSAGES/PloneMeeting.mo +0 -0
  29. imio/pm/wsclient/locales/en/LC_MESSAGES/PloneMeeting.po +29 -0
  30. imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.mo +0 -0
  31. imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po +352 -0
  32. imio/pm/wsclient/locales/fr/LC_MESSAGES/PloneMeeting.mo +0 -0
  33. imio/pm/wsclient/locales/fr/LC_MESSAGES/PloneMeeting.po +29 -0
  34. imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.mo +0 -0
  35. imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po +352 -0
  36. imio/pm/wsclient/locales/imio.pm.wsclient.pot +310 -0
  37. imio/pm/wsclient/locales/sync_pos.sh +11 -0
  38. imio/pm/wsclient/profiles/default/browserlayer.xml +7 -0
  39. imio/pm/wsclient/profiles/default/controlpanel.xml +20 -0
  40. imio/pm/wsclient/profiles/default/imio_pm_wsclient_marker.txt +4 -0
  41. imio/pm/wsclient/profiles/default/import_steps.xml +11 -0
  42. imio/pm/wsclient/profiles/default/jsregistry.xml +11 -0
  43. imio/pm/wsclient/profiles/default/metadata.xml +8 -0
  44. imio/pm/wsclient/profiles/default/registry.xml +4 -0
  45. imio/pm/wsclient/profiles/default/rolemap.xml +19 -0
  46. imio/pm/wsclient/profiles/testing/imio_pm_wsclient_testing_marker.txt +4 -0
  47. imio/pm/wsclient/profiles/testing/metadata.xml +8 -0
  48. imio/pm/wsclient/setuphandlers.py +27 -0
  49. imio/pm/wsclient/subscribers.zcml +17 -0
  50. imio/pm/wsclient/testing-settings.zcml +14 -0
  51. imio/pm/wsclient/testing.py +57 -0
  52. imio/pm/wsclient/testing.zcml +17 -0
  53. imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py +185 -0
  54. imio/pm/wsclient/tests/__init__.py +1 -0
  55. imio/pm/wsclient/tests/testEvents.py +104 -0
  56. imio/pm/wsclient/tests/testForms.py +285 -0
  57. imio/pm/wsclient/tests/testRESTMethods.py +503 -0
  58. imio/pm/wsclient/tests/testSettings.py +177 -0
  59. imio/pm/wsclient/tests/testViewlets.py +284 -0
  60. imio/pm/wsclient/tests/testViews.py +166 -0
  61. imio/pm/wsclient/tests/testVocabularies.py +148 -0
  62. imio/pm/wsclient/tests/vocabularies.py +24 -0
  63. imio/pm/wsclient/upgrade.py +31 -0
  64. imio.pm.wsclient-2.0.4-py3.13-nspkg.pth +2 -0
  65. imio_pm_wsclient-2.0.4.dist-info/METADATA +249 -0
  66. imio_pm_wsclient-2.0.4.dist-info/RECORD +69 -0
  67. imio_pm_wsclient-2.0.4.dist-info/WHEEL +5 -0
  68. imio_pm_wsclient-2.0.4.dist-info/namespace_packages.txt +2 -0
  69. 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