def __call__(self, context): """Query every available proposingGroups for current user in a distant PloneMeeting.""" portal = getSite() # while called in an inline_validation, portal is not correct... if not portal.__module__ == 'Products.CMFPlone.Portal': portal = portal.aq_inner.aq_parent ws4pmsettings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') field_mappings = ws4pmsettings.settings().field_mappings if not field_mappings: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(NO_FIELD_MAPPINGS_ERROR), 'error') return SimpleVocabulary([]) forcedProposingGroup = None vars = {} vars['meetingConfigId'] = portal.REQUEST.get('meetingConfigId') for field_mapping in field_mappings: # try to find out if a proposingGroup is forced in the configuration if field_mapping[u'field_name'] == 'proposingGroup': try: forcedProposingGroup = ws4pmsettings.renderTALExpression(context, portal, field_mapping['expression'], vars) break except Exception, e: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(TAL_EVAL_FIELD_ERROR, mapping={'expr': field_mapping['expression'], 'field_name': field_mapping['field_name'], 'error': e}), 'error') return SimpleVocabulary([])
def available(self): """ Check if the viewlet is available and needs to be shown. This method returns either True or False, or a tuple of str that contains an information message (str1 is the translated message and str2 is the message type : info, error, warning). """ # if we have an annotation specifying that the item was sent, we show the viewlet settings = self.ws4pmSettings.settings() isLinked = self.ws4pmSettings.checkAlreadySentToPloneMeeting( self.context) # in case it could not connect to PloneMeeting, checkAlreadySentToPloneMeeting returns None if isLinked is None: return (_(UNABLE_TO_CONNECT_ERROR), 'error') viewlet_display_condition = settings.viewlet_display_condition # if we have no defined viewlet_display_condition, use the isLinked value if not viewlet_display_condition or not viewlet_display_condition.strip( ): return isLinked # add 'isLinked' to data available in the TAL expression vars = {} vars['isLinked'] = isLinked try: res = self.ws4pmSettings.renderTALExpression( self.context, self.portal_state.portal(), settings.viewlet_display_condition, vars) if not res: return False except Exception, e: return (_(UNABLE_TO_DISPLAY_VIEWLET_ERROR, mapping={ 'expr': settings.viewlet_display_condition, 'field_name': 'viewlet_display_condition', 'error': e }), 'error')
class IUserMappingsSchema(Interface): """Schema used for the datagrid field 'user_mappings' of IWS4PMClientSettings.""" local_userid = schema.TextLine( title=_("Local user id"), required=True) pm_userid = schema.TextLine( title=_("PloneMeeting corresponding user id"), required=True,)
class IFieldMappingsSchema(Interface): """Schema used for the datagrid field 'field_mappings' of IWS4PMClientSettings.""" field_name = schema.Choice( title=_("PloneMeeting field name"), required=True, vocabulary=u'imio.pm.wsclient.pm_item_data_vocabulary') expression = schema.TextLine( title=_("TAL expression to evaluate for the corresponding PloneMeeting field name"), required=True,)
class WS4PMClientSettingsEditForm(RegistryEditForm): """ Define form logic """ schema = IWS4PMClientSettings label = _(u"WS4PM Client settings") description = _(u"""""") fields = field.Fields(IWS4PMClientSettings) fields['generated_actions'].widgetFactory = DataGridFieldFactory fields['field_mappings'].widgetFactory = DataGridFieldFactory fields['allowed_annexes_types'].widgetFactory = DataGridFieldFactory fields['user_mappings'].widgetFactory = DataGridFieldFactory def updateFields(self): super(WS4PMClientSettingsEditForm, self).updateFields() portal = getSite() # this is also called by the kss inline_validation, avoid too much work... if not portal.__module__ == 'Products.CMFPlone.Portal': return ctrl = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') # if we can not getConfigInfos from the given pm_url, we do not permit to edit other parameters generated_actions_field = self.fields.get('generated_actions') field_mappings = self.fields.get('field_mappings') if not ctrl._soap_getConfigInfos(): generated_actions_field.mode = 'display' field_mappings.mode = 'display' else: if generated_actions_field.mode == 'display' and \ 'form.buttons.save' not in self.request.form.keys(): # only change mode while not in the "saving" process (that calls updateFields, but why?) # because it leads to loosing generated_actions because a [] is returned by extractDate here above self.fields.get('generated_actions').mode = 'input' self.fields.get('field_mappings').mode = 'input' def updateWidgets(self): super(WS4PMClientSettingsEditForm, self).updateWidgets() @button.buttonAndHandler(_('Save'), name=None) def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"), "info") self.context.REQUEST.RESPONSE.redirect("@@ws4pmclient-settings") @button.buttonAndHandler(_('Cancel'), name='cancel') def handleCancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"), "info") self.request.response.redirect("%s/%s" % (self.context.absolute_url(), self.control_panel_view))
class IGeneratedActionsSchema(Interface): """Schema used for the datagrid field 'generated_actions' of IWS4PMClientSettings.""" condition = schema.TextLine( title=_("TAL Condition"), required=False,) permissions = schema.Choice( title=_("Permissions"), required=False, vocabulary=u'imio.pm.wsclient.possible_permissions_vocabulary') pm_meeting_config_id = schema.Choice( title=_("PloneMeeting meetingConfig id"), required=True, vocabulary=u'imio.pm.wsclient.pm_meeting_config_id_vocabulary')
def __call__(self, context): """Query every available categories for current user in a distant PloneMeeting.""" portal = getSite() # while called in an inline_validation, portal is not correct... if not portal.__module__ == 'Products.CMFPlone.Portal': portal = portal.aq_inner.aq_parent ws4pmsettings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') configInfos = ws4pmsettings._soap_getConfigInfos(showCategories=True) if not configInfos: portal.REQUEST.set('error_in_vocabularies', True) # add a status message if the main error is not the fact that we can not connect to the WS if configInfos is not None: IStatusMessage(portal.REQUEST).addStatusMessage( _(NO_CONFIG_INFOS_ERROR), 'error') return SimpleVocabulary([]) request = api.portal.getRequest() meeting_config_id = request.get('meetingConfigId', request.form.get('form.widgets.meetingConfigId')) data = {'meetingConfigId': meeting_config_id} possible_meetings = ws4pmsettings._soap_getMeetingsAcceptingItems(data) local = pytz.timezone("Europe/Brussels") for meeting in possible_meetings: meeting['date'] = meeting['date'].astimezone(local) terms = [] allowed_meetings = queryMultiAdapter((context, possible_meetings), IPreferredMeetings) meetings = allowed_meetings and allowed_meetings.get() or possible_meetings for meeting_info in meetings: terms.append(SimpleTerm(unicode(meeting_info['UID']), unicode(meeting_info['UID']), unicode(meeting_info['date']),)) return SimpleVocabulary(terms)
def test_canNotSendIfInNoPMCreatorGroup(self): """ If the user that wants to send the item in PloneMeeting is not a creator in PM, aka is not in a _creators suffixed group, a message is displayed to him. """ # remove pmCreator2 from the vendors_creators group # first check that the user is actually in a _creators group pmCreator2 = self.portal.portal_membership.getMemberById('pmCreator2') self.assertTrue([ group for group in self.portal.acl_users.source_groups. getGroupsForPrincipal(pmCreator2) if group.endswith('_creators') ]) self.portal.portal_groups.removePrincipalFromGroup( 'pmCreator2', self.vendors_creators) # pmCreator2 is no more in a _creators group self.assertFalse([ group for group in self.portal.acl_users.source_groups. getGroupsForPrincipal(pmCreator2) if group.endswith('_creators') ]) # try to send the item setCorrectSettingsConfig(self.portal) self.changeUser('pmCreator2') self.tool.getPloneMeetingFolder('plonemeeting-assembly', 'pmCreator2') transaction.commit() # create an element to send... document = createDocument(self.portal.Members.pmCreator2) messages = IStatusMessage(self.request) # if no item is created, _sendToPloneMeeting returns None self.assertFalse( self._sendToPloneMeeting(document, user='******', proposingGroup=self.vendors_uid)) msg = _(NO_PROPOSING_GROUP_ERROR, mapping={'userId': 'pmCreator2'}) self.assertEqual(messages.show()[-3].message, translate(msg))
def __call__(self): """ """ # first check that we can connect to PloneMeeting client = self.ws4pmSettings._soap_connectToPloneMeeting() if not client: IStatusMessage(self.request).addStatusMessage( _(UNABLE_TO_CONNECT_ERROR), "error") return self.request.RESPONSE.redirect(self.context.absolute_url())
def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return self.applyChanges(data) IStatusMessage(self.request).addStatusMessage(_(u"Changes saved"), "info") self.context.REQUEST.RESPONSE.redirect("@@ws4pmclient-settings")
def _soap_getItemTemplate(self, data): """Query the getItemTemplate SOAP server method.""" client = self._soap_connectToPloneMeeting() if client is not None: if 'inTheNameOf' not in data: data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() try: return client.service.getItemTemplate(**data) except Exception, exc: IStatusMessage(self.request).addStatusMessage( _(u"An error occured while generating the document in PloneMeeting! " "The error message was : %s" % exc), "error")
def __call__(self): """ """ ws_error = super(DownloadAnnexFromItemView, self).__call__() if ws_error: return ws_error response = self.request.RESPONSE if not self.annex_id: IStatusMessage(self.request).addStatusMessage( _(ANNEXID_MANDATORY_ERROR), "error") return response.redirect(self.context.absolute_url()) res = self.ws4pmSettings._soap_getItemInfos({ 'UID': self.itemUID, 'showAnnexes': True, 'allowed_annexes_types': self.annex_type, 'include_annex_binary': True, }) if not res: # an error occured, redirect to user to the context, a statusMessage will be displayed IStatusMessage(self.request).addStatusMessage( _(MISSING_FILE_ERROR), "error") return self.request.RESPONSE.redirect(self.context.absolute_url()) annex_info = [anx for anx in res[0].annexes if anx.id == self.annex_id] if annex_info: annex_info = annex_info[0] annex = annex_info.file mimetype = self.portal.mimetypes_registry.lookupExtension( annex_info.filename.split('.')[-1].lower()) response.setHeader('Content-Type', mimetype) response.setHeader('Content-Disposition', 'inline;filename="%s"' % annex_info.filename) return base64.b64decode(annex)
def getPloneMeetingLinkedInfos(self): """Search items created for context. To get every informations we need, we will use getItemInfos(showExtraInfos=True) because we need the meetingConfig id and title... So search the items with searchItems then query again each found items with getConfigInfos. If we encounter an error, we return a tuple as 'usual' like in self.available""" try: items = self.ws4pmSettings._soap_searchItems( {'externalIdentifier': self.context.UID()}) except Exception, exc: return (_( u"An error occured while searching for linked items in PloneMeeting! " "The error message was : %s" % exc), 'error')
def __call__(self): """ """ ws_error = super(GenerateItemTemplateView, self).__call__() if ws_error: return ws_error # if we can connect, proceed! response = self.request.RESPONSE mimetype = self.portal.mimetypes_registry.lookupExtension( self.templateFormat) if not mimetype: IStatusMessage(self.request).addStatusMessage( _(UNABLE_TO_DETECT_MIMETYPE_ERROR), "error") return response.redirect(self.context.absolute_url()) if not self.templateFilename: IStatusMessage(self.request).addStatusMessage( _(FILENAME_MANDATORY_ERROR), "error") return response.redirect(self.context.absolute_url()) # set relevant header for response so the browser behave normally with returned file type response.setHeader('Content-Type', mimetype.normalized()) response.setHeader( 'Content-Disposition', 'inline;filename="%s.%s"' % (self.templateFilename, self.templateFormat)) res = self.ws4pmSettings._soap_getItemTemplate({ 'itemUID': self.itemUID, 'templateId': self.templateId, }) if not res: # an error occured, redirect to user to the context, a statusMessage will be displayed return self.request.RESPONSE.redirect(self.context.absolute_url()) return base64.b64decode(res)
def _soap_createItem(self, meetingConfigId, proposingGroupId, creationData): """Query the createItem SOAP server method.""" client = self._soap_connectToPloneMeeting() if client is not None: try: # we create an item inTheNameOf the currently connected member # _getUserIdToCreateWith returns None if the settings defined username creates the item inTheNameOf = self._getUserIdToUseInTheNameOfWith() res = client.service.createItem(meetingConfigId, proposingGroupId, creationData, inTheNameOf=inTheNameOf) # return 'UID' and 'warnings' if any current user is a Manager warnings = [] if self.context.portal_membership.getAuthenticatedMember().has_role('Manager'): warnings = 'warnings' in res.__keylist__ and res['warnings'] or [] return res['UID'], warnings except Exception, exc: IStatusMessage(self.request).addStatusMessage(_(CONFIG_CREATE_ITEM_PM_ERROR, mapping={'error': exc}), "error")
def _soap_connectToPloneMeeting(self): """ Connect to distant PloneMeeting. Either return None or the connected client. """ settings = self.settings() url = self.request.form.get('form.widgets.pm_url') or settings.pm_url or '' username = self.request.form.get('form.widgets.pm_username') or settings.pm_username or '' password = self.request.form.get('form.widgets.pm_password') or settings.pm_password or '' timeout = self.request.form.get('form.widgets.pm_timeout') or settings.pm_timeout or '' imp = Import('http://schemas.xmlsoap.org/soap/encoding/') d = ImportDoctor(imp) t = HttpAuthenticated(username=username, password=password) try: client = Client(url, doctor=d, transport=t, timeout=int(timeout)) # call a SOAP server test method to check that everything is fine with given parameters client.service.testConnection('') except Exception, e: # if we are really on the configuration panel, display relevant message if self.request.get('URL', '').endswith('@@ws4pmclient-settings'): IStatusMessage(self.request).addStatusMessage( _(CONFIG_UNABLE_TO_CONNECT_ERROR, mapping={'error': (e.message or str(e.reason))}), "error") return None
because we need the meetingConfig id and title... So search the items with searchItems then query again each found items with getConfigInfos. If we encounter an error, we return a tuple as 'usual' like in self.available""" try: items = self.ws4pmSettings._soap_searchItems( {'externalIdentifier': self.context.UID()}) except Exception, exc: return (_( u"An error occured while searching for linked items in PloneMeeting! " "The error message was : %s" % exc), 'error') # if we are here, it means that the current element is actually linked to item(s) # in PloneMeeting but the current user can not see it! if not items: # we return a message in a tuple return (_(CAN_NOT_SEE_LINKED_ITEMS_INFO), 'info') annotations = IAnnotations(self.context) sent_to = annotations[WS4PMCLIENT_ANNOTATION_KEY] res = [] # to be able to know if some infos in PloneMeeting where not found # for current user, save the infos actually shown... settings = self.ws4pmSettings.settings() allowed_annexes_types = [ line.values()[0] for line in settings.allowed_annexes_types ] shownItemsMeetingConfigId = [] for item in items: res.append( self.ws4pmSettings._soap_getItemInfos({ 'UID': item['UID'],
def handleCancel(self, action): IStatusMessage(self.request).addStatusMessage(_(u"Edit cancelled"), "info") self.request.response.redirect("%s/%s" % (self.context.absolute_url(), self.control_panel_view))
class proposing_groups_for_user_vocabulary(object): implements(IVocabularyFactory) def __call__(self, context): """Query every available proposingGroups for current user in a distant PloneMeeting.""" portal = getSite() # while called in an inline_validation, portal is not correct... if not portal.__module__ == 'Products.CMFPlone.Portal': portal = portal.aq_inner.aq_parent ws4pmsettings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') field_mappings = ws4pmsettings.settings().field_mappings if not field_mappings: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(NO_FIELD_MAPPINGS_ERROR), 'error') return SimpleVocabulary([]) forcedProposingGroup = None vars = {} vars['meetingConfigId'] = portal.REQUEST.get('meetingConfigId') for field_mapping in field_mappings: # try to find out if a proposingGroup is forced in the configuration if field_mapping[u'field_name'] == 'proposingGroup': try: forcedProposingGroup = ws4pmsettings.renderTALExpression(context, portal, field_mapping['expression'], vars) break except Exception, e: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(TAL_EVAL_FIELD_ERROR, mapping={'expr': field_mapping['expression'], 'field_name': field_mapping['field_name'], 'error': e}), 'error') return SimpleVocabulary([]) # even if we get a forcedProposingGroup, double check that the current user can actually use it userInfos = ws4pmsettings._soap_getUserInfos(showGroups=True, suffix='creators') if not userInfos or 'groups' not in userInfos: portal.REQUEST.set('error_in_vocabularies', True) # add a status message if the main error is not the fact that we can not connect to the WS if userInfos is not None: userThatWillCreate = ws4pmsettings._getUserIdToUseInTheNameOfWith() IStatusMessage(portal.REQUEST).addStatusMessage( _(NO_USER_INFOS_ERROR, mapping={'userId': userThatWillCreate}), 'error') return SimpleVocabulary([]) terms = [] forcedProposingGroupExists = not forcedProposingGroup and True or False for group in userInfos['groups']: if forcedProposingGroup == group['id']: forcedProposingGroupExists = True terms.append(SimpleTerm(unicode(group['id']), unicode(group['id']), unicode(group['title']),)) break if not forcedProposingGroup: terms.append(SimpleTerm(unicode(group['id']), unicode(group['id']), unicode(group['title']),)) if not forcedProposingGroupExists: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(CAN_NOT_CREATE_FOR_PROPOSING_GROUP_ERROR), 'error') return SimpleVocabulary([]) return SimpleVocabulary(terms)
class IAllowedAnnexTypesSchema(Interface): """Schema used for the datagrid field 'allowed_annex_type' of IWS4PMClientSettings.""" annex_type = schema.TextLine( title=_("Annex type"), required=True)
class categories_for_user_vocabulary(object): implements(IVocabularyFactory) def __call__(self, context): """Query every available categories for current user in a distant PloneMeeting.""" portal = getSite() # while called in an inline_validation, portal is not correct... if not portal.__module__ == 'Products.CMFPlone.Portal': portal = portal.aq_inner.aq_parent ws4pmsettings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') field_mappings = ws4pmsettings.settings().field_mappings if not field_mappings: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(NO_FIELD_MAPPINGS_ERROR), 'error') return SimpleVocabulary([]) forcedCategory = None vars = {} meetingConfigId = portal.REQUEST.get('meetingConfigId') or \ portal.REQUEST.form.get('form.widgets.meetingConfigId') vars['meetingConfigId'] = meetingConfigId for field_mapping in field_mappings: # try to find out if a proposingGroup is forced in the configuration if field_mapping[u'field_name'] == 'category': try: forcedCategory = ws4pmsettings.renderTALExpression(context, portal, field_mapping['expression'], vars) break except Exception, e: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(TAL_EVAL_FIELD_ERROR, mapping={'expr': field_mapping['expression'], 'field_name': field_mapping['field_name'], 'error': e}), 'error') return SimpleVocabulary([]) configInfos = ws4pmsettings._soap_getConfigInfos(showCategories=True) if not configInfos: portal.REQUEST.set('error_in_vocabularies', True) # add a status message if the main error is not the fact that we can not connect to the WS if configInfos is not None: IStatusMessage(portal.REQUEST).addStatusMessage( _(NO_CONFIG_INFOS_ERROR), 'error') return SimpleVocabulary([]) categories = [] # find categories for given meetingConfigId for configInfo in configInfos.configInfo: if configInfo.id == meetingConfigId: categories = hasattr(configInfo, 'categories') and configInfo.categories or () break # if not categories is returned, it means that the meetingConfig does # not use categories... if not categories: return SimpleVocabulary([]) terms = [] forcedCategoryExists = not forcedCategory and True or False for category in categories: if forcedCategory == category.id: forcedCategoryExists = True terms.append(SimpleTerm(unicode(category.id), unicode(category.id), unicode(category.title),)) break if not forcedCategory: terms.append(SimpleTerm(unicode(category.id), unicode(category.id), unicode(category.title),)) if not forcedCategoryExists: portal.REQUEST.set('error_in_vocabularies', True) IStatusMessage(portal.REQUEST).addStatusMessage( _(CAN_NOT_CREATE_WITH_CATEGORY_ERROR), 'error') return SimpleVocabulary([]) return SimpleVocabulary(terms)
class IWS4PMClientSettings(Interface): """ Configuration of the WS4PM Client """ pm_url = schema.TextLine( title=_(u"PloneMeeting WSDL URL"), required=True,) pm_timeout = schema.Int( title=_(u"PloneMeeting connection timeout"), description=_(u"Enter the timeout while connecting to PloneMeeting. Do not set a too high timeout because it " "will impact the load of the viewlet showing PM infos on a sent element if PM is not available. " "Default is '10' seconds."), default=10, required=True,) pm_username = schema.TextLine( title=_("PloneMeeting username to use"), description=_(u"The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding " "the <i>User ids mappings</i> defined here under."), required=True,) pm_password = schema.Password( title=_("PloneMeeting password to use"), required=True,) only_one_sending = schema.Bool( title=_("An element can be sent one time only"), default=True, required=True,) viewlet_display_condition = schema.TextLine( title=_("Viewlet display condition"), description=_("Enter a TAL expression that will be evaluated to check if the viewlet displaying " "informations about the created items in PloneMeeting should be displayed. " "If empty, the viewlet will only be displayed if an item is actually linked to it. " "The 'isLinked' variable representing this default behaviour is available " "in the TAL expression."), required=False,) field_mappings = schema.List( title=_("Field accessor mappings"), description=_("For every available data you can send, define in the mapping a TAL expression that will be " "executed to obtain the correct value to send. The 'meetingConfigId' and 'proposingGroupId' " "variables are also available for the expression. Special case for the 'proposingGroup' and " "'category' fields, you can 'force' the use of a particular value by defining it here. If not " "defined the user will be able to use every 'proposingGroup' or 'category' he is allowed to " "use in PloneMeeting."), value_type=DictRow(title=_("Field mappings"), schema=IFieldMappingsSchema, required=False), required=False,) allowed_annexes_types = schema.List( title=_("Allowed annexes types"), description=_("List here the annexes types allowed to be display in the linked meeting item viewlet"), value_type=DictRow(title=_("Allowed annex type"), schema=IAllowedAnnexTypesSchema, required=False), default=[], required=False,) user_mappings = schema.List( title=_("User ids mappings"), description=_("By default, while sending an element to PloneMeeting, the user id of the logged in user " "is used and a binding is made to the same user id in PloneMeeting. " "If the local user id does not exist in PloneMeeting, you can define here the user mappings " "to use. For example : 'jdoe' in 'Local user id' of the current application correspond to " "'johndoe' in PloneMeeting."), value_type=DictRow(title=_("User mappings"), schema=IUserMappingsSchema, required=False), required=False,) generated_actions = schema.List( title=_("Generated actions"), description=_("Actions to send an item to PloneMeeting can be generated. First enter a 'TAL condition' " "evaluated to show the action then choose permission(s) the user must have to see the action. " "Finally, choose the meetingConfig the item will be sent to."), value_type=DictRow(title=_("Actions"), schema=IGeneratedActionsSchema, required=False), required=False,)