From 1185b4750e16c3311196328afaa855805420fc38 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 29 Jun 2022 16:33:48 +0200 Subject: [PATCH 01/65] Pin `gnureadline` --- buildout.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/buildout.cfg b/buildout.cfg index bf7482e..81a952b 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -58,3 +58,4 @@ initialization = [versions] imio.pm.wsclient = +gnureadline = 8.1.2 From 51838fd9866727cb61648d35f9f6938fb10736e7 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 29 Jun 2022 16:34:06 +0200 Subject: [PATCH 02/65] Update requirements to reflect pinned versions --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2fabf24..76e142a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ # using buildout.pm -#setuptools == 38.2.4 -#zc.buildout == 2.13.1 +zc.buildout==2.13.3 +setuptools==44.1.1 From 9c9251ab505cd3d9e8456fae9fb577cd93ad1300 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 29 Jun 2022 16:37:12 +0200 Subject: [PATCH 03/65] Update package metadata --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e203ec3..d8fd94f 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ keywords='', author='Gauthier Bastien', author_email='devs@imio.be', - url='http://svn.communesplone.org/svn/communesplone/imio.pm.wsclient/', + url='https://github.com/IMIO/imio.pm.wsclient', license='GPL', packages=find_packages('src'), package_dir={'': 'src'}, From b4d98f37880994b7225a4bde60590e4654d2e81a Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 29 Jun 2022 16:43:47 +0200 Subject: [PATCH 04/65] WIP: Adapt methods to use REST instead of SOAP --- setup.py | 3 +- src/imio/pm/wsclient/browser/configure.zcml | 1 - src/imio/pm/wsclient/browser/forms.py | 12 +- src/imio/pm/wsclient/browser/settings.py | 153 ++++++++++--------- src/imio/pm/wsclient/browser/viewlets.py | 4 +- src/imio/pm/wsclient/browser/views.py | 6 +- src/imio/pm/wsclient/browser/vocabularies.py | 12 +- 7 files changed, 96 insertions(+), 95 deletions(-) diff --git a/setup.py b/setup.py index d8fd94f..888d421 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,7 @@ 'collective.z3cform.datagridfield', 'imio.pm.locales', 'plone.memoize', - # -*- Extra requirements: -*- - 'suds-jurko', + 'requests', ], extras_require={'test': ['plone.app.testing', 'imio.pm.ws', 'mock']}, entry_points=""" diff --git a/src/imio/pm/wsclient/browser/configure.zcml b/src/imio/pm/wsclient/browser/configure.zcml index 3af3ab6..2af6834 100644 --- a/src/imio/pm/wsclient/browser/configure.zcml +++ b/src/imio/pm/wsclient/browser/configure.zcml @@ -2,7 +2,6 @@ xmlns="http://namespaces.zope.org/zope" xmlns:five="http://namespaces.zope.org/five" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" - xmlns:soap="http://namespaces.zope.org/soap" xmlns:browser="http://namespaces.zope.org/browser" i18n_domain="imio.pm.wsclient"> diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index 6ac0124..dae570c 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -187,7 +187,7 @@ def update(self): # False means that is was not sent, so no connection test is made to PloneMeeting for performance reason if alreadySent is not None: # now connect to PloneMeeting - client = self.ws4pmSettings._soap_connectToPloneMeeting() + client = self.ws4pmSettings._rest_connectToPloneMeeting() if alreadySent is None or not client: IStatusMessage(self.request).addStatusMessage(_(UNABLE_TO_CONNECT_ERROR), "error") self._changeFormForErrors() @@ -195,7 +195,7 @@ def update(self): # do not go further if current user can not create an item in # PloneMeeting with any proposingGroup - userInfos = self.ws4pmSettings._soap_getUserInfos(showGroups=True, suffix='creators') + userInfos = self.ws4pmSettings._rest_getUserInfos(showGroups=True, suffix='creators') if not userInfos or 'groups' not in userInfos: userThatWillCreate = self.ws4pmSettings._getUserIdToUseInTheNameOfWith() if not userInfos: @@ -281,13 +281,13 @@ def _doSendToPloneMeeting(self): settings.only_one_sending: return False # build the creationData - client = self.ws4pmSettings._soap_connectToPloneMeeting() + client = self.ws4pmSettings._rest_connectToPloneMeeting() creation_data = self._getCreationData(client) notify(WillbeSendToPMEvent(self.context)) - # call the SOAP method actually creating the item - res = self.ws4pmSettings._soap_createItem(self.meetingConfigId, + # call the REST method actually creating the item + res = self.ws4pmSettings._rest_createItem(self.meetingConfigId, self.proposingGroupId, creation_data) if res: @@ -319,7 +319,7 @@ def _doSendToPloneMeeting(self): def _getCreationData(self, client): """ Build creationData dict that will be used to actually create - the item in PloneMeeting thru SOAP createItem call + the item in PloneMeeting thru REST createItem call """ data = self._buildDataDict() # now that every values are evaluated, build the CreationData diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 94a18e5..d99eef3 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -16,10 +16,6 @@ from Products.CMFCore.Expression import createExprContext from Products.CMFCore.Expression import Expression from Products.statusmessages.interfaces import IStatusMessage -from suds.client import Client -from suds.transport.http import HttpAuthenticated -from suds.xsd.doctor import Import -from suds.xsd.doctor import ImportDoctor from z3c.form import button from z3c.form import field from zope import schema @@ -31,6 +27,8 @@ from zope.interface import Interface from zope.schema.interfaces import IVocabularyFactory +import requests + class IGeneratedActionsSchema(Interface): """Schema used for the datagrid field 'generated_actions' of IWS4PMClientSettings.""" @@ -80,7 +78,7 @@ class IWS4PMClientSettings(Interface): Configuration of the WS4PM Client """ pm_url = schema.TextLine( - title=_(u"PloneMeeting WSDL URL"), + title=_(u"PloneMeeting URL"), required=True,) pm_timeout = schema.Int( title=_(u"PloneMeeting connection timeout"), @@ -175,7 +173,7 @@ def updateFields(self): # 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(): + if not ctrl._rest_getConfigInfos(): generated_actions_field.mode = 'display' field_mappings.mode = 'display' else: @@ -219,120 +217,125 @@ def settings(self): settings = registry.forInterface(IWS4PMClientSettings, check=False) return settings + @property + def url(self): + """Return PloneMeeting App URL""" + settings = self.settings() + return self.request.form.get('form.widgets.pm_url') or settings.pm_url or '' + @memoize - def _soap_connectToPloneMeeting(self): + def _rest_connectToPloneMeeting(self): """ Connect to distant PloneMeeting. - Either return None or the connected client. + Either return None or the session """ 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('') + session = requests.Session() + session.auth = (username, password) + response = requests.get(self.url, timeout=int(timeout)) + if response.status_code != 200: + raise ConnectionError 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 - return client + return session - def _soap_checkIsLinked(self, data): - """Query the checkIsLinked SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: - return client.service.checkIsLinked(**data) + def _rest_checkIsLinked(self, data): + """Query the checkIsLinked REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: + return session.service.checkIsLinked(**data) @memoize - def _soap_getConfigInfos(self, showCategories=False): - """Query the getConfigInfos SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: - return client.service.getConfigInfos(showCategories=showCategories) + def _rest_getConfigInfos(self, showCategories=False): + """Query the getConfigInfos REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: + return session.get("{0}/@config") + return session.service.getConfigInfos(showCategories=showCategories) @memoize - def _soap_getUserInfos(self, showGroups=False, suffix=''): - """Query the getUserInfos SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_getUserInfos(self, showGroups=False, suffix=''): + """Query the getUserInfos REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: # get the inTheNameOf userid if it was not already set userId = self._getUserIdToUseInTheNameOfWith(mandatory=True) try: - return client.service.getUserInfos(userId, showGroups, suffix) + return session.service.getUserInfos(userId, showGroups, suffix) except Exception: return None - def _soap_searchItems(self, data): - """Query the searchItems SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_searchItems(self, data): + """Query the searchItems REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: # get the inTheNameOf userid if it was not already set if 'inTheNameOf' not in data: data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() - return client.service.searchItems(**data) + return session.service.searchItems(**data) - def _soap_getItemInfos(self, data): - """Query the getItemInfos SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_getItemInfos(self, data): + """Query the getItemInfos REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: # get the inTheNameOf userid if it was not already set if 'inTheNameOf' not in data: data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() - return client.service.getItemInfos(**data) + return session.service.getItemInfos(**data) - def _soap_getMeetingsAcceptingItems(self, data): - """Query the getItemInfos SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_getMeetingsAcceptingItems(self, data): + """Query the getItemInfos REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: if 'inTheNameOf' not in data: data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() - return client.service.meetingsAcceptingItems(**data) + return session.service.meetingsAcceptingItems(**data) - def _soap_getItemTemplate(self, data): - """Query the getItemTemplate SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_getItemTemplate(self, data): + """Query the getItemTemplate REST server method.""" + session = self._rest_connectToPloneMeeting() + if session is not None: if 'inTheNameOf' not in data: data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() try: - return client.service.getItemTemplate(**data) + return session.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") @memoize - def _soap_getItemCreationAvailableData(self): - """Query SOAP WSDL to obtain the list of available fields useable while creating an item.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_getItemCreationAvailableData(self): + """Query REST WSDL to obtain the list of available fields useable while creating an item.""" + session = self._rest_connectToPloneMeeting() + if session is not None: # extract data from the CreationData ComplexType that is used to create an item - namespace = str(client.wsdl.tns[1]) + namespace = str(session.wsdl.tns[1]) res = ['proposingGroup'] res += [str(data.name) for data in - client.factory.wsdl.build_schema().types['CreationData', namespace].rawchildren[0].rawchildren] + session.factory.wsdl.build_schema().types['CreationData', namespace].rawchildren[0].rawchildren] return res - def _soap_createItem(self, meetingConfigId, proposingGroupId, creationData): - """Query the createItem SOAP server method.""" - client = self._soap_connectToPloneMeeting() - if client is not None: + def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): + """Query the createItem REST server method.""" + session = self._rest_connectToPloneMeeting() + if session 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) + res = session.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'): @@ -353,13 +356,13 @@ def _getUserIdToUseInTheNameOfWith(self, mandatory=False): """ member = self.context.portal_membership.getAuthenticatedMember() memberId = member.getId() - # get username specified to connect to the SOAP distant site + # get username specified to connect to the REST distant site settings = self.settings() - soapUsername = settings.pm_username and settings.pm_username.strip() + restUsername = settings.pm_username and settings.pm_username.strip() # if current user is the user defined in the settings, return None - if memberId == soapUsername: + if memberId == restUsername: if mandatory: - return soapUsername + return restUsername else: return None # check if a user_mapping exists @@ -367,13 +370,13 @@ def _getUserIdToUseInTheNameOfWith(self, mandatory=False): for user_mapping in settings.user_mappings: localUserId, distantUserId = user_mapping['local_userid'], user_mapping['pm_userid'] # if we found a mapping for the current user, check also - # that the distantUserId the mapping is linking to, is not the soapUsername + # that the distantUserId the mapping is linking to, is not the restUsername if memberId == localUserId.strip(): - if not soapUsername == distantUserId.strip(): + if not restUsername == distantUserId.strip(): return distantUserId.strip() else: if mandatory: - return soapUsername + return restUsername else: return None return memberId @@ -390,7 +393,7 @@ def checkAlreadySentToPloneMeeting(self, context, meetingConfigIds=[]): This script also wipe out every meetingConfigIds for wich the item does not exist anymore in PloneMeeting """ annotations = IAnnotations(context) - # for performance reason (avoid to connect to SOAP if no annotations) + # for performance reason (avoid to connect to REST if no annotations) # if there are no relevant annotations, it means that the p_context # is not linked and we return False isLinked = False @@ -404,7 +407,7 @@ def checkAlreadySentToPloneMeeting(self, context, meetingConfigIds=[]): # this will wipe out the entire annotation meetingConfigIds = list(annotations[WS4PMCLIENT_ANNOTATION_KEY]) for meetingConfigId in meetingConfigIds: - res = self._soap_checkIsLinked({'externalIdentifier': context.UID(), + res = self._rest_checkIsLinked({'externalIdentifier': context.UID(), 'meetingConfigId': meetingConfigId, }) # if res is None, it means that it could not connect to PloneMeeting if res is None: @@ -441,7 +444,7 @@ def renderTALExpression(self, context, portal, expression, vars={}): for k, v in vars.items(): ctx.setContext(k, v) res = Expression(expression)(ctx) - # make sure we do not return None because it breaks SOAP call + # make sure we do not return None because it breaks REST call if res is None: return u'' else: diff --git a/src/imio/pm/wsclient/browser/viewlets.py b/src/imio/pm/wsclient/browser/viewlets.py index df3d5d3..22ba679 100644 --- a/src/imio/pm/wsclient/browser/viewlets.py +++ b/src/imio/pm/wsclient/browser/viewlets.py @@ -75,7 +75,7 @@ def getPloneMeetingLinkedInfos(self): 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()}) + items = self.ws4pmSettings._rest_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') @@ -94,7 +94,7 @@ def getPloneMeetingLinkedInfos(self): 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'], + res.append(self.ws4pmSettings._rest_getItemInfos({'UID': item['UID'], 'showExtraInfos': True, 'showAnnexes': True, 'allowed_annexes_types': allowed_annexes_types, diff --git a/src/imio/pm/wsclient/browser/views.py b/src/imio/pm/wsclient/browser/views.py index 2158d39..39caf51 100644 --- a/src/imio/pm/wsclient/browser/views.py +++ b/src/imio/pm/wsclient/browser/views.py @@ -30,7 +30,7 @@ def __init__(self, context, request): def __call__(self): """ """ # first check that we can connect to PloneMeeting - client = self.ws4pmSettings._soap_connectToPloneMeeting() + client = self.ws4pmSettings._rest_connectToPloneMeeting() if not client: IStatusMessage(self.request).addStatusMessage(_(UNABLE_TO_CONNECT_ERROR), "error") return self.request.RESPONSE.redirect(self.context.absolute_url()) @@ -68,7 +68,7 @@ def __call__(self): response.setHeader('Content-Disposition', 'inline;filename="%s.%s"' % (self.templateFilename, self.templateFormat)) - res = self.ws4pmSettings._soap_getItemTemplate({'itemUID': self.itemUID, + res = self.ws4pmSettings._rest_getItemTemplate({'itemUID': self.itemUID, 'templateId': self.templateId, }) if not res: # an error occured, redirect to user to the context, a statusMessage will be displayed @@ -100,7 +100,7 @@ def __call__(self): IStatusMessage(self.request).addStatusMessage(_(ANNEXID_MANDATORY_ERROR), "error") return response.redirect(self.context.absolute_url()) - res = self.ws4pmSettings._soap_getItemInfos( + res = self.ws4pmSettings._rest_getItemInfos( { 'UID': self.itemUID, 'showAnnexes': True, diff --git a/src/imio/pm/wsclient/browser/vocabularies.py b/src/imio/pm/wsclient/browser/vocabularies.py index 1db8ab5..035d910 100644 --- a/src/imio/pm/wsclient/browser/vocabularies.py +++ b/src/imio/pm/wsclient/browser/vocabularies.py @@ -36,7 +36,7 @@ def __call__(self, context=None): if not portal.__module__ == 'Products.CMFPlone.Portal': portal = portal.aq_inner.aq_parent settings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') - pmConfigInfos = settings._soap_getConfigInfos() + pmConfigInfos = settings._rest_getConfigInfos() terms = [] if pmConfigInfos: for pmConfigInfo in pmConfigInfos.configInfo: @@ -80,7 +80,7 @@ def __call__(self, context): if not portal.__module__ == 'Products.CMFPlone.Portal': portal = portal.aq_inner.aq_parent settings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') - availableDatas = settings._soap_getItemCreationAvailableData() + availableDatas = settings._rest_getItemCreationAvailableData() if availableDatas: for availableData in availableDatas: terms.append(SimpleTerm(unicode(availableData), @@ -130,7 +130,7 @@ def __call__(self, context): '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') + userInfos = ws4pmsettings._rest_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 @@ -204,7 +204,7 @@ def __call__(self, context): 'error') return SimpleVocabulary([]) - configInfos = ws4pmsettings._soap_getConfigInfos(showCategories=True) + configInfos = ws4pmsettings._rest_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 @@ -263,7 +263,7 @@ def __call__(self, context): 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) + configInfos = ws4pmsettings._rest_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 @@ -274,7 +274,7 @@ def __call__(self, context): 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) + possible_meetings = ws4pmsettings._rest_getMeetingsAcceptingItems(data) local = pytz.timezone("Europe/Brussels") for meeting in possible_meetings: meeting['date'] = meeting['date'].astimezone(local) From 169d8dc660335fc0b5671e2945c64bdf5c64260d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 29 Jun 2022 16:44:57 +0200 Subject: [PATCH 05/65] Fix syntax --- src/imio/pm/wsclient/browser/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index d99eef3..188a148 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -239,7 +239,7 @@ def _rest_connectToPloneMeeting(self): response = requests.get(self.url, timeout=int(timeout)) if response.status_code != 200: raise ConnectionError - except Exception, e: + except Exception as e: # if we are really on the configuration panel, display relevant message if self.request.get('URL', '').endswith('@@ws4pmclient-settings'): IStatusMessage(self.request).addStatusMessage( @@ -307,7 +307,7 @@ def _rest_getItemTemplate(self, data): data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() try: return session.service.getItemTemplate(**data) - except Exception, exc: + except Exception as exc: IStatusMessage(self.request).addStatusMessage( _(u"An error occured while generating the document in PloneMeeting! " "The error message was : %s" % exc), "error") @@ -341,7 +341,7 @@ def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): 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: + except Exception as exc: IStatusMessage(self.request).addStatusMessage(_(CONFIG_CREATE_ITEM_PM_ERROR, mapping={'error': exc}), "error") From 96ac156b3be4297eff15d1f7f53e85cef0286299 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 29 Jun 2022 16:45:09 +0200 Subject: [PATCH 06/65] Comment problems that may occur for REST migration --- src/imio/pm/wsclient/browser/settings.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 188a148..26bafec 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -249,6 +249,7 @@ def _rest_connectToPloneMeeting(self): def _rest_checkIsLinked(self, data): """Query the checkIsLinked REST server method.""" + # XXX To be implemented in plonemeeting.restapi session = self._rest_connectToPloneMeeting() if session is not None: return session.service.checkIsLinked(**data) @@ -258,12 +259,15 @@ def _rest_getConfigInfos(self, showCategories=False): """Query the getConfigInfos REST server method.""" session = self._rest_connectToPloneMeeting() if session is not None: + # XXX Implement @configs endpoint / extend @infos endpoint ? + # XXX showCategories need to be reimplemented in endpoint ? return session.get("{0}/@config") return session.service.getConfigInfos(showCategories=showCategories) @memoize def _rest_getUserInfos(self, showGroups=False, suffix=''): """Query the getUserInfos REST server method.""" + # XXX Use @users endpoint (suffix may need to be reimplemented) session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -275,6 +279,7 @@ def _rest_getUserInfos(self, showGroups=False, suffix=''): def _rest_searchItems(self, data): """Query the searchItems REST server method.""" + # XXX Use @search endpoint and `in_name_of` parameter session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -284,6 +289,8 @@ def _rest_searchItems(self, data): def _rest_getItemInfos(self, data): """Query the getItemInfos REST server method.""" + # XXX Use @get endpoint but handle `in_name_of` parameter and ensure that all + # required attributes are returned session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -293,6 +300,7 @@ def _rest_getItemInfos(self, data): def _rest_getMeetingsAcceptingItems(self, data): """Query the getItemInfos REST server method.""" + # XXX use @meeting endpoint ? does this endpoint return the accepting items ? session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -301,6 +309,7 @@ def _rest_getMeetingsAcceptingItems(self, data): def _rest_getItemTemplate(self, data): """Query the getItemTemplate REST server method.""" + # XXX new endpoint ? Extending @get endpoint ? session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -315,6 +324,7 @@ def _rest_getItemTemplate(self, data): @memoize def _rest_getItemCreationAvailableData(self): """Query REST WSDL to obtain the list of available fields useable while creating an item.""" + # XXX new endpoint ? Extending @meeting endpoint ? session = self._rest_connectToPloneMeeting() if session is not None: # extract data from the CreationData ComplexType that is used to create an item From 803d4dbdbed333eb27a34e8a51351c953648b7e2 Mon Sep 17 00:00:00 2001 From: Gauthier Bastien Date: Fri, 1 Jul 2022 10:12:58 +0200 Subject: [PATCH 07/65] Completed comments to know what to use in plonemeeting.restapi available endpoints --- src/imio/pm/wsclient/browser/settings.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 26bafec..f05d495 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -250,6 +250,7 @@ def _rest_connectToPloneMeeting(self): def _rest_checkIsLinked(self, data): """Query the checkIsLinked REST server method.""" # XXX To be implemented in plonemeeting.restapi + # this will disappear and is replaced by a direct call to @item GET session = self._rest_connectToPloneMeeting() if session is not None: return session.service.checkIsLinked(**data) @@ -261,6 +262,7 @@ def _rest_getConfigInfos(self, showCategories=False): if session is not None: # XXX Implement @configs endpoint / extend @infos endpoint ? # XXX showCategories need to be reimplemented in endpoint ? + # this could be replace with @config?extra_include=categories return session.get("{0}/@config") return session.service.getConfigInfos(showCategories=showCategories) @@ -268,6 +270,7 @@ def _rest_getConfigInfos(self, showCategories=False): def _rest_getUserInfos(self, showGroups=False, suffix=''): """Query the getUserInfos REST server method.""" # XXX Use @users endpoint (suffix may need to be reimplemented) + # use @users?extra_include=groups&extra_include_groups_suffixes=creators session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -280,6 +283,7 @@ def _rest_getUserInfos(self, showGroups=False, suffix=''): def _rest_searchItems(self, data): """Query the searchItems REST server method.""" # XXX Use @search endpoint and `in_name_of` parameter + # use @search?config_id=meeting-config-college&in_name_of=username&... session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -291,6 +295,7 @@ def _rest_getItemInfos(self, data): """Query the getItemInfos REST server method.""" # XXX Use @get endpoint but handle `in_name_of` parameter and ensure that all # required attributes are returned + # use @get (that is overrided) ?in_name_of=username&uid=a_uid session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -301,6 +306,7 @@ def _rest_getItemInfos(self, data): def _rest_getMeetingsAcceptingItems(self, data): """Query the getItemInfos REST server method.""" # XXX use @meeting endpoint ? does this endpoint return the accepting items ? + # @search?config_id=meeting-config-college&meetings_accepting_items=true session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -310,6 +316,7 @@ def _rest_getMeetingsAcceptingItems(self, data): def _rest_getItemTemplate(self, data): """Query the getItemTemplate REST server method.""" # XXX new endpoint ? Extending @get endpoint ? + # @get?uid=item_uid&extra_include=pod_templates session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -325,6 +332,7 @@ def _rest_getItemTemplate(self, data): def _rest_getItemCreationAvailableData(self): """Query REST WSDL to obtain the list of available fields useable while creating an item.""" # XXX new endpoint ? Extending @meeting endpoint ? + # this could be @config?config_id=meeting-config-college&metadata_fields=usedItemAttributes session = self._rest_connectToPloneMeeting() if session is not None: # extract data from the CreationData ComplexType that is used to create an item @@ -337,6 +345,7 @@ def _rest_getItemCreationAvailableData(self): def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): """Query the createItem REST server method.""" session = self._rest_connectToPloneMeeting() + # use @item POST query if session is not None: try: # we create an item inTheNameOf the currently connected member From 6564654ae4176f43220d923868551525d6ba2d5b Mon Sep 17 00:00:00 2001 From: sdelcourt Date: Tue, 5 Jul 2022 13:58:12 +0200 Subject: [PATCH 08/65] Use plonemeeting.restapi base test case for wsclient tests. --- src/imio/pm/wsclient/testing.py | 2 -- src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/imio/pm/wsclient/testing.py b/src/imio/pm/wsclient/testing.py index 1ebd74a..5997306 100644 --- a/src/imio/pm/wsclient/testing.py +++ b/src/imio/pm/wsclient/testing.py @@ -25,7 +25,6 @@ class WSCLIENTLayer(PMLayer): zcml_package=imio.pm.wsclient, additional_z2_products=('imio.dashboard', 'imio.pm.wsclient', - 'imio.pm.ws', 'Products.PasswordStrength'), gs_profile_id='imio.pm.wsclient:testing', name="WS4PMCLIENT") @@ -38,7 +37,6 @@ class WSCLIENTLayer(PMLayer): 'Products.PloneMeeting', 'Products.CMFPlacefulWorkflow', 'imio.pm.wsclient', - 'imio.pm.ws', 'Products.PasswordStrength'), gs_profile_id='Products.PloneMeeting:testing', name="WS4PMCLIENT_PM_TESTING_PROFILE") diff --git a/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py b/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py index 8e07d93..9f5aec8 100644 --- a/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py +++ b/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py @@ -21,9 +21,9 @@ # from Acquisition import aq_base +from imio.pm.ws.tests.WS4PMTestCase import WS4PMTestCase from imio.pm.wsclient.testing import WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL from Products.PloneMeeting.config import DEFAULT_USER_PASSWORD -from Products.PloneMeeting.tests.PloneMeetingTestCase import PloneMeetingTestCase from zope.annotation.interfaces import IAnnotations from zope.component import getMultiAdapter @@ -33,7 +33,7 @@ SEND_TO_PM_VIEW_NAME = '@@send_to_plonemeeting_form' -class WS4PMCLIENTTestCase(PloneMeetingTestCase): +class WS4PMCLIENTTestCase(WS4PMTestCase): '''Base class for defining WS4PMCLIENT test cases.''' # define PM base TestCase test file that we will not launch from here @@ -48,7 +48,7 @@ class WS4PMCLIENTTestCase(PloneMeetingTestCase): def setUp(self): """ """ - PloneMeetingTestCase.setUp(self) + WS4PMTestCase.setUp(self) def _sendToPloneMeeting(self, obj, user='pmCreator1', proposingGroup='developers', meetingConfigId='plonemeeting-assembly', category=''): @@ -96,7 +96,7 @@ def _validate(self, value): ws4pmSettings = getMultiAdapter((portal, portal.REQUEST), name='ws4pmclient-settings') settings = ws4pmSettings.settings() if setConnectionParams: - settings.pm_url = kwargs.get('pm_url', None) or u'%s/ws4pm.wsdl' % portal.absolute_url() + settings.pm_url = kwargs.get('pm_url', None) or portal.absolute_url().decode('utf-8') settings.pm_username = kwargs.get('pm_username', None) or u'pmManager' settings.pm_password = kwargs.get('pm_password', None) or DEFAULT_USER_PASSWORD settings.user_mappings = kwargs.get('user_mappings', None) or \ From f0b7a5a509063e807fa4c9ee5f36d97c8a033dec Mon Sep 17 00:00:00 2001 From: sdelcourt Date: Tue, 5 Jul 2022 14:01:21 +0200 Subject: [PATCH 09/65] Rewrite rest version of _rest_connectToPloneMeeting. --- src/imio/pm/wsclient/browser/settings.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 26bafec..dcba205 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -16,6 +16,7 @@ from Products.CMFCore.Expression import createExprContext from Products.CMFCore.Expression import Expression from Products.statusmessages.interfaces import IStatusMessage +from StringIO import StringIO from z3c.form import button from z3c.form import field from zope import schema @@ -27,6 +28,7 @@ from zope.interface import Interface from zope.schema.interfaces import IVocabularyFactory +import json import requests @@ -234,11 +236,14 @@ def _rest_connectToPloneMeeting(self): 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 '' try: + infos_url = "{}/@infos".format(self.url) session = requests.Session() session.auth = (username, password) - response = requests.get(self.url, timeout=int(timeout)) - if response.status_code != 200: - raise ConnectionError + session.headers.update({'Accept': 'application/json', 'Content-Type': 'application/json'}) + login = session.get(infos_url, timeout=int(timeout)) + if login.status_code != 200: + response = json.load(StringIO(login.content)) + raise ConnectionError(response['error']['message']) except Exception as e: # if we are really on the configuration panel, display relevant message if self.request.get('URL', '').endswith('@@ws4pmclient-settings'): From d6a659fdc8148f4eddeebdfaddfbd8bff95df923 Mon Sep 17 00:00:00 2001 From: sdelcourt Date: Tue, 5 Jul 2022 14:02:24 +0200 Subject: [PATCH 10/65] Include plonemeeting.restapi rather than imio.pm.ws. --- src/imio/pm/wsclient/testing.zcml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/testing.zcml b/src/imio/pm/wsclient/testing.zcml index 8a8cfc7..39069df 100644 --- a/src/imio/pm/wsclient/testing.zcml +++ b/src/imio/pm/wsclient/testing.zcml @@ -3,7 +3,7 @@ i18n_domain="imio.pm.wsclient"> - + Date: Tue, 5 Jul 2022 14:03:02 +0200 Subject: [PATCH 11/65] Rename test _soap_ test methods by _rest_ . --- src/imio/pm/wsclient/tests/testSOAPMethods.py | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index 2d2977e..8d05b36 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -18,62 +18,62 @@ class testSOAPMethods(WS4PMCLIENTTestCase): Tests the browser.settings SOAP client methods """ - def test_soap_connectToPloneMeeting(self): + def test_rest_connectToPloneMeeting(self): """Check that we can actually connect to PloneMeeting with given parameters.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') settings = ws4pmSettings.settings() setCorrectSettingsConfig(self.portal, minimal=True) # with valid informations, we can connect to PloneMeeting SOAP webservices - self.failUnless(ws4pmSettings._soap_connectToPloneMeeting()) + self.failUnless(ws4pmSettings._rest_connectToPloneMeeting()) # if either url or username/password is not valid, we can not connect... valid_url = settings.pm_url settings.pm_url = settings.pm_url + 'invalidEndOfURL' cleanMemoize(self.request) # with invalid url, it fails... - self.failIf(ws4pmSettings._soap_connectToPloneMeeting()) + self.failIf(ws4pmSettings._rest_connectToPloneMeeting()) settings.pm_url = valid_url # with valid url but wrong password, it fails... settings.pm_password = u'wrongPassword' cleanMemoize(self.request) - self.failIf(ws4pmSettings._soap_connectToPloneMeeting()) + self.failIf(ws4pmSettings._rest_connectToPloneMeeting()) - def test_soap_getConfigInfos(self): + def test_rest_getConfigInfos(self): """Check that we receive valid infos about the PloneMeeting's configuration.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) - configInfos = ws4pmSettings._soap_getConfigInfos() + configInfos = ws4pmSettings._rest_getConfigInfos() # check thatt we received elements like MeetingConfig and MeetingGroups self.assertTrue(configInfos.configInfo) self.assertTrue(configInfos.groupInfo) # by default, no categories self.assertFalse(hasattr(configInfos.configInfo[0], 'categories')) - # we can ask categories by passing a showCategories=True to _soap_getConfigInfos - configInfos = ws4pmSettings._soap_getConfigInfos(showCategories=True) + # we can ask categories by passing a showCategories=True to _rest_getConfigInfos + configInfos = ws4pmSettings._rest_getConfigInfos(showCategories=True) self.assertTrue(hasattr(configInfos.configInfo[1], 'categories')) - def test_soap_getItemCreationAvailableData(self): + def test_rest_getItemCreationAvailableData(self): """Check that we receive the list of available data for creating an item.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) - availableData = ws4pmSettings._soap_getItemCreationAvailableData() + availableData = ws4pmSettings._rest_getItemCreationAvailableData() availableData.sort() self.assertEqual(availableData, ['annexes', - 'associatedGroups', - 'category', - 'decision', - 'description', - 'detailedDescription', - 'externalIdentifier', - 'extraAttrs', - 'groupsInCharge', - 'motivation', - 'optionalAdvisers', - 'preferredMeeting', - 'proposingGroup', - 'title', - 'toDiscuss']) + 'associatedGroups', + 'category', + 'decision', + 'description', + 'detailedDescription', + 'externalIdentifier', + 'extraAttrs', + 'groupsInCharge', + 'motivation', + 'optionalAdvisers', + 'preferredMeeting', + 'proposingGroup', + 'title', + 'toDiscuss']) - def test_soap_getItemInfos(self): + def test_rest_getItemInfos(self): """Check the fact of getting informations about an existing item.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) @@ -83,16 +83,16 @@ def test_soap_getItemInfos(self): # we have to commit() here or portal used behing the SOAP call # does not have the freshly created item... transaction.commit() - self.assertTrue(len(ws4pmSettings._soap_getItemInfos({'UID': item.UID()})) == 1) + self.assertTrue(len(ws4pmSettings._rest_getItemInfos({'UID': item.UID()})) == 1) # getItemInfos is called inTheNameOf the currently connected user # if the user (like 'pmCreator1') can see the item, he gets it in the request # either (like for 'pmCreator2') the item is not found self.changeUser('pmCreator1') - self.assertTrue(len(ws4pmSettings._soap_getItemInfos({'UID': item.UID()})) == 1) + self.assertTrue(len(ws4pmSettings._rest_getItemInfos({'UID': item.UID()})) == 1) self.changeUser('pmCreator2') - self.assertTrue(len(ws4pmSettings._soap_getItemInfos({'UID': item.UID()})) == 0) + self.assertTrue(len(ws4pmSettings._rest_getItemInfos({'UID': item.UID()})) == 0) - def test_soap_searchItems(self): + def test_rest_searchItems(self): """Check the fact of searching items informations about existing items.""" SAME_TITLE = 'sameTitleForBothItems' ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') @@ -112,15 +112,15 @@ def test_soap_searchItems(self): transaction.commit() # searchItems will automatically restrict searches to the connected user self.changeUser('pmCreator1') - result = ws4pmSettings._soap_searchItems({'Title': SAME_TITLE}) + result = ws4pmSettings._rest_searchItems({'Title': SAME_TITLE}) self.assertTrue(len(result), 1) self.assertTrue(result[0].UID == item1.UID()) self.changeUser('pmCreator2') - result = ws4pmSettings._soap_searchItems({'Title': SAME_TITLE}) + result = ws4pmSettings._rest_searchItems({'Title': SAME_TITLE}) self.assertTrue(len(result), 1) self.assertTrue(result[0].UID == item2.UID()) - def test_soap_createItem(self): + def test_rest_createItem(self): """Check item creation. Item creation will automatically use currently connected user to create the item regarding the _getUserIdToUseInTheNameOfWith.""" @@ -148,7 +148,7 @@ def test_soap_createItem(self): 'externalIdentifier': u'my-external-identifier', 'extraAttrs': [{'key': 'internalNotes', 'value': '

Internal notes

'}]} - result = ws4pmSettings._soap_createItem(cfg2Id, 'developers', data) + result = ws4pmSettings._rest_createItem(cfg2Id, 'developers', data) # commit again so the item is really created transaction.commit() # the item is created and his UID is returned @@ -170,16 +170,16 @@ def test_soap_createItem(self): # if we try to create with wrong data, the SOAP ws returns a response # that is displayed to the user creating the item data['category'] = 'unexisting-category-id' - result = ws4pmSettings._soap_createItem('plonegov-assembly', 'developers', data) + result = ws4pmSettings._rest_createItem('plonegov-assembly', 'developers', data) self.assertIsNone(result) messages = IStatusMessage(self.request) # a message is displayed self.assertEqual(messages.show()[-1].message, - u"An error occured during the item creation in PloneMeeting! " - "The error message was : Server raised fault: ''unexisting-category-id' " - "is not available for the 'developers' group!'") + u"An error occured during the item creation in PloneMeeting! " + "The error message was : Server raised fault: ''unexisting-category-id' " + "is not available for the 'developers' group!'") - def test_soap_getItemTemplate(self): + def test_rest_getItemTemplate(self): """Check while getting rendered template for an item. getItemTemplate will automatically use currently connected user to render item template regarding the _getUserIdToUseInTheNameOfWith.""" @@ -191,29 +191,29 @@ def test_soap_getItemTemplate(self): # we have to commit() here or portal used behing the SOAP call # does not have the freshly created item... transaction.commit() - self.assertTrue(ws4pmSettings._soap_getItemTemplate( + self.assertTrue(ws4pmSettings._rest_getItemTemplate( {'itemUID': item.UID(), 'templateId': POD_TEMPLATE_ID_PATTERN.format('itemTemplate', 'odt')})) # getItemTemplate is called inTheNameOf the currently connected user # if the user (like 'pmCreator1') can see the item, he gets the rendered template # either (like for 'pmCreator2') nothing is returned self.changeUser('pmCreator1') - self.assertTrue(ws4pmSettings._soap_getItemTemplate( + self.assertTrue(ws4pmSettings._rest_getItemTemplate( {'itemUID': item.UID(), 'templateId': POD_TEMPLATE_ID_PATTERN.format('itemTemplate', 'odt')})) self.changeUser('pmCreator2') - self.assertFalse(ws4pmSettings._soap_getItemTemplate( + self.assertFalse(ws4pmSettings._rest_getItemTemplate( {'itemUID': item.UID(), 'templateId': POD_TEMPLATE_ID_PATTERN.format('itemTemplate', 'odt')})) - def test_soap_getMeetingAcceptingItems(self): + def test_rest_getMeetingAcceptingItems(self): """Check getting accepting items meeting. Should only return meetings in the state 'creation' and 'frozen'""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) cfg = self.meetingConfig cfgId = cfg.getId() - meetings = ws4pmSettings._soap_getMeetingsAcceptingItems( + meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId, 'inTheNameOf': 'pmCreator1'} ) self.assertEqual(meetings, []) @@ -222,7 +222,7 @@ def test_soap_getMeetingAcceptingItems(self): meeting_2 = self.create('Meeting', date=datetime(2013, 3, 3)) transaction.commit() self.changeUser('pmCreator1') - meetings = ws4pmSettings._soap_getMeetingsAcceptingItems( + meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId, 'inTheNameOf': 'pmCreator1'} ) # so far find the two meetings @@ -233,7 +233,7 @@ def test_soap_getMeetingAcceptingItems(self): api.content.transition(meeting_2, 'freeze') transaction.commit() self.changeUser('pmCreator1') - meetings = ws4pmSettings._soap_getMeetingsAcceptingItems( + meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId, 'inTheNameOf': 'pmCreator1'} ) self.assertEqual(len(meetings), 2) @@ -244,21 +244,21 @@ def test_soap_getMeetingAcceptingItems(self): transaction.commit() # after publishing meeting_2, it should not be in the results anymore. self.changeUser('pmCreator1') - meetings = ws4pmSettings._soap_getMeetingsAcceptingItems( + meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId, 'inTheNameOf': 'pmCreator1'} ) self.assertEqual(len(meetings), 1) self.assertEqual(meetings[0].UID, meeting_1.UID()) - # if no inTheNameOf param is explicitly passed, _soap_getMeetingsAcceptingItems() + # if no inTheNameOf param is explicitly passed, _rest_getMeetingsAcceptingItems() # should set a default one. - meetings = ws4pmSettings._soap_getMeetingsAcceptingItems( + meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId} ) self.assertEqual(len(meetings), 1) self.assertEqual(meetings[0].UID, meeting_1.UID()) # As pmManager, we should get all the meetings - meetings = ws4pmSettings._soap_getMeetingsAcceptingItems( + meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId, 'inTheNameOf': 'pmManager'} ) self.assertEqual(len(meetings), 2) From 5578dad2206931c5f5dfe5509ac4eaf3882d52ef Mon Sep 17 00:00:00 2001 From: sdelcourt Date: Wed, 6 Jul 2022 10:53:53 +0200 Subject: [PATCH 12/65] Test vocabularies to complete coverage. --- .../pm/wsclient/tests/testVocabularies.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/imio/pm/wsclient/tests/testVocabularies.py diff --git a/src/imio/pm/wsclient/tests/testVocabularies.py b/src/imio/pm/wsclient/tests/testVocabularies.py new file mode 100644 index 0000000..769189b --- /dev/null +++ b/src/imio/pm/wsclient/tests/testVocabularies.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from imio.pm.wsclient.browser import vocabularies +from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import WS4PMCLIENTTestCase + +from mock import patch + + +class testVocabularies(WS4PMCLIENTTestCase): + """ + Test the vocabularies. + """ + + @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getConfigInfos') + def test_pm_meeting_config_id_vocabulary(self, _rest_getConfigInfos): + """ """ + _rest_getConfigInfos.return_value = type( + 'ConfigInfos', (object,), { + 'configInfo': [ + {'id': u'plonegov-assembly', 'title': u'PloneGov Assembly'}, + {'id': u'plonemeeting-assembly', 'title': u'PloneMeeting Assembly'}, + ] + } + )() + raw_voc = vocabularies.pm_meeting_config_id_vocabularyFactory() + self.assertEqual(len(raw_voc), 2) + voc = [v for v in raw_voc] + self.assertEqual(voc[0].value, 'plonegov-assembly') + self.assertEqual(voc[1].value, 'plonemeeting-assembly') From ed8bc704962e59dd1effc365d751f57df47bba0a Mon Sep 17 00:00:00 2001 From: sdelcourt Date: Wed, 6 Jul 2022 10:54:53 +0200 Subject: [PATCH 13/65] Reimplements _rest_getConfigInfos . --- src/imio/pm/wsclient/browser/settings.py | 35 ++++++++++--------- src/imio/pm/wsclient/tests/testSOAPMethods.py | 11 +++--- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 3bda0ee..9aa896a 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -16,7 +16,6 @@ from Products.CMFCore.Expression import createExprContext from Products.CMFCore.Expression import Expression from Products.statusmessages.interfaces import IStatusMessage -from StringIO import StringIO from z3c.form import button from z3c.form import field from zope import schema @@ -28,7 +27,6 @@ from zope.interface import Interface from zope.schema.interfaces import IVocabularyFactory -import json import requests @@ -242,7 +240,7 @@ def _rest_connectToPloneMeeting(self): session.headers.update({'Accept': 'application/json', 'Content-Type': 'application/json'}) login = session.get(infos_url, timeout=int(timeout)) if login.status_code != 200: - response = json.load(StringIO(login.content)) + response = login.json() raise ConnectionError(response['error']['message']) except Exception as e: # if we are really on the configuration panel, display relevant message @@ -255,7 +253,6 @@ def _rest_connectToPloneMeeting(self): def _rest_checkIsLinked(self, data): """Query the checkIsLinked REST server method.""" # XXX To be implemented in plonemeeting.restapi - # this will disappear and is replaced by a direct call to @item GET session = self._rest_connectToPloneMeeting() if session is not None: return session.service.checkIsLinked(**data) @@ -265,17 +262,29 @@ def _rest_getConfigInfos(self, showCategories=False): """Query the getConfigInfos REST server method.""" session = self._rest_connectToPloneMeeting() if session is not None: - # XXX Implement @configs endpoint / extend @infos endpoint ? - # XXX showCategories need to be reimplemented in endpoint ? - # this could be replace with @config?extra_include=categories - return session.get("{0}/@config") - return session.service.getConfigInfos(showCategories=showCategories) + # XXX to reimplements once @configs endpoint is implemented in plonemeeting.restapi + config_url = "{}/@users/{}?extra_include=configs".format(self.url, session.auth[0]) + user_infos = session.get(config_url) + if user_infos.status_code == 200: + configs_info = user_infos.json()['extra_include_configs'] + if showCategories: + config_url = '{}&extra_include=categories'.format(config_url) + for config_info in configs_info: + config_url = '{}&extra_include_categories_configs={}'.format( + config_url, + config_info['id'] + ) + user_infos = session.get(config_url) + content = user_infos.json() + configs_info = content['extra_include_configs'] + for config_info in configs_info: + config_info['categories'] = content['extra_include_categories'][config_info['id']] + return configs_info @memoize def _rest_getUserInfos(self, showGroups=False, suffix=''): """Query the getUserInfos REST server method.""" # XXX Use @users endpoint (suffix may need to be reimplemented) - # use @users?extra_include=groups&extra_include_groups_suffixes=creators session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -288,7 +297,6 @@ def _rest_getUserInfos(self, showGroups=False, suffix=''): def _rest_searchItems(self, data): """Query the searchItems REST server method.""" # XXX Use @search endpoint and `in_name_of` parameter - # use @search?config_id=meeting-config-college&in_name_of=username&... session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -300,7 +308,6 @@ def _rest_getItemInfos(self, data): """Query the getItemInfos REST server method.""" # XXX Use @get endpoint but handle `in_name_of` parameter and ensure that all # required attributes are returned - # use @get (that is overrided) ?in_name_of=username&uid=a_uid session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -311,7 +318,6 @@ def _rest_getItemInfos(self, data): def _rest_getMeetingsAcceptingItems(self, data): """Query the getItemInfos REST server method.""" # XXX use @meeting endpoint ? does this endpoint return the accepting items ? - # @search?config_id=meeting-config-college&meetings_accepting_items=true session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -321,7 +327,6 @@ def _rest_getMeetingsAcceptingItems(self, data): def _rest_getItemTemplate(self, data): """Query the getItemTemplate REST server method.""" # XXX new endpoint ? Extending @get endpoint ? - # @get?uid=item_uid&extra_include=pod_templates session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -337,7 +342,6 @@ def _rest_getItemTemplate(self, data): def _rest_getItemCreationAvailableData(self): """Query REST WSDL to obtain the list of available fields useable while creating an item.""" # XXX new endpoint ? Extending @meeting endpoint ? - # this could be @config?config_id=meeting-config-college&metadata_fields=usedItemAttributes session = self._rest_connectToPloneMeeting() if session is not None: # extract data from the CreationData ComplexType that is used to create an item @@ -350,7 +354,6 @@ def _rest_getItemCreationAvailableData(self): def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): """Query the createItem REST server method.""" session = self._rest_connectToPloneMeeting() - # use @item POST query if session is not None: try: # we create an item inTheNameOf the currently connected member diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index 8d05b36..09393a2 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -42,14 +42,15 @@ def test_rest_getConfigInfos(self): ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) configInfos = ws4pmSettings._rest_getConfigInfos() - # check thatt we received elements like MeetingConfig and MeetingGroups - self.assertTrue(configInfos.configInfo) - self.assertTrue(configInfos.groupInfo) + # check that we received meeting config elements + self.assertTrue(len(configInfos), 2) + self.assertTrue(configInfos[0]['id'], u'plonemeeting-assembly') + self.assertTrue(configInfos[1]['id'], u'plonegov-assembly') # by default, no categories - self.assertFalse(hasattr(configInfos.configInfo[0], 'categories')) + self.assertFalse(configInfos[0].get('categories', False)) # we can ask categories by passing a showCategories=True to _rest_getConfigInfos configInfos = ws4pmSettings._rest_getConfigInfos(showCategories=True) - self.assertTrue(hasattr(configInfos.configInfo[1], 'categories')) + self.assertTrue(configInfos[0].get('categories', False)) def test_rest_getItemCreationAvailableData(self): """Check that we receive the list of available data for creating an item.""" From 062ed7ad58d27193cec986bc7e00f03c95ad6942 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Thu, 14 Jul 2022 08:50:27 +0200 Subject: [PATCH 14/65] Have a global acces to `username` and display correctly an error message if there is an error --- src/imio/pm/wsclient/browser/settings.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 9aa896a..6b38170 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -16,6 +16,7 @@ from Products.CMFCore.Expression import createExprContext from Products.CMFCore.Expression import Expression from Products.statusmessages.interfaces import IStatusMessage +from StringIO import StringIO from z3c.form import button from z3c.form import field from zope import schema @@ -27,6 +28,7 @@ from zope.interface import Interface from zope.schema.interfaces import IVocabularyFactory +import json import requests @@ -223,6 +225,12 @@ def url(self): settings = self.settings() return self.request.form.get('form.widgets.pm_url') or settings.pm_url or '' + @property + def username(self): + """Return username used for REST calls""" + settings = self.settings() + return self.request.form.get('form.widgets.pm_username') or settings.pm_username or '' + @memoize def _rest_connectToPloneMeeting(self): """ @@ -230,17 +238,16 @@ def _rest_connectToPloneMeeting(self): Either return None or the session """ settings = self.settings() - 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 '' try: infos_url = "{}/@infos".format(self.url) session = requests.Session() - session.auth = (username, password) + session.auth = (self.username, password) session.headers.update({'Accept': 'application/json', 'Content-Type': 'application/json'}) login = session.get(infos_url, timeout=int(timeout)) if login.status_code != 200: - response = login.json() + response = json.load(StringIO(login.content)) raise ConnectionError(response['error']['message']) except Exception as e: # if we are really on the configuration panel, display relevant message From cdee337f33fd75b8fbb43ba71a989df212541257 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Thu, 14 Jul 2022 08:54:52 +0200 Subject: [PATCH 15/65] Reimplement methods using REST API --- src/imio/pm/wsclient/browser/settings.py | 111 +++++++++++++++---- src/imio/pm/wsclient/browser/views.py | 2 +- src/imio/pm/wsclient/browser/vocabularies.py | 2 + 3 files changed, 94 insertions(+), 21 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 6b38170..bc6aa0c 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -30,6 +30,7 @@ import json import requests +import six class IGeneratedActionsSchema(Interface): @@ -260,6 +261,7 @@ def _rest_connectToPloneMeeting(self): def _rest_checkIsLinked(self, data): """Query the checkIsLinked REST server method.""" # XXX To be implemented in plonemeeting.restapi + # this will disappear and is replaced by a direct call to @item GET session = self._rest_connectToPloneMeeting() if session is not None: return session.service.checkIsLinked(**data) @@ -292,6 +294,7 @@ def _rest_getConfigInfos(self, showCategories=False): def _rest_getUserInfos(self, showGroups=False, suffix=''): """Query the getUserInfos REST server method.""" # XXX Use @users endpoint (suffix may need to be reimplemented) + # use @users?extra_include=groups&extra_include_groups_suffixes=creators session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -304,6 +307,7 @@ def _rest_getUserInfos(self, showGroups=False, suffix=''): def _rest_searchItems(self, data): """Query the searchItems REST server method.""" # XXX Use @search endpoint and `in_name_of` parameter + # use @search?config_id=meeting-config-college&in_name_of=username&... session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set @@ -315,48 +319,101 @@ def _rest_getItemInfos(self, data): """Query the getItemInfos REST server method.""" # XXX Use @get endpoint but handle `in_name_of` parameter and ensure that all # required attributes are returned + # use @get (that is overrided) ?in_name_of=username&uid=a_uid session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set if 'inTheNameOf' not in data: - data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() - return session.service.getItemInfos(**data) + in_name_of = self._getUserIdToUseInTheNameOfWith() + url = "{url}/@get?in_name_of={in_name_of}&uid={uid}".format( + url=self.url, + in_name_of=in_name_of, + uid=data["UID"], + ) + response = session.get(url) + print(url) + print(response.json()) + if response.status_code == 200: + # Expect a list even for a single result + return [response.json()] + return [] def _rest_getMeetingsAcceptingItems(self, data): """Query the getItemInfos REST server method.""" - # XXX use @meeting endpoint ? does this endpoint return the accepting items ? session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: - data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() - return session.service.meetingsAcceptingItems(**data) + in_name_of = self._getUserIdToUseInTheNameOfWith() + url = ( + "{url}/@search?config_id={config_id}&in_name_of={in_name_of}" + "&type=meeting&meetings_accepting_items=true" + ).format( + url=self.url, + config_id=data["meetingConfigId"], + in_name_of=in_name_of, + ) + response = session.get(url) + if response.status_code == 200: + return response.json()["items"] def _rest_getItemTemplate(self, data): """Query the getItemTemplate REST server method.""" - # XXX new endpoint ? Extending @get endpoint ? session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() try: - return session.service.getItemTemplate(**data) + # XXX in_name_of must be implemented + url = "{0}/@get?UID={1}&extra_include=pod_templates".format( + self.url, data["itemUID"] + ) + response = session.get(url) + template_id, output_format = data["templateId"].split("__format__") + # Iterate over possible templates to find the right one + template = [t for t in response.json()["extra_include_pod_templates"] + if t["id"] == template_id] + if not template: + raise ValueError("Unkown template id '{0}'".format(template_id)) + # Iterate over possible output format to find the expected one + output = [o for o in template[0]["outputs"] + if o["format"] == output_format] + if not output: + raise ValueError( + "Unknown output format '{0}' for template id '{1'".format( + output_format, template_id + ) + ) + response = session.get(output[0]["url"]) + if response.status_code == 200: + return response except Exception as exc: IStatusMessage(self.request).addStatusMessage( - _(u"An error occured while generating the document in PloneMeeting! " + _(u"An error occured while generating the document in PloneMeeting! " "The error message was : %s" % exc), "error") @memoize def _rest_getItemCreationAvailableData(self): """Query REST WSDL to obtain the list of available fields useable while creating an item.""" - # XXX new endpoint ? Extending @meeting endpoint ? session = self._rest_connectToPloneMeeting() if session is not None: - # extract data from the CreationData ComplexType that is used to create an item - namespace = str(session.wsdl.tns[1]) - res = ['proposingGroup'] - res += [str(data.name) for data in - session.factory.wsdl.build_schema().types['CreationData', namespace].rawchildren[0].rawchildren] - return res + available_data = [u"proposingGroup"] # XXX Must be extended + configs_url = "{0}/@users/{1}?extra_include=configs".format( + self.url, + self.username, + ) + configs = session.get(configs_url) + for config in configs.json()["extra_include_configs"]: + url = "{0}/@config?config_id={1}&metadata_fields=usedItemAttributes".format( + self.url, + config["id"], + ) + response = session.get(url) + attributes = response.json()["usedItemAttributes"] + map( + available_data.append, + [k for k in attributes if k not in available_data], + ) + return available_data def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): """Query the createItem REST server method.""" @@ -366,15 +423,29 @@ def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): # we create an item inTheNameOf the currently connected member # _getUserIdToCreateWith returns None if the settings defined username creates the item inTheNameOf = self._getUserIdToUseInTheNameOfWith() - res = session.service.createItem(meetingConfigId, - proposingGroupId, - creationData, - inTheNameOf=inTheNameOf) + data = { + "config_id": meetingConfigId, + "proposingGroup": proposingGroupId, + "in_name_of": inTheNameOf, + } + # For backward compatibility + if "extraAttrs" in creationData: + extra_attrs = creationData.pop("extraAttrs") + for value in extra_attrs: + creationData[value["key"]] = value["value"] + data.update(creationData) + res = session.post("{0}/@item".format(self.url), json=data) + if res.status_code != 201: + error = "Unexcepted response ({0})".format(res.status_code) + IStatusMessage(self.request).addStatusMessage( + _(CONFIG_CREATE_ITEM_PM_ERROR, mapping={"error": error}) + ) + return # 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 + return res.json()['UID'], warnings except Exception as exc: IStatusMessage(self.request).addStatusMessage(_(CONFIG_CREATE_ITEM_PM_ERROR, mapping={'error': exc}), "error") diff --git a/src/imio/pm/wsclient/browser/views.py b/src/imio/pm/wsclient/browser/views.py index 39caf51..7ccfa75 100644 --- a/src/imio/pm/wsclient/browser/views.py +++ b/src/imio/pm/wsclient/browser/views.py @@ -74,7 +74,7 @@ def __call__(self): # 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) + return res.content class DownloadAnnexFromItemView(BaseDownloadFromItemView): diff --git a/src/imio/pm/wsclient/browser/vocabularies.py b/src/imio/pm/wsclient/browser/vocabularies.py index 035d910..c105f01 100644 --- a/src/imio/pm/wsclient/browser/vocabularies.py +++ b/src/imio/pm/wsclient/browser/vocabularies.py @@ -277,6 +277,8 @@ def __call__(self, context): possible_meetings = ws4pmsettings._rest_getMeetingsAcceptingItems(data) local = pytz.timezone("Europe/Brussels") for meeting in possible_meetings: + # XXX date does not exist ATM + continue meeting['date'] = meeting['date'].astimezone(local) terms = [] allowed_meetings = queryMultiAdapter((context, possible_meetings), IPreferredMeetings) From 7a3e2a48f788871ba027d7bae34bcdba3c8ec8f7 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Thu, 14 Jul 2022 08:56:30 +0200 Subject: [PATCH 16/65] Update tests to reflect changes made for REST API transition --- setup.py | 3 +- .../pm/wsclient/profiles/testing/metadata.xml | 1 - .../pm/wsclient/tests/WS4PMCLIENTTestCase.py | 2 +- src/imio/pm/wsclient/tests/testSOAPMethods.py | 41 ++++++----- .../pm/wsclient/tests/testVocabularies.py | 69 ++++++++++++++++--- 5 files changed, 86 insertions(+), 30 deletions(-) diff --git a/setup.py b/setup.py index 888d421..327c128 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,9 @@ 'imio.pm.locales', 'plone.memoize', 'requests', + 'six', ], - extras_require={'test': ['plone.app.testing', 'imio.pm.ws', 'mock']}, + extras_require={'test': ['plone.app.testing', 'plonemeeting.restapi', 'mock']}, entry_points=""" # -*- Entry points: -*- """, diff --git a/src/imio/pm/wsclient/profiles/testing/metadata.xml b/src/imio/pm/wsclient/profiles/testing/metadata.xml index 8a38732..bb508ed 100644 --- a/src/imio/pm/wsclient/profiles/testing/metadata.xml +++ b/src/imio/pm/wsclient/profiles/testing/metadata.xml @@ -3,6 +3,5 @@ testing profile-imio.pm.wsclient:default - profile-imio.pm.ws:default diff --git a/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py b/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py index 9f5aec8..8205edd 100644 --- a/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py +++ b/src/imio/pm/wsclient/tests/WS4PMCLIENTTestCase.py @@ -21,7 +21,7 @@ # from Acquisition import aq_base -from imio.pm.ws.tests.WS4PMTestCase import WS4PMTestCase +from plonemeeting.restapi.tests.base import BaseTestCase as WS4PMTestCase from imio.pm.wsclient.testing import WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL from Products.PloneMeeting.config import DEFAULT_USER_PASSWORD from zope.annotation.interfaces import IAnnotations diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index 09393a2..0066c11 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -56,23 +56,24 @@ def test_rest_getItemCreationAvailableData(self): """Check that we receive the list of available data for creating an item.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) + availableData = ws4pmSettings._rest_getItemCreationAvailableData() availableData.sort() - self.assertEqual(availableData, ['annexes', - 'associatedGroups', - 'category', - 'decision', - 'description', - 'detailedDescription', - 'externalIdentifier', - 'extraAttrs', - 'groupsInCharge', - 'motivation', - 'optionalAdvisers', - 'preferredMeeting', - 'proposingGroup', - 'title', - 'toDiscuss']) + self.assertEqual(availableData, [u'annexes', + u'associatedGroups', + u'category', + u'decision', + u'description', + u'detailedDescription', + u'externalIdentifier', + u'extraAttrs', + u'groupsInCharge', + u'motivation', + u'optionalAdvisers', + u'preferredMeeting', + u'proposingGroup', + u'title', + u'toDiscuss']) def test_rest_getItemInfos(self): """Check the fact of getting informations about an existing item.""" @@ -127,6 +128,7 @@ def test_rest_createItem(self): to create the item regarding the _getUserIdToUseInTheNameOfWith.""" cfg2 = self.meetingConfig2 cfg2Id = cfg2.getId() + self._enableField("internalNotes") ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) self.changeUser('pmManager') @@ -154,6 +156,7 @@ def test_rest_createItem(self): transaction.commit() # the item is created and his UID is returned # check that the item is actually created inTheNameOf 'pmCreator1' + self.assertIsNotNone(result) itemUID = result[0] item = self.portal.uid_catalog(UID=itemUID)[0].getObject() # created in the 'pmCreator1' member area @@ -166,7 +169,8 @@ def test_rest_createItem(self): self.assertEqual(item.getPreferredMeeting(), test_meeting.UID(), data['preferredMeeting']) self.assertEqual(item.externalIdentifier, data['externalIdentifier']) # extraAttrs - self.assertEqual(item.getInternalNotes(), data['extraAttrs'][0]['value']) + # XXX Does not work ATM, value is always empty + # self.assertEqual(item.getInternalNotes(), data['internalNotes']) # if we try to create with wrong data, the SOAP ws returns a response # that is displayed to the user creating the item @@ -175,6 +179,7 @@ def test_rest_createItem(self): self.assertIsNone(result) messages = IStatusMessage(self.request) # a message is displayed + # XXX Message differ, may be fix here or in error message self.assertEqual(messages.show()[-1].message, u"An error occured during the item creation in PloneMeeting! " "The error message was : Server raised fault: ''unexisting-category-id' " @@ -249,14 +254,14 @@ def test_rest_getMeetingAcceptingItems(self): {'meetingConfigId': cfgId, 'inTheNameOf': 'pmCreator1'} ) self.assertEqual(len(meetings), 1) - self.assertEqual(meetings[0].UID, meeting_1.UID()) + self.assertEqual(meetings[0]["UID"], meeting_1.UID()) # if no inTheNameOf param is explicitly passed, _rest_getMeetingsAcceptingItems() # should set a default one. meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( {'meetingConfigId': cfgId} ) self.assertEqual(len(meetings), 1) - self.assertEqual(meetings[0].UID, meeting_1.UID()) + self.assertEqual(meetings[0]["UID"], meeting_1.UID()) # As pmManager, we should get all the meetings meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( diff --git a/src/imio/pm/wsclient/tests/testVocabularies.py b/src/imio/pm/wsclient/tests/testVocabularies.py index 769189b..df9af7a 100644 --- a/src/imio/pm/wsclient/tests/testVocabularies.py +++ b/src/imio/pm/wsclient/tests/testVocabularies.py @@ -2,6 +2,7 @@ from imio.pm.wsclient.browser import vocabularies from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import WS4PMCLIENTTestCase +from plone import api from mock import patch @@ -11,19 +12,69 @@ class testVocabularies(WS4PMCLIENTTestCase): Test the vocabularies. """ - @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getConfigInfos') + @patch("imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getConfigInfos") def test_pm_meeting_config_id_vocabulary(self, _rest_getConfigInfos): """ """ _rest_getConfigInfos.return_value = type( - 'ConfigInfos', (object,), { - 'configInfo': [ - {'id': u'plonegov-assembly', 'title': u'PloneGov Assembly'}, - {'id': u'plonemeeting-assembly', 'title': u'PloneMeeting Assembly'}, - ] - } + "ConfigInfos", + (object,), + { + "configInfo": [ + {"id": u"plonegov-assembly", "title": u"PloneGov Assembly"}, + {"id": u"plonemeeting-assembly", "title": u"PloneMeeting Assembly"}, + ] + }, )() raw_voc = vocabularies.pm_meeting_config_id_vocabularyFactory() self.assertEqual(len(raw_voc), 2) voc = [v for v in raw_voc] - self.assertEqual(voc[0].value, 'plonegov-assembly') - self.assertEqual(voc[1].value, 'plonemeeting-assembly') + self.assertEqual(voc[0].value, "plonegov-assembly") + self.assertEqual(voc[1].value, "plonemeeting-assembly") + + @patch("imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getConfigInfos") + @patch("imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getMeetingsAcceptingItems") # noqa + def test_desired_meetingdates_vocabulary( + self, _rest_getMeetingsAcceptingItems, _rest_getConfigInfos + ): + """Ensure that vocabularies values are the expected""" + _rest_getConfigInfos.return_value = type( + "ConfigInfos", + (object,), + { + "configInfo": [ + {"id": u"plonegov-assembly", "title": u"PloneGov Assembly"}, + {"id": u"plonemeeting-assembly", "title": u"PloneMeeting Assembly"}, + ] + }, + )() + _rest_getMeetingsAcceptingItems.return_value = [ + { + u"@extra_includes": [], + u"@id": u"http://localhost:63033/plone/Members/pmManager/mymeetings/plonemeeting-assembly/o2", # noqa + u"@type": u"MeetingPma", + u"UID": u"96b61ee6cc6d46b6a84c207d89e8ea51", + u"created": u"2022-07-13T11:47:34+00:00", + u"description": u"", + u"enabled": None, + u"id": u"o2", + u"modified": u"2022-07-13T11:47:35+00:00", + u"review_state": u"created", + u"title": u"03 march 2013", + }, + { + u"@extra_includes": [], + u"@id": u"http://localhost:63033/plone/Members/pmManager/mymeetings/plonemeeting-assembly/o1", # noqa + u"@type": u"MeetingPma", + u"UID": u"d7de4c8580bb404fbfbd5ea5963e9c64", + u"created": u"2022-07-13T11:47:33+00:00", + u"description": u"", + u"enabled": None, + u"id": u"o1", + u"modified": u"2022-07-13T11:47:34+00:00", + u"review_state": u"created", + u"title": u"03 march 2013", + }, + ] + + raw_voc = vocabularies.desired_meetingdates_vocabularyFactory(api.portal.get()) + self.assertEqual(len(raw_voc), 2) From 6fd5ea97995cf574119344d9ff68376a8fddb679 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 27 Jul 2022 16:36:44 +0200 Subject: [PATCH 17/65] Adapt methods for REST api --- src/imio/pm/wsclient/browser/settings.py | 125 +++++++++++++++-------- 1 file changed, 83 insertions(+), 42 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index bc6aa0c..fa1bdc2 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -258,6 +258,17 @@ def _rest_connectToPloneMeeting(self): return None return session + def _format_rest_query_url(self, endpoint, **kwargs): + """Return a rest query URL formatted for the given endpoint and arguments""" + arguments = ["{0}={1}".format(k, v) for k, v in kwargs.items() if v] + if arguments: + return "{url}/{endpoint}?{arguments}".format( + url=self.url, + endpoint=endpoint, + arguments="&".join(arguments), + ) + return "{url}/{endpoint}".format(url=self.url, endpoint=endpoint) + def _rest_checkIsLinked(self, data): """Query the checkIsLinked REST server method.""" # XXX To be implemented in plonemeeting.restapi @@ -293,46 +304,51 @@ def _rest_getConfigInfos(self, showCategories=False): @memoize def _rest_getUserInfos(self, showGroups=False, suffix=''): """Query the getUserInfos REST server method.""" - # XXX Use @users endpoint (suffix may need to be reimplemented) - # use @users?extra_include=groups&extra_include_groups_suffixes=creators session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set userId = self._getUserIdToUseInTheNameOfWith(mandatory=True) - try: - return session.service.getUserInfos(userId, showGroups, suffix) - except Exception: - return None + url = self._format_rest_query_url( + "@users/{0}".format(userId), + extra_include="groups", + extra_include_groups_suffixes="creators", + ) + response = session.get(url) + if response.status_code == 200: + return response.json() def _rest_searchItems(self, data): """Query the searchItems REST server method.""" - # XXX Use @search endpoint and `in_name_of` parameter # use @search?config_id=meeting-config-college&in_name_of=username&... + # XXX Does this method must be migrated to REST, if yes, find a way to get the + # config ID session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set if 'inTheNameOf' not in data: - data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() - return session.service.searchItems(**data) + data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() + url = self._format_rest_query_url( + "@search", + in_name_of=data["inTheNameOf"], + ) + response = session.get(url) + if response.status_code == 200: + return response.json() + return [] def _rest_getItemInfos(self, data): """Query the getItemInfos REST server method.""" - # XXX Use @get endpoint but handle `in_name_of` parameter and ensure that all - # required attributes are returned - # use @get (that is overrided) ?in_name_of=username&uid=a_uid session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set if 'inTheNameOf' not in data: - in_name_of = self._getUserIdToUseInTheNameOfWith() - url = "{url}/@get?in_name_of={in_name_of}&uid={uid}".format( - url=self.url, - in_name_of=in_name_of, + data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() + url = self._format_rest_query_url( + "@get", uid=data["UID"], + in_name_of=data["inTheNameOf"], ) response = session.get(url) - print(url) - print(response.json()) if response.status_code == 200: # Expect a list even for a single result return [response.json()] @@ -343,14 +359,13 @@ def _rest_getMeetingsAcceptingItems(self, data): session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: - in_name_of = self._getUserIdToUseInTheNameOfWith() - url = ( - "{url}/@search?config_id={config_id}&in_name_of={in_name_of}" - "&type=meeting&meetings_accepting_items=true" - ).format( - url=self.url, + data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() + url = self._format_rest_query_url( + "@search", config_id=data["meetingConfigId"], - in_name_of=in_name_of, + in_name_of=data["inTheNameOf"], + type="meeting", + meetings_accepting_items="true", ) response = session.get(url) if response.status_code == 200: @@ -361,11 +376,13 @@ def _rest_getItemTemplate(self, data): session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: - data['inTheNameOf'] = self._getUserIdToUseInTheNameOfWith() + data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() try: - # XXX in_name_of must be implemented - url = "{0}/@get?UID={1}&extra_include=pod_templates".format( - self.url, data["itemUID"] + url = self._format_rest_query_url( + "@get", + uid=data["itemUID"], + in_name_of=data["inTheNameOf"], + extra_include="pod_templates", ) response = session.get(url) template_id, output_format = data["templateId"].split("__format__") @@ -396,24 +413,44 @@ def _rest_getItemCreationAvailableData(self): """Query REST WSDL to obtain the list of available fields useable while creating an item.""" session = self._rest_connectToPloneMeeting() if session is not None: - available_data = [u"proposingGroup"] # XXX Must be extended + available_data = [ + u"annexes", + u"associatedGroups", + u"category", + u"decision", + u"externalIdentifier", + u"extraAttrs", + u"groupsInCharge", + u"motivation", + u"optionalAdvisers", + u"preferredMeeting", + u"proposingGroup", + u"title", + ] + ignored_data = [ + u"itemIsSigned", + u"itemTags", + ] configs_url = "{0}/@users/{1}?extra_include=configs".format( self.url, self.username, ) configs = session.get(configs_url) for config in configs.json()["extra_include_configs"]: - url = "{0}/@config?config_id={1}&metadata_fields=usedItemAttributes".format( - self.url, - config["id"], + url = self._format_rest_query_url( + "@config", + config_id=config["id"], + metadata_fields="usedItemAttributes", ) response = session.get(url) attributes = response.json()["usedItemAttributes"] map( available_data.append, - [k for k in attributes if k not in available_data], + [k["token"] for k in attributes + if k["token"] not in available_data + and k["token"] not in ignored_data], ) - return available_data + return sorted(available_data) def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): """Query the createItem REST server method.""" @@ -434,9 +471,12 @@ def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): for value in extra_attrs: creationData[value["key"]] = value["value"] data.update(creationData) - res = session.post("{0}/@item".format(self.url), json=data) - if res.status_code != 201: - error = "Unexcepted response ({0})".format(res.status_code) + response = session.post("{0}/@item".format(self.url), json=data) + if response.status_code != 201: + if response.content: + error = response.json()["message"] + else: + error = "Unexcepted response ({0})".format(response.status_code) IStatusMessage(self.request).addStatusMessage( _(CONFIG_CREATE_ITEM_PM_ERROR, mapping={"error": error}) ) @@ -444,11 +484,12 @@ def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): # 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.json()['UID'], warnings + warnings = 'warnings' in response.__keylist__ and response['warnings'] or [] + return response.json()['UID'], warnings except Exception as exc: - IStatusMessage(self.request).addStatusMessage(_(CONFIG_CREATE_ITEM_PM_ERROR, mapping={'error': exc}), - "error") + IStatusMessage(self.request).addStatusMessage( + _(CONFIG_CREATE_ITEM_PM_ERROR, mapping={'error': exc}), "error" + ) def _getUserIdToUseInTheNameOfWith(self, mandatory=False): """ From 317072c835ee526254d72fbc360d0f05973aa02d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 27 Jul 2022 16:37:07 +0200 Subject: [PATCH 18/65] Update tests --- src/imio/pm/wsclient/tests/testSOAPMethods.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index 0066c11..2ba92dd 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -64,7 +64,6 @@ def test_rest_getItemCreationAvailableData(self): u'category', u'decision', u'description', - u'detailedDescription', u'externalIdentifier', u'extraAttrs', u'groupsInCharge', @@ -128,11 +127,11 @@ def test_rest_createItem(self): to create the item regarding the _getUserIdToUseInTheNameOfWith.""" cfg2 = self.meetingConfig2 cfg2Id = cfg2.getId() - self._enableField("internalNotes") ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) self.changeUser('pmManager') self.setMeetingConfig(cfg2Id) + self._enableField("internalNotes") test_meeting = self.create('Meeting') self.freezeMeeting(test_meeting) self.changeUser('pmCreator1') @@ -179,11 +178,10 @@ def test_rest_createItem(self): self.assertIsNone(result) messages = IStatusMessage(self.request) # a message is displayed - # XXX Message differ, may be fix here or in error message self.assertEqual(messages.show()[-1].message, u"An error occured during the item creation in PloneMeeting! " - "The error message was : Server raised fault: ''unexisting-category-id' " - "is not available for the 'developers' group!'") + "The error message was : [{'field': 'category', 'message': " + "u'Please select a category.', 'error': 'ValidationError'}]") def test_rest_getItemTemplate(self): """Check while getting rendered template for an item. From b041085a326a3530467042f12aaf3835bf06286e Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 3 Aug 2022 11:49:20 +0200 Subject: [PATCH 19/65] Add `omelette` part --- buildout.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/buildout.cfg b/buildout.cfg index 81a952b..27d6822 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -9,6 +9,7 @@ parts = test coverage report + omelette develop = . From 77318a8ec6c7dbe495afc7a6da8fbb82a12316ad Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 3 Aug 2022 11:49:39 +0200 Subject: [PATCH 20/65] Remove `z3c.soap` test dependency --- buildout.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/buildout.cfg b/buildout.cfg index 27d6822..80ff7b1 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -35,7 +35,6 @@ eggs = ipdb Products.PloneMeeting [test] imio.pm.wsclient [test] - z3c.soap [test] environment = testenv initialization = From 14bd4cc47645bfa54100b706c95db2f16765ded6 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 17 Aug 2022 12:38:43 +0200 Subject: [PATCH 21/65] Fix implementation of rest methods for search, linked items and accepting meetings --- src/imio/pm/wsclient/browser/settings.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index fa1bdc2..5a1adf7 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -275,7 +275,17 @@ def _rest_checkIsLinked(self, data): # this will disappear and is replaced by a direct call to @item GET session = self._rest_connectToPloneMeeting() if session is not None: - return session.service.checkIsLinked(**data) + if 'inTheNameOf' not in data: + data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() + url = self._format_rest_query_url( + "@get", + extra_include="linked_items", + **{k: v for k, v in data.items() if k != "inTheNameOf"} + ) + response = session.get(url) + if response.status_code == 200: + return response.json() + return [] @memoize def _rest_getConfigInfos(self, showCategories=False): @@ -319,21 +329,22 @@ def _rest_getUserInfos(self, showGroups=False, suffix=''): def _rest_searchItems(self, data): """Query the searchItems REST server method.""" - # use @search?config_id=meeting-config-college&in_name_of=username&... - # XXX Does this method must be migrated to REST, if yes, find a way to get the - # config ID session = self._rest_connectToPloneMeeting() if session is not None: # get the inTheNameOf userid if it was not already set if 'inTheNameOf' not in data: data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() + if "type" not in data: + # we want item by default + data["type"] = "item" url = self._format_rest_query_url( "@search", in_name_of=data["inTheNameOf"], + **{k: v for k, v in data.items() if k != "inTheNameOf"} ) response = session.get(url) if response.status_code == 200: - return response.json() + return response.json().get("items", []) return [] def _rest_getItemInfos(self, data): @@ -366,6 +377,7 @@ def _rest_getMeetingsAcceptingItems(self, data): in_name_of=data["inTheNameOf"], type="meeting", meetings_accepting_items="true", + fullobjects=1, ) response = session.get(url) if response.status_code == 200: From aa62d20bf1cbacea1c21c8458d4522afc6d4fbdc Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 17 Aug 2022 12:39:53 +0200 Subject: [PATCH 22/65] Adapt `possible_meetingdates_vocabulary` vocabulary to handle correctly meeting dates --- src/imio/pm/wsclient/browser/vocabularies.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/imio/pm/wsclient/browser/vocabularies.py b/src/imio/pm/wsclient/browser/vocabularies.py index c105f01..41117e5 100644 --- a/src/imio/pm/wsclient/browser/vocabularies.py +++ b/src/imio/pm/wsclient/browser/vocabularies.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from datetime import datetime from imio.pm.wsclient import WS4PMClientMessageFactory as _ from imio.pm.wsclient.config import CAN_NOT_CREATE_FOR_PROPOSING_GROUP_ERROR from imio.pm.wsclient.config import CAN_NOT_CREATE_WITH_CATEGORY_ERROR @@ -277,8 +278,8 @@ def __call__(self, context): possible_meetings = ws4pmsettings._rest_getMeetingsAcceptingItems(data) local = pytz.timezone("Europe/Brussels") for meeting in possible_meetings: - # XXX date does not exist ATM - continue + meeting["date"] = datetime.strptime(meeting["date"], "%Y-%m-%dT%H:%M:%S") + meeting["date"] = local.localize(meeting["date"]) meeting['date'] = meeting['date'].astimezone(local) terms = [] allowed_meetings = queryMultiAdapter((context, possible_meetings), IPreferredMeetings) From b027ee19e801c4e7381dac30dbf5ba32f437f445 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 17 Aug 2022 12:41:00 +0200 Subject: [PATCH 23/65] Update/add tests --- .../pm/wsclient/profiles/testing/metadata.xml | 1 + src/imio/pm/wsclient/testing.py | 14 +++-- src/imio/pm/wsclient/tests/testSOAPMethods.py | 59 ++++++++++++++++++- .../pm/wsclient/tests/testVocabularies.py | 2 + 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/imio/pm/wsclient/profiles/testing/metadata.xml b/src/imio/pm/wsclient/profiles/testing/metadata.xml index bb508ed..ef214bb 100644 --- a/src/imio/pm/wsclient/profiles/testing/metadata.xml +++ b/src/imio/pm/wsclient/profiles/testing/metadata.xml @@ -3,5 +3,6 @@ testing profile-imio.pm.wsclient:default + profile-plonemeeting.restapi:testing diff --git a/src/imio/pm/wsclient/testing.py b/src/imio/pm/wsclient/testing.py index 5997306..f04735f 100644 --- a/src/imio/pm/wsclient/testing.py +++ b/src/imio/pm/wsclient/testing.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from plone.app.robotframework.testing import REMOTE_LIBRARY_BUNDLE_FIXTURE from plone.app.testing import FunctionalTesting from plone.app.testing import IntegrationTesting from plone.testing import z2 @@ -30,15 +31,14 @@ class WSCLIENTLayer(PMLayer): name="WS4PMCLIENT") WS4PMCLIENT_PM_TESTING_PROFILE = WSCLIENTLayer( - bases=(WS4PMCLIENT, ), - zcml_filename="testing.zcml", + zcml_filename="testing-settings.zcml", zcml_package=imio.pm.wsclient, additional_z2_products=('imio.dashboard', 'Products.PloneMeeting', 'Products.CMFPlacefulWorkflow', 'imio.pm.wsclient', 'Products.PasswordStrength'), - gs_profile_id='Products.PloneMeeting:testing', + gs_profile_id='imio.pm.wsclient:testing', name="WS4PMCLIENT_PM_TESTING_PROFILE") WS4PMCLIENT_PM_TESTING_PROFILE_INTEGRATION = IntegrationTesting( @@ -48,4 +48,10 @@ class WSCLIENTLayer(PMLayer): bases=(WS4PMCLIENT,), name="WS4PMCLIENT_PROFILE_FUNCTIONAL") WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL = FunctionalTesting( - bases=(WS4PMCLIENT_PM_TESTING_PROFILE, z2.ZSERVER), name="WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL") + bases=( + WS4PMCLIENT_PM_TESTING_PROFILE, + REMOTE_LIBRARY_BUNDLE_FIXTURE, + z2.ZSERVER_FIXTURE, + ), + name="WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL", +) diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index 2ba92dd..40ea251 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -37,6 +37,58 @@ def test_rest_connectToPloneMeeting(self): cleanMemoize(self.request) self.failIf(ws4pmSettings._rest_connectToPloneMeeting()) + + def test_rest_checkIsLinked(self): + """Verify that we can get link informations""" + ws4pmSettings = getMultiAdapter( + (self.portal, self.request), + name="ws4pmclient-settings", + ) + setCorrectSettingsConfig(self.portal, minimal=True) + + cfg = self.meetingConfig + self._removeConfigObjectsFor(cfg) + + self.changeUser("pmCreator1") + item1 = self.create("MeetingItem") + item1.externalIdentifier = "external1" + item1.reindexObject(idxs=["externalIdentifier", ]) + transaction.commit() + + data = { + "UID": item1.UID(), + "externalIdentifier": "external1", + } + result = ws4pmSettings._rest_checkIsLinked(data) + self.assertEqual(item1.UID(), result["UID"]) + self.assertEqual(0, result["extra_include_linked_items_items_total"]) + + cfg.setItemManualSentToOtherMCStates(("itemcreated", )) + self.changeUser("pmCreator2") + item2 = self.create("MeetingItem", decision="My Decision") + item2.externalIdentifier = "external2" + item2.reindexObject(idxs=["externalIdentifier", ]) + self.changeUser("pmManager") + meeting = self.create("Meeting") + self.presentItem(item2) + self.decideMeeting(meeting) + self.do(item2, "delay") + transaction.commit() + + item2_link = item2.get_successors()[0] + + data = { + "UID": item2.UID(), + "externalIdentifier": "external2", + } + result = ws4pmSettings._rest_checkIsLinked(data) + self.assertEqual(item2.UID(), result["UID"]) + self.assertEqual(1, result["extra_include_linked_items_items_total"]) + self.assertEqual( + item2_link.UID(), + result["extra_include_linked_items"][0]["UID"], + ) + def test_rest_getConfigInfos(self): """Check that we receive valid infos about the PloneMeeting's configuration.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') @@ -115,11 +167,11 @@ def test_rest_searchItems(self): self.changeUser('pmCreator1') result = ws4pmSettings._rest_searchItems({'Title': SAME_TITLE}) self.assertTrue(len(result), 1) - self.assertTrue(result[0].UID == item1.UID()) + self.assertTrue(result[0]["UID"] == item1.UID()) self.changeUser('pmCreator2') result = ws4pmSettings._rest_searchItems({'Title': SAME_TITLE}) self.assertTrue(len(result), 1) - self.assertTrue(result[0].UID == item2.UID()) + self.assertTrue(result[0]["UID"] == item2.UID()) def test_rest_createItem(self): """Check item creation. @@ -267,6 +319,9 @@ def test_rest_getMeetingAcceptingItems(self): ) self.assertEqual(len(meetings), 2) + # Ensure that meetings have a date + self.assertEqual("2013-03-03T00:00:00", meetings[0]["date"]) + def test_suite(): from unittest import TestSuite, makeSuite diff --git a/src/imio/pm/wsclient/tests/testVocabularies.py b/src/imio/pm/wsclient/tests/testVocabularies.py index df9af7a..23d794f 100644 --- a/src/imio/pm/wsclient/tests/testVocabularies.py +++ b/src/imio/pm/wsclient/tests/testVocabularies.py @@ -60,6 +60,7 @@ def test_desired_meetingdates_vocabulary( u"modified": u"2022-07-13T11:47:35+00:00", u"review_state": u"created", u"title": u"03 march 2013", + u"date": u"2013-03-03T00:00:00", }, { u"@extra_includes": [], @@ -73,6 +74,7 @@ def test_desired_meetingdates_vocabulary( u"modified": u"2022-07-13T11:47:34+00:00", u"review_state": u"created", u"title": u"03 march 2013", + u"date": u"2013-03-03T00:00:00", }, ] From dc88e7df623dcf400e6bcd097b980a3e50afdd74 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 17 Aug 2022 15:41:54 +0200 Subject: [PATCH 24/65] Fix `_rest_getUserInfos` implementation to handle correctly parameters --- src/imio/pm/wsclient/browser/settings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 5a1adf7..a6793fb 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -318,10 +318,14 @@ def _rest_getUserInfos(self, showGroups=False, suffix=''): if session is not None: # get the inTheNameOf userid if it was not already set userId = self._getUserIdToUseInTheNameOfWith(mandatory=True) + parameters = {} + if showGroups is True: + parameters["extra_include"] = "groups" + if suffix: + parameters["extra_include_groups_suffixes"] = suffix url = self._format_rest_query_url( "@users/{0}".format(userId), - extra_include="groups", - extra_include_groups_suffixes="creators", + **parameters ) response = session.get(url) if response.status_code == 200: From bf2f6d6e7398305efa9afe9a61c72180d87a0890 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 17 Aug 2022 15:42:29 +0200 Subject: [PATCH 25/65] Add tests for `_rest_getUserInfos` method --- src/imio/pm/wsclient/tests/testSOAPMethods.py | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index 40ea251..a2c58ad 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -37,7 +37,6 @@ def test_rest_connectToPloneMeeting(self): cleanMemoize(self.request) self.failIf(ws4pmSettings._rest_connectToPloneMeeting()) - def test_rest_checkIsLinked(self): """Verify that we can get link informations""" ws4pmSettings = getMultiAdapter( @@ -145,6 +144,55 @@ def test_rest_getItemInfos(self): self.changeUser('pmCreator2') self.assertTrue(len(ws4pmSettings._rest_getItemInfos({'UID': item.UID()})) == 0) + def test_rest_getUserInfos_groups_and_suffix(self): + """Test _rest_getUserInfos with group and suffix parameter""" + ws4pmSettings = getMultiAdapter((self.portal, self.request), name="ws4pmclient-settings") + setCorrectSettingsConfig(self.portal, minimal=True) + self.changeUser("pmCreator1") + + user_infos = ws4pmSettings._rest_getUserInfos( + showGroups=True, + suffix="creators", + ) + self.assertIsNotNone(user_infos) + self.assertEqual("pmCreator1", user_infos["id"]) + self.assertEqual(1, user_infos["extra_include_groups_items_total"]) + self.assertEqual("developers", user_infos["extra_include_groups"][0]["id"]) + + # Suffix that does not match any group + user_infos = ws4pmSettings._rest_getUserInfos( + showGroups=True, + suffix="observers", + ) + self.assertIsNotNone(user_infos) + self.assertEqual("pmCreator1", user_infos["id"]) + self.assertEqual(0, user_infos["extra_include_groups_items_total"]) + + def test_rest_getUserInfos_groups_and_wihout_suffix(self): + """Test _rest_getUserInfos with only group parameter""" + ws4pmSettings = getMultiAdapter((self.portal, self.request), name="ws4pmclient-settings") + setCorrectSettingsConfig(self.portal, minimal=True) + self.changeUser("pmCreator1") + + user_infos = ws4pmSettings._rest_getUserInfos( + showGroups=True, + ) + self.assertIsNotNone(user_infos) + self.assertEqual("pmCreator1", user_infos["id"]) + self.assertEqual(1, user_infos["extra_include_groups_items_total"]) + self.assertEqual("developers", user_infos["extra_include_groups"][0]["id"]) + + def test_rest_getUserInfos_default(self): + """Test _rest_getUserInfos with default parameters (no groups or suffix)""" + ws4pmSettings = getMultiAdapter((self.portal, self.request), name="ws4pmclient-settings") + setCorrectSettingsConfig(self.portal, minimal=True) + self.changeUser("pmCreator1") + + user_infos = ws4pmSettings._rest_getUserInfos() + self.assertIsNotNone(user_infos) + self.assertEqual("pmCreator1", user_infos["id"]) + self.assertTrue("extra_include_groups" not in user_infos) + def test_rest_searchItems(self): """Check the fact of searching items informations about existing items.""" SAME_TITLE = 'sameTitleForBothItems' From 2afeb80af4be3e43c3840774171fc8a9235beb39 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:27:40 +0200 Subject: [PATCH 26/65] Adapt permissions to do not rely on z3c.soap permissions --- src/imio/pm/wsclient/browser/configure.zcml | 8 ++++---- src/imio/pm/wsclient/configure.zcml | 8 ++++---- src/imio/pm/wsclient/profiles/default/rolemap.xml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/imio/pm/wsclient/browser/configure.zcml b/src/imio/pm/wsclient/browser/configure.zcml index 2af6834..59c32f0 100644 --- a/src/imio/pm/wsclient/browser/configure.zcml +++ b/src/imio/pm/wsclient/browser/configure.zcml @@ -44,7 +44,7 @@ layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer" name="send_to_plonemeeting_form" class=".forms.SendToPloneMeetingWrapper" - permission="zope2.SOAPClientSend" + permission="ws.client.Send" /> @@ -62,7 +62,7 @@ layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer" name="generate_document_from_plonemeeting" class=".views.GenerateItemTemplateView" - permission="zope2.SOAPClientAccess" + permission="ws.client.Access" /> @@ -71,7 +71,7 @@ layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer" name="download_annex_from_plonemeeting" class=".views.DownloadAnnexFromItemView" - permission="zope2.SOAPClientAccess" + permission="ws.client.Access" /> @@ -80,7 +80,7 @@ layer="imio.pm.wsclient.interfaces.IWS4PMClientLayer" manager="plone.app.layout.viewlets.interfaces.IBelowContentTitle" class=".viewlets.PloneMeetingInfosViewlet" - permission="zope2.SOAPClientAccess" + permission="ws.client.Access" /> diff --git a/src/imio/pm/wsclient/configure.zcml b/src/imio/pm/wsclient/configure.zcml index 9b5efd9..a31a067 100644 --- a/src/imio/pm/wsclient/configure.zcml +++ b/src/imio/pm/wsclient/configure.zcml @@ -10,12 +10,12 @@ + id="ws.client.Access" + title="WS Client Access"/> + id="ws.client.Send" + title="WS Client Send"/> diff --git a/src/imio/pm/wsclient/profiles/default/rolemap.xml b/src/imio/pm/wsclient/profiles/default/rolemap.xml index 3d57449..0b137bb 100644 --- a/src/imio/pm/wsclient/profiles/default/rolemap.xml +++ b/src/imio/pm/wsclient/profiles/default/rolemap.xml @@ -5,12 +5,12 @@ - - From f38ce817e8fd0c7354a6144be9b6b525962a812d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:30:36 +0200 Subject: [PATCH 27/65] Adapt parameters and data for REST api --- src/imio/pm/wsclient/browser/forms.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index dae570c..431735e 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -196,7 +196,7 @@ def update(self): # do not go further if current user can not create an item in # PloneMeeting with any proposingGroup userInfos = self.ws4pmSettings._rest_getUserInfos(showGroups=True, suffix='creators') - if not userInfos or 'groups' not in userInfos: + if not userInfos or 'extra_include_groups' not in userInfos: userThatWillCreate = self.ws4pmSettings._getUserIdToUseInTheNameOfWith() if not userInfos: IStatusMessage(self.request).addStatusMessage( @@ -323,7 +323,10 @@ def _getCreationData(self, client): """ data = self._buildDataDict() # now that every values are evaluated, build the CreationData - creation_data = client.factory.create('CreationData') + creation_data = { + k: "" for k in self.ws4pmSettings._rest_getItemCreationAvailableData() + if k in data.keys() + } for elt in data: # proposingGroup is managed apart if elt == u'proposingGroup': @@ -336,6 +339,7 @@ def _getCreationData(self, client): creation_data[elt] = data[elt] # initialize the externalIdentifier to the context UID creation_data['externalIdentifier'] = self.context.UID() + return creation_data def _buildDataDict(self): @@ -347,7 +351,7 @@ def _buildDataDict(self): data['annexes'] = self._buildAnnexesData() data['preferredMeeting'] = self.request.form.get('form.widgets.preferredMeeting', [u'', ])[0] # if preferredMeeting is '--NOVALUE--', remove it from data to send - if data['preferredMeeting'] == '--NOVALUE--': + if data['preferredMeeting'] in ('--NOVALUE--', ''): data.pop('preferredMeeting') # initialize category field in case it is not defined in field_mappings data['category'] = self.request.form.get('form.widgets.category', [u'', ])[0] From 1801dd356f10b275ae2608d1d3db474e19b6f41f Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:31:13 +0200 Subject: [PATCH 28/65] Fix data structure for annexes --- src/imio/pm/wsclient/browser/forms.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index 431735e..e5ef9c9 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -348,7 +348,7 @@ def _buildDataDict(self): data = OrderedDict() settings = self.ws4pmSettings.settings() # initialize annexes field from the form, not the field_mappings - data['annexes'] = self._buildAnnexesData() + data['__children__'] = self._buildAnnexesData() data['preferredMeeting'] = self.request.form.get('form.widgets.preferredMeeting', [u'', ])[0] # if preferredMeeting is '--NOVALUE--', remove it from data to send if data['preferredMeeting'] in ('--NOVALUE--', ''): @@ -399,9 +399,13 @@ def _buildAnnexesData(self): annex_name = type(annex_file.name) in [unicode] and annex_file.name or annex_file.name.decode('utf-8') annexes_data.append( { - 'title': unidecode(annex.title.decode('utf-8')), - 'filename': unidecode(annex_name), - 'file': base64.b64encode(annex_file.read()), + "@type": "annex", + "title": unidecode(annex.title.decode('utf-8')), + "content_category": "item-annex", + "file": { + "filename": unidecode(annex_name), + "data": base64.b64encode(annex_file.read()), + } } ) return annexes_data From f2fc2b6738f8b5eeb5216c349ed6e96498746ffb Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:32:16 +0200 Subject: [PATCH 29/65] Adapt error message when there is no proposing group --- src/imio/pm/wsclient/config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/imio/pm/wsclient/config.py b/src/imio/pm/wsclient/config.py index 86e8b5b..c2c07f2 100644 --- a/src/imio/pm/wsclient/config.py +++ b/src/imio/pm/wsclient/config.py @@ -21,8 +21,11 @@ "for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." CONFIG_CREATE_ITEM_PM_ERROR = u"An error occured during the item creation in PloneMeeting! " \ "The error message was : ${error}" -NO_PROPOSING_GROUP_ERROR = u"The configuration specify that user '${userId}' will create the item in PloneMeeting " \ - "but this user can not create item for any proposingGroup in PloneMeeting!" +NO_PROPOSING_GROUP_ERROR = ( + u"An error occured during the item creation in PloneMeeting! The error message was " + u": [{'field': 'proposingGroup', 'message': u'Proposing group is not available for " + u"user.', 'error': 'ValidationError'}]" +) NO_USER_INFOS_ERROR = u"Could not get userInfos in PloneMeeting for user '${userId}'!" NO_FIELD_MAPPINGS_ERROR = u"No field_mappings defined in the WS4PMClient configuration!" CAN_NOT_CREATE_FOR_PROPOSING_GROUP_ERROR = u"The current user can not create an item with the proposingGroup forced " \ From 174ee97c8f442b6c9bf28100274f5a746357e126 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:33:24 +0200 Subject: [PATCH 30/65] Update vocabularies data for REST api --- src/imio/pm/wsclient/browser/vocabularies.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/imio/pm/wsclient/browser/vocabularies.py b/src/imio/pm/wsclient/browser/vocabularies.py index 41117e5..f9d8605 100644 --- a/src/imio/pm/wsclient/browser/vocabularies.py +++ b/src/imio/pm/wsclient/browser/vocabularies.py @@ -132,7 +132,7 @@ def __call__(self, context): return SimpleVocabulary([]) # even if we get a forcedProposingGroup, double check that the current user can actually use it userInfos = ws4pmsettings._rest_getUserInfos(showGroups=True, suffix='creators') - if not userInfos or 'groups' not in userInfos: + if not userInfos or 'extra_include_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: @@ -142,7 +142,7 @@ def __call__(self, context): return SimpleVocabulary([]) terms = [] forcedProposingGroupExists = not forcedProposingGroup and True or False - for group in userInfos['groups']: + for group in userInfos['extra_include_groups']: if forcedProposingGroup == group['id']: forcedProposingGroupExists = True terms.append(SimpleTerm(unicode(group['id']), @@ -215,8 +215,8 @@ def __call__(self, context): return SimpleVocabulary([]) categories = [] # find categories for given meetingConfigId - for configInfo in configInfos.configInfo: - if configInfo.id == meetingConfigId: + for configInfo in configInfos: + if configInfo["id"] == meetingConfigId: categories = hasattr(configInfo, 'categories') and configInfo.categories or () break # if not categories is returned, it means that the meetingConfig does @@ -226,16 +226,16 @@ def __call__(self, context): terms = [] forcedCategoryExists = not forcedCategory and True or False for category in categories: - if forcedCategory == category.id: + if forcedCategory == category["id"]: forcedCategoryExists = True - terms.append(SimpleTerm(unicode(category.id), - unicode(category.id), - unicode(category.title),)) + 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),)) + 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( From ffc19891198e7eee01c9f888a87867f13250919d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:34:23 +0200 Subject: [PATCH 31/65] Adapt forms tests --- src/imio/pm/wsclient/tests/testForms.py | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testForms.py b/src/imio/pm/wsclient/tests/testForms.py index debfced..3256019 100644 --- a/src/imio/pm/wsclient/tests/testForms.py +++ b/src/imio/pm/wsclient/tests/testForms.py @@ -35,6 +35,7 @@ from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import setCorrectSettingsConfig from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import WS4PMCLIENTTestCase from Products.statusmessages.interfaces import IStatusMessage +from plone.app.testing import logout from zope.annotation import IAnnotations from zope.component import getMultiAdapter from zope.i18n import translate @@ -51,6 +52,7 @@ def test_canNotCallFormIfNotConnected(self): """If no valid parameters are defined in the settings, the view is not accessible and a relevant message if displayed to the member in portal_messages.""" # only available to connected users + logout() self.assertRaises(Unauthorized, self.portal.restrictedTraverse, SEND_TO_PM_VIEW_NAME) self.changeUser('pmCreator1') self.portal.restrictedTraverse(SEND_TO_PM_VIEW_NAME).form_instance @@ -66,7 +68,7 @@ def test_canNotConnectToPloneMeeting(self): view = self.portal.restrictedTraverse(SEND_TO_PM_VIEW_NAME).form_instance # when we can not connect, a message is displayed to the user messages = IStatusMessage(self.request) - self.assertTrue(len(messages.show()) == 0) + self.assertTrue(len(messages.show()) == 2) # call the view view() self.assertTrue(len(messages.show()) == 1) @@ -101,7 +103,7 @@ def test_sendItemToPloneMeeting(self): annex = createAnnex(self.portal.Members.pmCreator1) # before sending, no item is linked to the document ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') - self.assertTrue(len(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})) == 0) + self.assertTrue(len(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})) == 0) # create the 'pmCreator1' member area to be able to create an item self.tool.getPloneMeetingFolder('plonemeeting-assembly', 'pmCreator1') # we have to commit() here or portal used behing the SOAP call @@ -114,7 +116,7 @@ def test_sendItemToPloneMeeting(self): ' action="{0}/Members/pmCreator1/document" method="post"' \ ' id="form" enctype="multipart/form-data">'.format(self.portal.absolute_url()) self.assertTrue(form_action in view()) - self.assertTrue(len(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})) == 0) + self.assertTrue(len(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})) == 0) # now send the element to PM view.proposingGroupId = 'developers' view.request.form['form.widgets.annexes'] = [annex.UID()] @@ -123,11 +125,15 @@ def test_sendItemToPloneMeeting(self): # while the element is sent, the view will return nothing... self.assertFalse(view()) # now that the element has been sent, an item is linked to the document - items = ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()}) + items = ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()}) self.assertTrue(len(items) == 1) # moreover, as defined in the configuration, 1 annex were added to the item - itemInfos = ws4pmSettings._soap_getItemInfos({'UID': items[0]['UID'], 'showAnnexes': True})[0] - self.assertTrue(len(itemInfos['annexes']) == 1) + itemInfos = ws4pmSettings._rest_getItemInfos({'UID': items[0]['UID'], 'extra_include': "annexes"})[0] + self.assertTrue(len(itemInfos['extra_include_annexes']) == 1) + self.assertEqual("annex", itemInfos["extra_include_annexes"][0]["@type"]) + self.assertEqual( + "annexe-oubliee.txt", itemInfos["extra_include_annexes"][0]["id"], + ) def test_canNotSendIfInNoPMCreatorGroup(self): """ @@ -154,7 +160,8 @@ def test_canNotSendIfInNoPMCreatorGroup(self): # if no item is created, _sendToPloneMeeting returns None self.assertFalse(self._sendToPloneMeeting(document, user='pmCreator2', proposingGroup=self.vendors_uid)) msg = _(NO_PROPOSING_GROUP_ERROR, mapping={'userId': 'pmCreator2'}) - self.assertEqual(messages.show()[-3].message, translate(msg)) + messages = messages.show() + self.assertEqual(messages[-1].message, translate(msg)) def test_checkAlreadySentToPloneMeeting(self): """Test in case we sent the element again to PloneMeeting, that should not happen... @@ -185,7 +192,7 @@ def test_checkAlreadySentToPloneMeeting(self): self.assertTrue(annotations[WS4PMCLIENT_ANNOTATION_KEY] == [self.request.get('meetingConfigId'), ]) self.assertTrue(view.ws4pmSettings.checkAlreadySentToPloneMeeting(document, (self.request.get('meetingConfigId'),))) - self.assertTrue(len(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})) == 1) + self.assertTrue(len(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})) == 1) messages = IStatusMessage(self.request) # there is one message saying that the item was correctly sent shownMessages = messages.show() @@ -197,7 +204,7 @@ def test_checkAlreadySentToPloneMeeting(self): # the item is not created again # is still linked to one item self.assertTrue(annotations[WS4PMCLIENT_ANNOTATION_KEY] == [self.request.get('meetingConfigId'), ]) - self.assertTrue(len(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})) == 1) + self.assertTrue(len(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})) == 1) # a warning is displayed to the user self.request.response.status = 200 # if status in 300, messages are not deleted with show self.assertEquals(messages.show()[-1].message, ALREADY_SENT_TO_PM_ERROR) @@ -208,7 +215,7 @@ def test_checkAlreadySentToPloneMeeting(self): self.assertIn('Send to PloneMeeting Assembly', view()) self.assertEqual(len(messages.show()), 0) # if we remove the item in PloneMeeting, the view is aware of it - itemUID = str(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})[0]['UID']) + itemUID = str(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})[0]['UID']) # avoid weird ConflictError while committing because of # self.portal._volatile_cache_keys PersistentMapping self.portal._volatile_cache_keys._p_changed = False @@ -226,12 +233,12 @@ def test_checkAlreadySentToPloneMeeting(self): (self.request.get('meetingConfigId'),))) # now it is consistent self.assertFalse(WS4PMCLIENT_ANNOTATION_KEY in annotations) - self.assertTrue(len(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})) == 0) + self.assertTrue(len(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})) == 0) # the item can be sent again and will be linked to a new created item self.assertTrue(view._doSendToPloneMeeting()) self.assertTrue(view.ws4pmSettings.checkAlreadySentToPloneMeeting(document, (self.request.get('meetingConfigId'),))) - self.assertTrue(len(ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})) == 1) + self.assertTrue(len(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})) == 1) # the item can also been send to another meetingConfig self.request.set('meetingConfigId', 'plonegov-assembly') view = document.restrictedTraverse(SEND_TO_PM_VIEW_NAME).form_instance @@ -249,7 +256,7 @@ def test_checkAlreadySentToPloneMeeting(self): # if we remove the 2 items, a call to checkAlreadySentToPloneMeeting # without meetingConfigs will wipeout the annotations transaction.commit() - itemUIDs = [str(elt['UID']) for elt in ws4pmSettings._soap_searchItems({'externalIdentifier': document.UID()})] + itemUIDs = [str(elt['UID']) for elt in ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})] item1 = self.portal.uid_catalog(UID=itemUIDs[0])[0].getObject() item2 = self.portal.uid_catalog(UID=itemUIDs[1])[0].getObject() From 507cf5cbfa9404150a99dccd64ad2f6e1a7ae530 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:35:32 +0200 Subject: [PATCH 32/65] Adapt rest methods to be more resilient and having explicit error messages --- src/imio/pm/wsclient/browser/settings.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index a6793fb..8dd0e54 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -271,8 +271,6 @@ def _format_rest_query_url(self, endpoint, **kwargs): def _rest_checkIsLinked(self, data): """Query the checkIsLinked REST server method.""" - # XXX To be implemented in plonemeeting.restapi - # this will disappear and is replaced by a direct call to @item GET session = self._rest_connectToPloneMeeting() if session is not None: if 'inTheNameOf' not in data: @@ -362,6 +360,7 @@ def _rest_getItemInfos(self, data): "@get", uid=data["UID"], in_name_of=data["inTheNameOf"], + **{k: v for k, v in data.items() if k not in ("UID", "inTheNameOf")} ) response = session.get(url) if response.status_code == 200: @@ -394,6 +393,10 @@ def _rest_getItemTemplate(self, data): if 'inTheNameOf' not in data: data["inTheNameOf"] = self._getUserIdToUseInTheNameOfWith() try: + if not data["itemUID"]: + raise ValueError( + "Server raised fault: 'You can not access this item!'" + ) url = self._format_rest_query_url( "@get", uid=data["itemUID"], @@ -401,6 +404,10 @@ def _rest_getItemTemplate(self, data): extra_include="pod_templates", ) response = session.get(url) + if not data["templateId"]: + raise ValueError( + "Server raised fault: 'You can not access this template!'" + ) template_id, output_format = data["templateId"].split("__format__") # Iterate over possible templates to find the right one template = [t for t in response.json()["extra_include_pod_templates"] @@ -412,7 +419,7 @@ def _rest_getItemTemplate(self, data): if o["format"] == output_format] if not output: raise ValueError( - "Unknown output format '{0}' for template id '{1'".format( + "Unknown output format '{0}' for template id '{1}'".format( output_format, template_id ) ) @@ -499,9 +506,10 @@ def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): return # return 'UID' and 'warnings' if any current user is a Manager warnings = [] + response_json = response.json() if self.context.portal_membership.getAuthenticatedMember().has_role('Manager'): - warnings = 'warnings' in response.__keylist__ and response['warnings'] or [] - return response.json()['UID'], warnings + warnings = 'warnings' in response_json and response_json['warnings'] or [] + return response_json['UID'], warnings except Exception as exc: IStatusMessage(self.request).addStatusMessage( _(CONFIG_CREATE_ITEM_PM_ERROR, mapping={'error': exc}), "error" @@ -570,15 +578,15 @@ def checkAlreadySentToPloneMeeting(self, context, meetingConfigIds=[]): meetingConfigIds = list(annotations[WS4PMCLIENT_ANNOTATION_KEY]) for meetingConfigId in meetingConfigIds: res = self._rest_checkIsLinked({'externalIdentifier': context.UID(), - 'meetingConfigId': meetingConfigId, }) + 'config_id': meetingConfigId, }) # if res is None, it means that it could not connect to PloneMeeting if res is None: return None # we found at least one linked item - elif res is True: + elif res: isLinked = True # could connect to PM but did not find a result - elif res is False: + elif not res: # either the item was deleted in PloneMeeting # or it was never send, wipe out if it was deleted in PloneMeeting if meetingConfigId in annotations[WS4PMCLIENT_ANNOTATION_KEY]: From b4a1963669b1941599005fa0f9a96934c8a5f462 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 10:36:29 +0200 Subject: [PATCH 33/65] Adapt views tests --- src/imio/pm/wsclient/tests/testViews.py | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testViews.py b/src/imio/pm/wsclient/tests/testViews.py index a1579de..de2cc7e 100644 --- a/src/imio/pm/wsclient/tests/testViews.py +++ b/src/imio/pm/wsclient/tests/testViews.py @@ -38,8 +38,8 @@ def test_generateItemTemplateView(self): view = document.restrictedTraverse('@@generate_document_from_plonemeeting') # nothing is generated, just redirected to the context self.assertFalse(view() != DOCUMENT_ABSOLUTE_URL) - self.assertTrue(len(messages.show()) == 1) - self.assertTrue(messages.show()[0].message == UNABLE_TO_CONNECT_ERROR) + self.assertTrue(len(messages.show()) == 3) + self.assertTrue(messages.show()[-1].message == UNABLE_TO_CONNECT_ERROR) # _soap_connectToPloneMeeting is memoized... cleanMemoize(self.request) item = self._sendToPloneMeeting(document) @@ -65,7 +65,7 @@ def test_generateItemTemplateView(self): self.assertFalse(view() != DOCUMENT_ABSOLUTE_URL) self.assertEqual( messages.show()[-1].message, u"An error occured while generating the document in " - "PloneMeeting! The error message was : Server raised fault: 'You can not access this item!'" + "PloneMeeting! The error message was : Server raised fault: 'You can not access this item!'" ) # now with a valid itemUID but no valid templateId self.request.set('itemUID', item.UID()) @@ -74,7 +74,7 @@ def test_generateItemTemplateView(self): self.assertFalse(view() != DOCUMENT_ABSOLUTE_URL) self.assertEqual( messages.show()[-1].message, u"An error occured while generating the document in " - "PloneMeeting! The error message was : Server raised fault: 'You can not access this template!'" + "PloneMeeting! The error message was : Server raised fault: 'You can not access this template!'" ) # now with all valid infos self.request.set('templateId', POD_TEMPLATE_ID_PATTERN.format('itemTemplate', 'odt')) @@ -96,35 +96,35 @@ def test_DownloadAnnexFromItemView_without_annex_id(self): self.changeUser('admin') download_annex = self.portal.restrictedTraverse('@@download_annex_from_plonemeeting') # mock connexion to PloneMeeting - download_annex.ws4pmSettings._soap_connectToPloneMeeting = Mock(return_value=True) + download_annex.ws4pmSettings._rest_connectToPloneMeeting = Mock(return_value=True) download_annex() self.assertEqual(messages.show()[-1].message, ANNEXID_MANDATORY_ERROR) - @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._soap_getItemInfos') - def test_DownloadAnnexFromItemView_with_no_result(self, _soap_getItemInfos): + @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getItemInfos') + def test_DownloadAnnexFromItemView_with_no_result(self, _rest_getItemInfos): """ Test the BrowserView that return the annex of an item """ # return no annex - _soap_getItemInfos.return_value = None + _rest_getItemInfos.return_value = None messages = IStatusMessage(self.request) self.changeUser('admin') document = createDocument(self.portal) self.request.set('annex_id', 'my_annex') download_annex = document.restrictedTraverse('@@download_annex_from_plonemeeting') # mock connexion to PloneMeeting - download_annex.ws4pmSettings._soap_connectToPloneMeeting = Mock(return_value=True) + download_annex.ws4pmSettings._rest_connectToPloneMeeting = Mock(return_value=True) download_annex() self.assertEqual(messages.show()[-1].message, MISSING_FILE_ERROR) - @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._soap_getItemInfos') - def test_DownloadAnnexFromItemView(self, _soap_getItemInfos): + @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getItemInfos') + def test_DownloadAnnexFromItemView(self, _rest_getItemInfos): """ Test the BrowserView that return the annex of an item """ # return an annex annex_id = 'my_annex' - _soap_getItemInfos.return_value = [type( + _rest_getItemInfos.return_value = [type( 'ItemInfo', (object,), { 'annexes': [ type( @@ -142,7 +142,7 @@ def test_DownloadAnnexFromItemView(self, _soap_getItemInfos): self.request.set('annex_id', annex_id) download_annex = document.restrictedTraverse('@@download_annex_from_plonemeeting') # mock connexion to PloneMeeting - download_annex.ws4pmSettings._soap_connectToPloneMeeting = Mock(return_value=True) + download_annex.ws4pmSettings._rest_connectToPloneMeeting = Mock(return_value=True) annex = download_annex() self.assertEqual(annex, 'Hello!') self.assertEqual(self.request.RESPONSE.headers.get('content-type'), 'application/pdf') From a49d44ccfacd403ddf1d7c1b5faf8118924e4651 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 15:03:22 +0200 Subject: [PATCH 34/65] Fix settings tests --- src/imio/pm/wsclient/tests/testSettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testSettings.py b/src/imio/pm/wsclient/tests/testSettings.py index 949d4da..78655c4 100644 --- a/src/imio/pm/wsclient/tests/testSettings.py +++ b/src/imio/pm/wsclient/tests/testSettings.py @@ -7,7 +7,7 @@ from AccessControl import Unauthorized from imio.pm.wsclient.config import ACTION_SUFFIX -from imio.pm.wsclient.testing import WS4PMCLIENT_PROFILE_FUNCTIONAL +from imio.pm.wsclient.testing import WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import createDocument from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import setCorrectSettingsConfig from plone.app.testing import login @@ -27,7 +27,7 @@ class testSettings(unittest.TestCase): Tests the browser.settings SOAP client methods """ - layer = WS4PMCLIENT_PROFILE_FUNCTIONAL + layer = WS4PMCLIENT_PM_TESTING_PROFILE_FUNCTIONAL def setUp(self): portal = self.layer['portal'] From f0e3f65b8329c6f9329d38006151bde6276d6451 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 15:03:48 +0200 Subject: [PATCH 35/65] Adapt code to handle multiple extra_include arguments and fullobjects parameter --- src/imio/pm/wsclient/browser/settings.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 8dd0e54..a1339c7 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -260,7 +260,16 @@ def _rest_connectToPloneMeeting(self): def _format_rest_query_url(self, endpoint, **kwargs): """Return a rest query URL formatted for the given endpoint and arguments""" - arguments = ["{0}={1}".format(k, v) for k, v in kwargs.items() if v] + arguments = [] + for k, v in kwargs.items(): + if isinstance(v, six.string_types) and "," in v: + for v in v.split(","): + arguments.append("{0}={1}".format(k, v)) + else: + if v: + arguments.append("{0}={1}".format(k, v)) + elif k in ("fullobjects", ): + arguments.append(k) if arguments: return "{url}/{endpoint}?{arguments}".format( url=self.url, From 46f667e5b4fc0f0aca7bd124957cfc69d07fbcf0 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 15:05:02 +0200 Subject: [PATCH 36/65] Adapt code to be compatible with REST result --- src/imio/pm/wsclient/browser/viewlets.py | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/imio/pm/wsclient/browser/viewlets.py b/src/imio/pm/wsclient/browser/viewlets.py index 22ba679..802ef4c 100644 --- a/src/imio/pm/wsclient/browser/viewlets.py +++ b/src/imio/pm/wsclient/browser/viewlets.py @@ -94,18 +94,18 @@ def getPloneMeetingLinkedInfos(self): allowed_annexes_types = [line.values()[0] for line in settings.allowed_annexes_types] shownItemsMeetingConfigId = [] for item in items: - res.append(self.ws4pmSettings._rest_getItemInfos({'UID': item['UID'], - 'showExtraInfos': True, - 'showAnnexes': True, - 'allowed_annexes_types': allowed_annexes_types, - 'include_annex_binary': False, - 'showExtraInfos': True, - 'showTemplates': True})[0]) + res.append(self.ws4pmSettings._rest_getItemInfos( + { + 'UID': item['UID'], + 'extra_include': 'meeting,pod_templates,annexes,config', + 'extra_include_meeting_additional_values': '*', + } + )[0]) lastAddedItem = res[-1] - shownItemsMeetingConfigId.append(lastAddedItem['extraInfos']['meeting_config_id']) + shownItemsMeetingConfigId.append(lastAddedItem['extra_include_config']['id']) # XXX special case if something went wrong and there is an item in PM # that is not in the context sent_to annotation - lastAddedItemMeetingConfigId = str(lastAddedItem['extraInfos']['meeting_config_id']) + lastAddedItemMeetingConfigId = str(lastAddedItem['extra_include_config']['id']) if lastAddedItemMeetingConfigId not in sent_to: existingSentTo = list(sent_to) existingSentTo.append(lastAddedItemMeetingConfigId) @@ -125,13 +125,13 @@ def getPloneMeetingLinkedInfos(self): # in extraInfos so sort here under works correctly # in the linked viewlet template, we test if there is a 'UID' in the given infos, if not # it means that it is this special message - res.append({'extraInfos': {'meeting_config_id': sent, - 'meeting_config_title': meetingConfigVocab.getTerm(sent).title}}) + res.append({'extra_include_config': {'id': sent, + 'title': meetingConfigVocab.getTerm(sent).title}}) # sort res to comply with sent order, for example sent first to college then council def sortByMeetingConfigId(x, y): - return cmp(sent_to.index(x['extraInfos']['meeting_config_id']), - sent_to.index(y['extraInfos']['meeting_config_id'])) + return cmp(sent_to.index(x['extra_include_config']['id']), + sent_to.index(y['extra_include_config']['id'])) res.sort(sortByMeetingConfigId) return res From e730732a2b5aa5e347477b1e4695b02ca97259ef Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Tue, 25 Oct 2022 15:06:06 +0200 Subject: [PATCH 37/65] Remove test that was only relevant for soap --- src/imio/pm/wsclient/tests/testViewlets.py | 42 ---------------------- 1 file changed, 42 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testViewlets.py b/src/imio/pm/wsclient/tests/testViewlets.py index 8d1bef2..f7182fa 100644 --- a/src/imio/pm/wsclient/tests/testViewlets.py +++ b/src/imio/pm/wsclient/tests/testViewlets.py @@ -170,48 +170,6 @@ def test_displayMeetingDate(self): # If hours, then a long format is used self.assertTrue(viewlet.displayMeetingDate(datetime(2013, 6, 10, 15, 30)) == u'Jun 10, 2013 03:30 PM') - def test_displayedDateRespectTimeZone(self): - """ - This confirm a bug in suds 0.4 corrected in suds-jurko 0.4.1.jurko.4 - where returned date does not work when not using current time zone. - For example, a GMT+1 date is wrongly displayed when we are in GMT+2, it miss 1 hour, - so 2013/03/03 00:00 becomes 2013/03/02 23:00... - Returned dates should always be UTC. - """ - self.changeUser('admin') - document = createDocument(self.portal) - item = self._sendToPloneMeeting(document) - # create a meeting and present the item - self.changeUser('pmManager') - # use a date in GMT+1 - meeting = self.create('Meeting', date=datetime(2013, 3, 3)) - self.presentItem(item) - transaction.commit() - viewlet = PloneMeetingInfosViewlet(document, self.request, None, None) - viewlet.update() - meeting_date = viewlet.getPloneMeetingLinkedInfos()[0]['meeting_date'] - # date TZ naive, uses UTC - self.assertEqual(meeting_date.tzname(), 'UTC') - self.assertEquals((meeting_date.year, - meeting_date.month, - meeting_date.day, - meeting_date.hour, - meeting_date.minute), - (2013, 3, 2, 23, 0)) - # change meeting date, use a date in GMT+2 - meeting.date = datetime(2013, 8, 8) - transaction.commit() - viewlet = PloneMeetingInfosViewlet(document, self.request, None, None) - viewlet.update() - meeting_date = viewlet.getPloneMeetingLinkedInfos()[0]['meeting_date'] - self.assertEqual(meeting_date.tzname(), 'UTC') - self.assertEquals((meeting_date.year, - meeting_date.month, - meeting_date.day, - meeting_date.hour, - meeting_date.minute), - (2013, 8, 7, 22, 0)) - def test_suite(): from unittest import TestSuite, makeSuite From 6d6508fc601e7d8564a89206c534257a09b76723 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 12:37:58 +0200 Subject: [PATCH 38/65] Add `_rest_getAnnex` to return the content of an annex --- src/imio/pm/wsclient/browser/settings.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index a1339c7..dbb0207 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -377,6 +377,16 @@ def _rest_getItemInfos(self, data): return [response.json()] return [] + def _rest_getAnnex(self, url): + """Return an annex based on his download url. !!! WARNING !!! this must only + used inside code that validate before that the user can access the annex""" + session = self._rest_connectToPloneMeeting() + if session is not None: + response = session.get(url) + if response.status_code == 200: + return response.content + return '' + def _rest_getMeetingsAcceptingItems(self, data): """Query the getItemInfos REST server method.""" session = self._rest_connectToPloneMeeting() From e01b22804af57c6fdd8f4304bf3a7599b0c3b7a2 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 12:41:33 +0200 Subject: [PATCH 39/65] Fix annex download view --- src/imio/pm/wsclient/browser/views.py | 16 +++++----- src/imio/pm/wsclient/tests/testViews.py | 40 ++++++++++++++----------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/imio/pm/wsclient/browser/views.py b/src/imio/pm/wsclient/browser/views.py index 7ccfa75..7652bda 100644 --- a/src/imio/pm/wsclient/browser/views.py +++ b/src/imio/pm/wsclient/browser/views.py @@ -103,9 +103,8 @@ def __call__(self): res = self.ws4pmSettings._rest_getItemInfos( { 'UID': self.itemUID, - 'showAnnexes': True, - 'allowed_annexes_types': self.annex_type, - 'include_annex_binary': True, + 'extra_include': 'annexes', + 'allowed_annexes_types': self.annex_type, # XXX Must be implemented } ) if not res: @@ -113,11 +112,10 @@ def __call__(self): 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] + annex_info = [a for a in res["extra_include_annexes"] if a["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) + response.setHeader('Content-Type', annex_info["file"]["content-type"]) + response.setHeader('Content-Disposition', 'inline;filename="%s"' % annex_info["file"]["filename"]) + annex_content = self.ws4pmSettings._rest_getAnnex(annex_info["file"]["download"]) + return annex_content diff --git a/src/imio/pm/wsclient/tests/testViews.py b/src/imio/pm/wsclient/tests/testViews.py index de2cc7e..710c2ed 100644 --- a/src/imio/pm/wsclient/tests/testViews.py +++ b/src/imio/pm/wsclient/tests/testViews.py @@ -117,26 +117,32 @@ def test_DownloadAnnexFromItemView_with_no_result(self, _rest_getItemInfos): download_annex() self.assertEqual(messages.show()[-1].message, MISSING_FILE_ERROR) + @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getAnnex') @patch('imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getItemInfos') - def test_DownloadAnnexFromItemView(self, _rest_getItemInfos): + def test_DownloadAnnexFromItemView(self, _rest_getItemInfos, _rest_getAnnex): """ Test the BrowserView that return the annex of an item """ # return an annex - annex_id = 'my_annex' - _rest_getItemInfos.return_value = [type( - 'ItemInfo', (object,), { - 'annexes': [ - type( - 'AnnexInfo', (object,), { - 'id': annex_id, - 'filename': 'my_annex.pdf', - 'file': base64.b64encode('Hello!') - } - ) - ] - } - )] + annex_id = 'annexe.txt' + _rest_getItemInfos.return_value = { + 'extra_include_annexes': [ + { + "@id": u"http://nohost/plone/Members/pmCreator/mymeetings/o1/p1/annexe.txt", # noqa + "@type": u"annex", + "id": "annexe.txt", + "UID": u"400117a02c3e4d0aa45878d727ecd9e0", + "title": "Annexe", + "file": { + "content-type": "text/plain", + "download": u"http://nohost/plone/Members/pmCreator/mymeetings/o1/p1/annexe.txt", # noqa + "filename": "annexe.txt", + "size": 6, + } + }, + ], + } + _rest_getAnnex.return_value = 'Hello!' self.changeUser('admin') document = createDocument(self.portal) self.request.set('annex_id', annex_id) @@ -145,10 +151,10 @@ def test_DownloadAnnexFromItemView(self, _rest_getItemInfos): download_annex.ws4pmSettings._rest_connectToPloneMeeting = Mock(return_value=True) annex = download_annex() self.assertEqual(annex, 'Hello!') - self.assertEqual(self.request.RESPONSE.headers.get('content-type'), 'application/pdf') + self.assertEqual(self.request.RESPONSE.headers.get('content-type'), 'text/plain') self.assertEqual( self.request.RESPONSE.headers.get('content-disposition'), - 'inline;filename="my_annex.pdf"' + 'inline;filename="annexe.txt"' ) From 7a860c5f81284a1e2768891d9a88c3aae6db143d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 12:43:07 +0200 Subject: [PATCH 40/65] Fix render of meeting date --- src/imio/pm/wsclient/browser/viewlets.py | 5 +++++ src/imio/pm/wsclient/tests/testViewlets.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/imio/pm/wsclient/browser/viewlets.py b/src/imio/pm/wsclient/browser/viewlets.py index 802ef4c..31499b9 100644 --- a/src/imio/pm/wsclient/browser/viewlets.py +++ b/src/imio/pm/wsclient/browser/viewlets.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from dateutil import tz +from datetime import datetime from imio.pm.wsclient import WS4PMClientMessageFactory as _ from imio.pm.wsclient.config import CAN_NOT_SEE_LINKED_ITEMS_INFO from imio.pm.wsclient.config import UNABLE_TO_CONNECT_ERROR @@ -99,6 +100,7 @@ def getPloneMeetingLinkedInfos(self): 'UID': item['UID'], 'extra_include': 'meeting,pod_templates,annexes,config', 'extra_include_meeting_additional_values': '*', + 'fullobjects': None, } )[0]) lastAddedItem = res[-1] @@ -139,6 +141,9 @@ def displayMeetingDate(self, meeting_date): """Display a correct related meeting date : - if linked to a meeting, either '-' - manage displayed hours (hide hours if 00:00)""" + if meeting_date == 'whatever' or not meeting_date: + return '-' + meeting_date = datetime.strptime(meeting_date, "%Y-%m-%dT%H:%M:%S") if meeting_date.year == 1950: return '-' diff --git a/src/imio/pm/wsclient/tests/testViewlets.py b/src/imio/pm/wsclient/tests/testViewlets.py index f7182fa..392077a 100644 --- a/src/imio/pm/wsclient/tests/testViewlets.py +++ b/src/imio/pm/wsclient/tests/testViewlets.py @@ -155,7 +155,7 @@ def test_displayMeetingDate(self): viewlet = PloneMeetingInfosViewlet(self.portal, self.request, None, None) viewlet.update() # The no meeting date correspond to a date in 1950 - self.assertTrue(viewlet.displayMeetingDate(datetime(1950, 1, 1)) == '-') + self.assertTrue(viewlet.displayMeetingDate("1950-01-01T00:00:00") == '-') # If there is a date, it is corretly displayed # if no hours (hours = 00:00), a short format is used, without displaying hours # the sent date is UTC but displayMeetingDate will return it using local timezone @@ -166,9 +166,9 @@ def test_displayMeetingDate(self): utcMeetingDate = utcMeetingDate - delta # set utcMeetingDate as being UTC utcMeetingDate = utcMeetingDate.replace(tzinfo=tz.tzutc()) - self.assertTrue(viewlet.displayMeetingDate(datetime(2013, 6, 10)) == u'Jun 10, 2013') + self.assertTrue(viewlet.displayMeetingDate("2013-06-10T00:00:00") == u'Jun 10, 2013') # If hours, then a long format is used - self.assertTrue(viewlet.displayMeetingDate(datetime(2013, 6, 10, 15, 30)) == u'Jun 10, 2013 03:30 PM') + self.assertTrue(viewlet.displayMeetingDate("2013-06-10T15:30:00") == u'Jun 10, 2013 03:30 PM') def test_suite(): From 74a379618fb0a4c4d05db6e758451acc87d779c5 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 12:43:29 +0200 Subject: [PATCH 41/65] Adapt viewlet template for REST api --- .../browser/templates/plonemeeting_infos.pt | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt index c81c118..baab6c2 100644 --- a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt +++ b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt @@ -29,18 +29,20 @@ - Item title - Creator fullname - Meeting config title - Review state - Category title - Preferred meeting date - Related meeting date + Item title + + Creator fullname + Meeting config title + + Review state + Category title + Preferred meeting date + Related meeting date - + tal:repeat="annex python: getattr(item, 'extra_include_annexes', [])"> +   @@ -49,11 +51,13 @@ - - - -   + tal:repeat="podTemplate item/extra_include_pod_templates"> + + + + +   + From 9417a46f71048b9e6d2418b53a4ec13678732bdc Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 12:48:58 +0200 Subject: [PATCH 42/65] Add a test on viewlet render --- src/imio/pm/wsclient/tests/testViewlets.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/imio/pm/wsclient/tests/testViewlets.py b/src/imio/pm/wsclient/tests/testViewlets.py index 392077a..eae32a5 100644 --- a/src/imio/pm/wsclient/tests/testViewlets.py +++ b/src/imio/pm/wsclient/tests/testViewlets.py @@ -170,6 +170,19 @@ def test_displayMeetingDate(self): # If hours, then a long format is used self.assertTrue(viewlet.displayMeetingDate("2013-06-10T15:30:00") == u'Jun 10, 2013 03:30 PM') + def test_render(self): + """Ensure that we can render the viewlet without any error""" + self.changeUser('admin') + document = createDocument(self.portal) + viewlet = PloneMeetingInfosViewlet(document, self.request, None, None) + viewlet.update() + # now send an element to PloneMeeting and check again + cleanMemoize(self.request, viewlet) + item = self._sendToPloneMeeting(document) + render = viewlet.render() + self.assertTrue("Document title" in render) + self.assertTrue("PloneMeeting assembly" in render) + def test_suite(): from unittest import TestSuite, makeSuite From e8d5dbadafa53eadf4b7e108e566fd4dc789f4a4 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 15:57:12 +0200 Subject: [PATCH 43/65] Fix config informations vocabularies with REST API --- src/imio/pm/wsclient/browser/vocabularies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/browser/vocabularies.py b/src/imio/pm/wsclient/browser/vocabularies.py index f9d8605..5f641fe 100644 --- a/src/imio/pm/wsclient/browser/vocabularies.py +++ b/src/imio/pm/wsclient/browser/vocabularies.py @@ -40,7 +40,7 @@ def __call__(self, context=None): pmConfigInfos = settings._rest_getConfigInfos() terms = [] if pmConfigInfos: - for pmConfigInfo in pmConfigInfos.configInfo: + for pmConfigInfo in pmConfigInfos: terms.append(SimpleTerm(unicode(pmConfigInfo['id']), unicode(pmConfigInfo['id']), unicode(pmConfigInfo['title']),)) From 6f04ebdca9247124282059426c18b9e83a2f28eb Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 15:57:55 +0200 Subject: [PATCH 44/65] Fix typo in translations --- src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po b/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po index 9a01639..c730f0c 100644 --- a/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po +++ b/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po @@ -199,7 +199,7 @@ msgstr "Impossible de se connecter à iA.Delib! Contactez votre administrateur #. Default: "Unable to connect to PloneMeeting! The error message was : ${error}!" msgid "Unable to connect to PloneMeeting! The error message was : ${error}!" -msgstr "Impossible de se conneter à iA.Delib! Le message d'erreur est : ${error}!" +msgstr "Impossible de se connecter à iA.Delib! Le message d'erreur est : ${error}!" #. Default: "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." msgid "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." From 103f56564186650fa47545cb9c1a449be3a3e1ca Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 16:44:40 +0200 Subject: [PATCH 45/65] Fix encoding of extra field labels --- src/imio/pm/wsclient/browser/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index e5ef9c9..17633db 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -110,7 +110,7 @@ def getDisplayableData(self): res = ['
{0}{1}
'.format( translate('PloneMeeting_label_' + extraAttr['key'], domain="PloneMeeting", - context=self.request), + context=self.request).encode("utf-8"), extraAttr['value']) for extraAttr in data[elt]] data[elt] = '
'.join(res) From 5ad7a10afc54c6291b0c3ddea922744a5f493837 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 16:47:02 +0200 Subject: [PATCH 46/65] Fix data structures to match REST API results --- .../browser/templates/plonemeeting_infos.pt | 2 +- src/imio/pm/wsclient/browser/views.py | 2 +- src/imio/pm/wsclient/tests/testViews.py | 4 ++-- src/imio/pm/wsclient/tests/testVocabularies.py | 14 ++++---------- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt index baab6c2..bf52425 100644 --- a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt +++ b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt @@ -41,7 +41,7 @@ + tal:repeat="annex item/extra_include_annexes"> diff --git a/src/imio/pm/wsclient/browser/views.py b/src/imio/pm/wsclient/browser/views.py index 7652bda..461c7e1 100644 --- a/src/imio/pm/wsclient/browser/views.py +++ b/src/imio/pm/wsclient/browser/views.py @@ -112,7 +112,7 @@ def __call__(self): IStatusMessage(self.request).addStatusMessage(_(MISSING_FILE_ERROR), "error") return self.request.RESPONSE.redirect(self.context.absolute_url()) - annex_info = [a for a in res["extra_include_annexes"] if a["id"] == self.annex_id] + annex_info = [a for a in res[0]["extra_include_annexes"] if a["id"] == self.annex_id] if annex_info: annex_info = annex_info[0] response.setHeader('Content-Type', annex_info["file"]["content-type"]) diff --git a/src/imio/pm/wsclient/tests/testViews.py b/src/imio/pm/wsclient/tests/testViews.py index 710c2ed..a3d27bd 100644 --- a/src/imio/pm/wsclient/tests/testViews.py +++ b/src/imio/pm/wsclient/tests/testViews.py @@ -125,7 +125,7 @@ def test_DownloadAnnexFromItemView(self, _rest_getItemInfos, _rest_getAnnex): """ # return an annex annex_id = 'annexe.txt' - _rest_getItemInfos.return_value = { + _rest_getItemInfos.return_value = [{ 'extra_include_annexes': [ { "@id": u"http://nohost/plone/Members/pmCreator/mymeetings/o1/p1/annexe.txt", # noqa @@ -141,7 +141,7 @@ def test_DownloadAnnexFromItemView(self, _rest_getItemInfos, _rest_getAnnex): } }, ], - } + }] _rest_getAnnex.return_value = 'Hello!' self.changeUser('admin') document = createDocument(self.portal) diff --git a/src/imio/pm/wsclient/tests/testVocabularies.py b/src/imio/pm/wsclient/tests/testVocabularies.py index 23d794f..6d5a064 100644 --- a/src/imio/pm/wsclient/tests/testVocabularies.py +++ b/src/imio/pm/wsclient/tests/testVocabularies.py @@ -15,16 +15,10 @@ class testVocabularies(WS4PMCLIENTTestCase): @patch("imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getConfigInfos") def test_pm_meeting_config_id_vocabulary(self, _rest_getConfigInfos): """ """ - _rest_getConfigInfos.return_value = type( - "ConfigInfos", - (object,), - { - "configInfo": [ - {"id": u"plonegov-assembly", "title": u"PloneGov Assembly"}, - {"id": u"plonemeeting-assembly", "title": u"PloneMeeting Assembly"}, - ] - }, - )() + _rest_getConfigInfos.return_value = [ + {"id": u"plonegov-assembly", "title": u"PloneGov Assembly"}, + {"id": u"plonemeeting-assembly", "title": u"PloneMeeting Assembly"}, + ] raw_voc = vocabularies.pm_meeting_config_id_vocabularyFactory() self.assertEqual(len(raw_voc), 2) voc = [v for v in raw_voc] From 210e9091ed1aaef01a8e624a8fca2bc3421fcfd7 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 16:50:07 +0200 Subject: [PATCH 47/65] Update comment --- src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt index bf52425..b7af6ee 100644 --- a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt +++ b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt @@ -30,10 +30,9 @@ Item title - + Creator fullname Meeting config title - Review state Category title Preferred meeting date From 946b9ffb5140ebb4077bbc30245bcd581c535e07 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 26 Oct 2022 16:50:37 +0200 Subject: [PATCH 48/65] Use meeting title that include date and hour instead of meeting date --- .../browser/templates/plonemeeting_infos.pt | 4 ++-- src/imio/pm/wsclient/browser/viewlets.py | 24 +++---------------- src/imio/pm/wsclient/tests/testViewlets.py | 21 ++++++---------- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt index b7af6ee..cc3e587 100644 --- a/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt +++ b/src/imio/pm/wsclient/browser/templates/plonemeeting_infos.pt @@ -35,8 +35,8 @@ Meeting config title Review state Category title - Preferred meeting date - Related meeting date + Preferred meeting date + Related meeting date Date: Wed, 9 Nov 2022 11:18:03 +0100 Subject: [PATCH 49/65] Update the locales script to rebuild pot files --- src/imio/pm/wsclient/locales/sync_pos.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imio/pm/wsclient/locales/sync_pos.sh b/src/imio/pm/wsclient/locales/sync_pos.sh index 2b2f06b..b3b020e 100755 --- a/src/imio/pm/wsclient/locales/sync_pos.sh +++ b/src/imio/pm/wsclient/locales/sync_pos.sh @@ -1,6 +1,8 @@ files="imio.pm.wsclient PloneMeeting" languages="en fr" +i18ndude rebuild-pot --pot imio.pm.wsclient.pot --create imio.pm.wsclient ../ + for file in $files; do for language in $languages; do i18ndude sync --pot $file.pot $language/LC_MESSAGES/$file.po From 5dd1fc0e4d79f9bfc40d5ab866e36a881bf68e97 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 9 Nov 2022 11:19:22 +0100 Subject: [PATCH 50/65] Update translations files --- src/imio/pm/wsclient/browser/forms.py | 2 +- src/imio/pm/wsclient/browser/settings.py | 2 +- src/imio/pm/wsclient/config.py | 80 +++-- .../en/LC_MESSAGES/imio.pm.wsclient.po | 153 +++++++++- .../fr/LC_MESSAGES/imio.pm.wsclient.po | 153 +++++++++- .../pm/wsclient/locales/imio.pm.wsclient.pot | 288 ++++++++++++------ 6 files changed, 514 insertions(+), 164 deletions(-) diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index 17633db..9e2afa4 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -149,7 +149,7 @@ def __init__(self, context, request): self.portal = self.portal_state.portal() self.ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') # manage the label to display to wich meetingConfig we are sending the element... - self.label = translate('Send to', + self.label = translate(_('Send to'), domain='imio.pm.wsclient', mapping={'meetingConfigTitle': self.ws4pmSettings.getMeetingConfigTitle(self.meetingConfigId), diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index dbb0207..c86e1e6 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -452,7 +452,7 @@ def _rest_getItemTemplate(self, data): @memoize def _rest_getItemCreationAvailableData(self): - """Query REST WSDL to obtain the list of available fields useable while creating an item.""" + """Query REST to obtain the list of available fields useable while creating an item.""" session = self._rest_connectToPloneMeeting() if session is not None: available_data = [ diff --git a/src/imio/pm/wsclient/config.py b/src/imio/pm/wsclient/config.py index c2c07f2..5ba8260 100644 --- a/src/imio/pm/wsclient/config.py +++ b/src/imio/pm/wsclient/config.py @@ -1,45 +1,63 @@ +from imio.pm.wsclient import WS4PMClientMessageFactory as _ + # suffix used when adding our actions to portal_actions ACTION_SUFFIX = 'plonemeeting_wsclient_action_' # taken from imio.pm.ws -DEFAULT_NO_WARNING_MESSAGE = 'There was NO WARNING message during item creation.' +DEFAULT_NO_WARNING_MESSAGE = _('There was NO WARNING message during item creation.') # messages -CAN_NOT_SEE_LINKED_ITEMS_INFO = u"This element is linked to item(s) in PloneMeeting but your are not allowed to see it." -CORRECTLY_SENT_TO_PM_INFO = u"The item has been correctly sent to PloneMeeting." -CONFIG_UNABLE_TO_CONNECT_ERROR = u"Unable to connect to PloneMeeting! The error message was : ${error}!" -UNABLE_TO_CONNECT_ERROR = u"Unable to connect to PloneMeeting! Please contact system administrator!" -ALREADY_SENT_TO_PM_ERROR = u"This element has already been sent to PloneMeeting!" -TAL_EVAL_FIELD_ERROR = u"There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! " \ - "The error was : ${error}. Please contact system administrator." -UNABLE_TO_DETECT_MIMETYPE_ERROR = u"Could not detect correct mimetype for item template! "\ - "Please contact system administrator!" -FILENAME_MANDATORY_ERROR = u"A filename is mandatory while generating a document! "\ - "Please contact system administrator!" -UNABLE_TO_DISPLAY_VIEWLET_ERROR = u"Unable to display informations about the potentially linked item " \ - "in PloneMeeting because there was an error evaluating the TAL expression '${expr}' " \ +CAN_NOT_SEE_LINKED_ITEMS_INFO = _(u"This element is linked to item(s) in PloneMeeting but your are not allowed to see it.") +CORRECTLY_SENT_TO_PM_INFO = _(u"The item has been correctly sent to PloneMeeting.") +CONFIG_UNABLE_TO_CONNECT_ERROR = _(u"Unable to connect to PloneMeeting! The error message was : ${error}!") +UNABLE_TO_CONNECT_ERROR = _(u"Unable to connect to PloneMeeting! Please contact system administrator!") +ALREADY_SENT_TO_PM_ERROR = _(u"This element has already been sent to PloneMeeting!") +TAL_EVAL_FIELD_ERROR = _( + u"There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! " + "The error was : ${error}. Please contact system administrator." +) +UNABLE_TO_DETECT_MIMETYPE_ERROR = _( + u"Could not detect correct mimetype for item template! " + "Please contact system administrator!" +) +FILENAME_MANDATORY_ERROR = _( + u"A filename is mandatory while generating a document! " + "Please contact system administrator!" +) +UNABLE_TO_DISPLAY_VIEWLET_ERROR = _( + u"Unable to display informations about the potentially linked item " + "in PloneMeeting because there was an error evaluating the TAL expression '${expr}' " "for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." -CONFIG_CREATE_ITEM_PM_ERROR = u"An error occured during the item creation in PloneMeeting! " \ - "The error message was : ${error}" -NO_PROPOSING_GROUP_ERROR = ( +) +CONFIG_CREATE_ITEM_PM_ERROR = _( + u"An error occured during the item creation in PloneMeeting! " + "The error message was : ${error}" +) +NO_PROPOSING_GROUP_ERROR = _( u"An error occured during the item creation in PloneMeeting! The error message was " u": [{'field': 'proposingGroup', 'message': u'Proposing group is not available for " u"user.', 'error': 'ValidationError'}]" ) -NO_USER_INFOS_ERROR = u"Could not get userInfos in PloneMeeting for user '${userId}'!" -NO_FIELD_MAPPINGS_ERROR = u"No field_mappings defined in the WS4PMClient configuration!" -CAN_NOT_CREATE_FOR_PROPOSING_GROUP_ERROR = u"The current user can not create an item with the proposingGroup forced " \ - "thru the configuration! Please contact system administrator!" -NO_CONFIG_INFOS_ERROR = u"No configuration informations found!" -CAN_NOT_CREATE_WITH_CATEGORY_ERROR = u"The current user can not create an item with the category forced " \ - "thru the configuration! Please contact system administrator!" -SEND_WITHOUT_SUFFICIENT_FIELD_MAPPINGS_DEFINED_WARNING = u"No sufficient field mappings are defined in the " \ - "configuration. It is recommended to define at least the " \ - "'title' mapping, but 'description' and 'decision' should " \ - "also be defined. It will ne be possible to create the " \ - "item in PloneMeeting." -ANNEXID_MANDATORY_ERROR = u"An annex id is mandatory to download an annex!" -MISSING_FILE_ERROR = u"The requested file could not be found on the item" +NO_USER_INFOS_ERROR = _(u"Could not get userInfos in PloneMeeting for user '${userId}'!") +NO_FIELD_MAPPINGS_ERROR = _(u"No field_mappings defined in the WS4PMClient configuration!") +CAN_NOT_CREATE_FOR_PROPOSING_GROUP_ERROR = _( + u"The current user can not create an item with the proposingGroup forced " + "thru the configuration! Please contact system administrator!" +) +NO_CONFIG_INFOS_ERROR = _(u"No configuration informations found!") +CAN_NOT_CREATE_WITH_CATEGORY_ERROR = _( + u"The current user can not create an item with the category forced " + "thru the configuration! Please contact system administrator!" +) +SEND_WITHOUT_SUFFICIENT_FIELD_MAPPINGS_DEFINED_WARNING = _( + u"No sufficient field mappings are defined in the " + "configuration. It is recommended to define at least the " + "'title' mapping, but 'description' and 'decision' should " + "also be defined. It will ne be possible to create the " + "item in PloneMeeting." +) +ANNEXID_MANDATORY_ERROR = _(u"An annex id is mandatory to download an annex!") +MISSING_FILE_ERROR = _(u"The requested file could not be found on the item") # annotations key WS4PMCLIENT_ANNOTATION_KEY = "imio.pm.wsclient-sent_to" diff --git a/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po b/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po index 32710ab..98fd7fa 100644 --- a/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po +++ b/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-02-12 13:15-100\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI +ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,212 +19,335 @@ msgstr "" "X-is-fallback-for: fr-be fr-ca fr-lu fr-mc fr-ch fr-fr\n" #. Default: "A filename is mandatory while generating a document! Please contact system administrator!" +#: ../config.py:23 msgid "A filename is mandatory while generating a document! Please contact system administrator!" msgstr "A filename is mandatory while generating a document! Please contact system administrator!" +#: ../browser/settings.py:149 +msgid "Actions" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:146 msgid "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." msgstr "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." +#: ../browser/settings.py:128 +msgid "Allowed annex type" +msgstr "" + +#: ../browser/settings.py:126 +msgid "Allowed annexes types" +msgstr "" + +#: ../config.py:59 +msgid "An annex id is mandatory to download an annex!" +msgstr "" + #. Default: "An element can be sent one time only" +#: ../browser/settings.py:102 msgid "An element can be sent one time only" msgstr "" #. Default: "An error occured during the item creation in PloneMeeting! The error message was : ${error}" +#: ../config.py:32 msgid "An error occured during the item creation in PloneMeeting! The error message was : ${error}" msgstr "An error occured during the item creation in PloneMeeting! The error message was : ${error}" +#: ../config.py:36 +msgid "An error occured during the item creation in PloneMeeting! The error message was : [{'field': 'proposingGroup', 'message': u'Proposing group is not available for user.', 'error': 'ValidationError'}]" +msgstr "" + +#: ../browser/settings.py:450 +msgid "An error occured while generating the document in PloneMeeting! The error message was : %s" +msgstr "" + +#: ../browser/viewlets.py:81 +msgid "An error occured while searching for linked items in PloneMeeting! The error message was : %s" +msgstr "" + +#: ../browser/settings.py:65 +msgid "Annex type" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:135 msgid "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." msgstr "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." #. Default: "Cancel" +#: ../browser/forms.py:169 +#: ../browser/settings.py:205 msgid "Cancel" msgstr "Cancel" +#: ../browser/settings.py:201 +msgid "Changes saved" +msgstr "" + #. Default: "Could not detect correct mimetype for item template! Please contact system administrator!" +#: ../config.py:19 msgid "Could not detect correct mimetype for item template! Please contact system administrator!" msgstr "Could not detect correct mimetype for item template! Please contact system administrator!" #. Default: "Could not get userInfos in PloneMeeting for user '${userId}'!" +#: ../config.py:41 msgid "Could not get userInfos in PloneMeeting for user '${userId}'!" msgstr "Could not get userInfos in PloneMeeting for user '${userId}'!" +#: ../browser/settings.py:207 +msgid "Edit cancelled" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:107 msgid "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." msgstr "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." #. Default: "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." +#: ../browser/settings.py:88 msgid "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." msgstr "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: "Field accessor mappings" +#: ../browser/settings.py:114 msgid "Field accessor mappings" msgstr "Field accessor mappings" +#: ../browser/settings.py:121 +msgid "Field mappings" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:115 msgid "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." msgstr "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." #. Default: "Generated actions" +#: ../browser/settings.py:145 msgid "Generated actions" msgstr "Generated actions" +#: ../browser/settings.py:127 +msgid "List here the annexes types allowed to be display in the linked meeting item viewlet" +msgstr "" + #. Default: "Local user id" +#: ../browser/settings.py:72 msgid "Local user id" msgstr "Local user id" +#: ../browser/forms.py:59 +msgid "Meeting config id" +msgstr "" + #. Default: "No configuration informations found!" +#: ../config.py:47 msgid "No configuration informations found!" msgstr "No configuration informations found!" #. Default: "No field_mappings defined in the WS4PMClient configuration!" +#: ../config.py:42 msgid "No field_mappings defined in the WS4PMClient configuration!" msgstr "No field_mappings defined in the WS4PMClient configuration!" #. Default: "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." +#: ../config.py:52 msgid "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." msgstr "" -#. Default: "No user informations found for current user or the current user is not a creator in PloneMeeting!" -msgid "No user informations found for current user or the current user is not a creator in PloneMeeting!" -msgstr "No user informations found for current user or the current user is not a creator in PloneMeeting!" +#: ../browser/settings.py:42 +msgid "Permissions" +msgstr "" -#. Default: "PloneMeeting WSDL URL" -msgid "PloneMeeting WSDL URL" -msgstr "PloneMeeting WSDL URL" +#: ../browser/settings.py:84 +msgid "PloneMeeting URL" +msgstr "" #. Default: "PloneMeeting connection timeout" +#: ../browser/settings.py:87 msgid "PloneMeeting connection timeout" msgstr "PloneMeeting connection timeout" #. Default: "PloneMeeting corresponding user id" +#: ../browser/settings.py:75 msgid "PloneMeeting corresponding user id" msgstr "PloneMeeting corresponding user id" #. Default: "PloneMeeting field name" +#: ../browser/settings.py:54 msgid "PloneMeeting field name" msgstr "PloneMeeting field name" #. Default: "PloneMeeting informations" +#: ../browser/templates/plonemeeting_infos.pt:9 msgid "PloneMeeting informations" msgstr "PloneMeeting informations" #. Default: "PloneMeeting meetingConfig id" +#: ../browser/settings.py:46 msgid "PloneMeeting meetingConfig id" msgstr "PloneMeeting meetingConfig id" #. Default: "PloneMeeting password to use" +#: ../browser/settings.py:99 msgid "PloneMeeting password to use" msgstr "PloneMeeting password to use" #. Default: "PloneMeeting username to use" +#: ../browser/settings.py:94 msgid "PloneMeeting username to use" msgstr "PloneMeeting username to use" +#: ../configure.zcml:29 +msgid "Registration of the Webservices Client for PloneMeeting" +msgstr "" + +#: ../testing.zcml:15 +msgid "Registration of the Webservices Client for PloneMeeting for the tests" +msgstr "" + #. Default: "Save" +#: ../browser/settings.py:193 msgid "Save" msgstr "" #. Default: "Select the proposing group to use for the created item in PloneMeeting" +#: ../browser/forms.py:75 msgid "Select the annexes to send" msgstr "" #. Default: "Select the category to use for the created item item in PloneMeeting" +#: ../browser/forms.py:66 msgid "Select the category to use for the created item item in PloneMeeting" msgstr "Select the category to use for the created item item in PloneMeeting" +#: ../browser/forms.py:70 msgid "Select the desired meeting date for the created item in PloneMeeting" msgstr "" #. Default: "Select the proposing group to use for the created item in PloneMeeting" +#: ../browser/forms.py:61 msgid "Select the proposing group to use for the created item in PloneMeeting" msgstr "Select the proposing group to use for the created item in PloneMeeting" #. Default: "Send" +#: ../browser/forms.py:159 msgid "Send" msgstr "Send" #. Default: "Send to ${meetingConfigTitle}" +#: ../browser/forms.py:152 msgid "Send to" msgstr "Send to ${meetingConfigTitle}" #. Default: "Send to PloneMeeting" +#: ../browser/forms.py:138 msgid "Send to PloneMeeting" msgstr "Send to PloneMeeting" #. Default: "TAL Condition" +#: ../browser/settings.py:39 msgid "TAL Condition" msgstr "TAL Condition" #. Default: "TAL expression to evaluate for the corresponding PloneMeeting field name" +#: ../browser/settings.py:58 msgid "TAL expression to evaluate for the corresponding PloneMeeting field name" msgstr "TAL expression to evaluate for the corresponding PloneMeeting field name" -#. Default: "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" -msgid "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" -msgstr "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" - #. Default: "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" +#: ../config.py:48 msgid "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" msgstr "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" #. Default: "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" +#: ../config.py:43 msgid "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" msgstr "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" #. Default: "The item has been correctly sent to PloneMeeting." +#: ../config.py:11 msgid "The item has been correctly sent to PloneMeeting." msgstr "The item has been correctly sent to PloneMeeting." +#: ../config.py:60 +msgid "The requested file could not be found on the item" +msgstr "" + #. Default: "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." +#: ../browser/settings.py:95 msgid "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." msgstr "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." +#: ../config.py:7 +msgid "There was NO WARNING message during item creation." +msgstr "" + #. Default: "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." +#: ../config.py:15 msgid "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." msgstr "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." #. Default: "This element has already been sent to PloneMeeting!" +#: ../config.py:14 msgid "This element has already been sent to PloneMeeting!" msgstr "This element has already been sent to PloneMeeting!" #. Default: "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." +#: ../config.py:10 msgid "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." msgstr "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." #. Default: "Unable to connect to PloneMeeting! Please contact system administrator!" +#: ../config.py:13 msgid "Unable to connect to PloneMeeting! Please contact system administrator!" msgstr "Unable to connect to PloneMeeting! Please contact system administrator!" #. Default: "Unable to connect to PloneMeeting! The error message was : ${error}!" +#: ../config.py:12 msgid "Unable to connect to PloneMeeting! The error message was : ${error}!" msgstr "Unable to connect to PloneMeeting! The error message was : ${error}!" #. Default: "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." +#: ../config.py:27 msgid "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." msgstr "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." #. Default: "User ids mappings" +#: ../browser/settings.py:134 msgid "User ids mappings" msgstr "User ids mappings" +#: ../browser/settings.py:140 +msgid "User mappings" +msgstr "" + #. Default: "Viewlet display condition" +#: ../browser/settings.py:106 msgid "Viewlet display condition" msgstr "Viewlet display condition" #. Default: "WS4PM Client settings" +#: ../browser/settings.py:160 +#: ../profiles/default/controlpanel.xml msgid "WS4PM Client settings" msgstr "WS4PM Client settings" +#: ../configure.zcml:29 +msgid "Webservices Client for PloneMeeting" +msgstr "" + +#: ../testing.zcml:15 +msgid "Webservices Client for PloneMeeting testing profile" +msgstr "" + #. Default: "You see these informations because the current element has been sent to PloneMeeting." +#: ../browser/templates/plonemeeting_infos.pt:10 msgid "pm_informations_help" msgstr "You see these informations because the current element has been sent to PloneMeeting." #. Default: "Here is a resume of what will be sent to PloneMeeting" +#: ../browser/templates/display_data_to_send.pt:2 msgid "send_to_plonemeeting_descr" msgstr "Here is a resume of what will be sent to PloneMeeting" - -#. Default: "Please specify the proposingGroup to use" -msgid "send_to_plonemeeting_specify_proposing_group" -msgstr "Please specify the proposingGroup to use" diff --git a/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po b/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po index c730f0c..7c78a3a 100644 --- a/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po +++ b/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-02-12 13:15-100\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI +ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,212 +19,335 @@ msgstr "" "X-is-fallback-for: fr-be fr-ca fr-lu fr-mc fr-ch fr-fr\n" #. Default: "A filename is mandatory while generating a document! Please contact system administrator!" +#: ../config.py:23 msgid "A filename is mandatory while generating a document! Please contact system administrator!" msgstr "Un nom de fichier est obligatoire pour générer un document! Contactez votre administrateur sytème!" +#: ../browser/settings.py:149 +msgid "Actions" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:146 msgid "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." msgstr "Des actions pour faciliter l'envoi d'un point vers iA.Delib peuvent être générées. Elle apparaîtront par défaut dans la liste déroulante 'Actions'. Vous pouvez définir une 'Expression au format TAL' et choisir une 'Permission' pour protéger l'action. Pour finir, définissez dans quelle configuration de séance le point correspondant sera créé." +#: ../browser/settings.py:128 +msgid "Allowed annex type" +msgstr "" + +#: ../browser/settings.py:126 +msgid "Allowed annexes types" +msgstr "" + +#: ../config.py:59 +msgid "An annex id is mandatory to download an annex!" +msgstr "" + #. Default: "An element can be sent one time only" +#: ../browser/settings.py:102 msgid "An element can be sent one time only" msgstr "Un élément ne peut être envoyé qu'une seule fois" #. Default: "An error occured during the item creation in PloneMeeting! The error message was : ${error}" +#: ../config.py:32 msgid "An error occured during the item creation in PloneMeeting! The error message was : ${error}" msgstr "Une erreur est survenue lors de l'envoi vers iA.Delib! L'erreur est : ${error}" +#: ../config.py:36 +msgid "An error occured during the item creation in PloneMeeting! The error message was : [{'field': 'proposingGroup', 'message': u'Proposing group is not available for user.', 'error': 'ValidationError'}]" +msgstr "" + +#: ../browser/settings.py:450 +msgid "An error occured while generating the document in PloneMeeting! The error message was : %s" +msgstr "" + +#: ../browser/viewlets.py:81 +msgid "An error occured while searching for linked items in PloneMeeting! The error message was : %s" +msgstr "" + +#: ../browser/settings.py:65 +msgid "Annex type" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:135 msgid "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." msgstr "Par défaut lors de l'envoi d'un point vers iA.Delib, le nom d'utilisateur de l'utilisateur couramment connecté est utilisé. Si le nom d'utilisteur local est différent du nom d'utilisateur dans iA.Delib, vous pouvez les définir ci-dessous. Par exemple 'jdoe' dans l'application courante a un utilisateur correspondant 'johndoe' dans iA.Delib." #. Default: "Cancel" +#: ../browser/forms.py:169 +#: ../browser/settings.py:205 msgid "Cancel" msgstr "Annuler" +#: ../browser/settings.py:201 +msgid "Changes saved" +msgstr "" + #. Default: "Could not detect correct mimetype for item template! Please contact system administrator!" +#: ../config.py:19 msgid "Could not detect correct mimetype for item template! Please contact system administrator!" msgstr "Impossible de détecter le type MIME pour le document à générer! Contactez votre administrateur système!" #. Default: "Could not get userInfos in PloneMeeting for user '${userId}'!" +#: ../config.py:41 msgid "Could not get userInfos in PloneMeeting for user '${userId}'!" msgstr "Impossible d'obtenir des informations sur l'utilisateur '${userId}' dans iA.Delib!" +#: ../browser/settings.py:207 +msgid "Edit cancelled" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:107 msgid "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." msgstr "Entrez une expression au format TAL qui sera évaluée pour vérifier si le viewlet affichant les informations dans iA.Delib doit être montré ou non. Par défaut, si aucune expression n'est entrée ci-dessous, le viewlet sera montré s'il y a effectivement un point correspondant dans iA.Delib. La variable 'isLinked' est disponible pour l'expression TAL." #. Default: "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." +#: ../browser/settings.py:88 msgid "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." msgstr "Entrez le timeout de connexion à iA.Delib. Attention, ne pas mettre une valeurs trop importe car si iA.Delib n'est pas disponible, cela influera sur la vitesse d'affichage d'une page dans laquelle le viewlet affichant les infos iA.Delib est montré." #. Default: "Field accessor mappings" +#: ../browser/settings.py:114 msgid "Field accessor mappings" msgstr "Correspondances pour les champs" +#: ../browser/settings.py:121 +msgid "Field mappings" +msgstr "" + #. Default: "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." +#: ../browser/settings.py:115 msgid "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." msgstr "Pour chaque données à envoyer, définissez une expression TAL qui fait correspondre à l'élément à envoyer, la valeur à utiliser pour un champ dans iA.Delib. Les variables 'meetingConfigId' et 'proposingGroupId' sont disponibles pour l'expression TAL. Pour les champs 'proposingGroup' et 'category', si vous définissez une valeur ici, cela 'forcera' l'utilisation de ces valeurs. Sinon l'utilisateur pourra utiluser les 'groupes proposants' et 'catégories' qu'il peut habituellement utiliser dans iA.Delib." #. Default: "Generated actions" +#: ../browser/settings.py:145 msgid "Generated actions" msgstr "Actions générées" +#: ../browser/settings.py:127 +msgid "List here the annexes types allowed to be display in the linked meeting item viewlet" +msgstr "" + #. Default: "Local user id" +#: ../browser/settings.py:72 msgid "Local user id" msgstr "Nom d'utilisateur local" +#: ../browser/forms.py:59 +msgid "Meeting config id" +msgstr "" + #. Default: "No configuration informations found!" +#: ../config.py:47 msgid "No configuration informations found!" msgstr "Aucune information de configuration n'a été trouvée dans iA.Delib!" #. Default: "No field_mappings defined in the WS4PMClient configuration!" +#: ../config.py:42 msgid "No field_mappings defined in the WS4PMClient configuration!" msgstr "Aucune correspondance de champs définie dans la configuration des webservices iA.Delib!" #. Default: "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." +#: ../config.py:52 msgid "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." msgstr "Les 'Correspondances pour les champs' définies dans la configuration ne sont pas suffisantes. Le champ 'title' doit être défini et idéalement, les champs 'description' et 'decision' également. Le point ne pourra pas être créé." -#. Default: "No user informations found for current user or the current user is not a creator in PloneMeeting!" -msgid "No user informations found for current user or the current user is not a creator in PloneMeeting!" -msgstr "Pas d'informations trouvées pour l'utilisateur courant ou l'utilisateur courant n'est pas un créateur dans iA.Delib!" +#: ../browser/settings.py:42 +msgid "Permissions" +msgstr "" -#. Default: "PloneMeeting WSDL URL" -msgid "PloneMeeting WSDL URL" -msgstr "URL du fichier WSDL iA.Delib" +#: ../browser/settings.py:84 +msgid "PloneMeeting URL" +msgstr "" #. Default: "PloneMeeting connection timeout" +#: ../browser/settings.py:87 msgid "PloneMeeting connection timeout" msgstr "Timeout de connexion à iA.Delib" #. Default: "PloneMeeting corresponding user id" +#: ../browser/settings.py:75 msgid "PloneMeeting corresponding user id" msgstr "Nom d'utilisateur correspondant dans iA.Delib" #. Default: "PloneMeeting field name" +#: ../browser/settings.py:54 msgid "PloneMeeting field name" msgstr "Nom du champ dans iA.Delib" #. Default: "PloneMeeting informations" +#: ../browser/templates/plonemeeting_infos.pt:9 msgid "PloneMeeting informations" msgstr "Informations provenant de iA.Delib" #. Default: "PloneMeeting meetingConfig id" +#: ../browser/settings.py:46 msgid "PloneMeeting meetingConfig id" msgstr "Identifiant de configuration de séance dans iA.Delib" #. Default: "PloneMeeting password to use" +#: ../browser/settings.py:99 msgid "PloneMeeting password to use" msgstr "Mot de passe à utiliser dans iA.Delib" #. Default: "PloneMeeting username to use" +#: ../browser/settings.py:94 msgid "PloneMeeting username to use" msgstr "Nom d'utilisateur à utiliser dans iA.Delib" +#: ../configure.zcml:29 +msgid "Registration of the Webservices Client for PloneMeeting" +msgstr "" + +#: ../testing.zcml:15 +msgid "Registration of the Webservices Client for PloneMeeting for the tests" +msgstr "" + #. Default: "Save" +#: ../browser/settings.py:193 msgid "Save" msgstr "Enregistrer" #. Default: "Select the proposing group to use for the created item in PloneMeeting" +#: ../browser/forms.py:75 msgid "Select the annexes to send" msgstr "Sélectionnez les annexes à envoyer" #. Default: "Select the category to use for the created item item in PloneMeeting" +#: ../browser/forms.py:66 msgid "Select the category to use for the created item item in PloneMeeting" msgstr "Sélectionnez la catégorie avec laquelle le point sera créé dans iA.Delib" +#: ../browser/forms.py:70 msgid "Select the desired meeting date for the created item in PloneMeeting" msgstr "Sélectionnez la date de séance souhaitée" #. Default: "Select the proposing group to use for the created item in PloneMeeting" +#: ../browser/forms.py:61 msgid "Select the proposing group to use for the created item in PloneMeeting" msgstr "Sélectionnez le groupe proposant avec lequel le point sera créé dans iA.Delib" #. Default: "Send" +#: ../browser/forms.py:159 msgid "Send" msgstr "Envoyer" #. Default: "Send to ${meetingConfigTitle}" +#: ../browser/forms.py:152 msgid "Send to" msgstr "Envoyer vers ${meetingConfigTitle}" #. Default: "Send to PloneMeeting" +#: ../browser/forms.py:138 msgid "Send to PloneMeeting" msgstr "Envoyer vers iA.Delib" #. Default: "TAL Condition" +#: ../browser/settings.py:39 msgid "TAL Condition" msgstr "Condition au format TAL" #. Default: "TAL expression to evaluate for the corresponding PloneMeeting field name" +#: ../browser/settings.py:58 msgid "TAL expression to evaluate for the corresponding PloneMeeting field name" msgstr "Expression TAL à évaluer pour le champ iA.Delib correspondant" -#. Default: "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" -msgid "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" -msgstr "La configuration specifie que l'utilisateur '${userId}' va créer le point dans iA.Delib mais cet utilisateur ne peut pas créer de points dans iA.Delib!" - #. Default: "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" +#: ../config.py:48 msgid "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" msgstr "L'utilisateur courant ne peut créer de point avec la catégorie forcée dans la configuration! Contactez votre administrateur système!" #. Default: "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" +#: ../config.py:43 msgid "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" msgstr "L'utilisateur courant ne peut créer de point pour le groupe proposant forcé par la configuration! Contactez votre administrateur système!" #. Default: "The item has been correctly sent to PloneMeeting." +#: ../config.py:11 msgid "The item has been correctly sent to PloneMeeting." msgstr "L'élément a été correctement envoyé vers iA.Delib." +#: ../config.py:60 +msgid "The requested file could not be found on the item" +msgstr "" + #. Default: "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." +#: ../browser/settings.py:95 msgid "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." msgstr "L'utilisateur renseigné doit être au moins 'MeetingManager'. Néanmoins, les points créés dans iA.Delib le seront en fonction de ce qui est défini dans le champ 'Correspondance pour les utilisateurs' ci-dessous." +#: ../config.py:7 +msgid "There was NO WARNING message during item creation." +msgstr "" + #. Default: "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." +#: ../config.py:15 msgid "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." msgstr "Une erreur s'est produite lors de l'évaluation de l'expression TAL '${expr} pour le champ '${field_name}'! L'erreur est : ${error}. Contactez votre administrateur système." #. Default: "This element has already been sent to PloneMeeting!" +#: ../config.py:14 msgid "This element has already been sent to PloneMeeting!" msgstr "Cet élément a déjà été envoyé vers iA.Delib!" #. Default: "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." +#: ../config.py:10 msgid "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." msgstr "Cet élément est lié à un(des) point(s) dans iA.Delib mais vous n'avez pas l'autorisation des le(s) voir." #. Default: "Unable to connect to PloneMeeting! Please contact system administrator!" +#: ../config.py:13 msgid "Unable to connect to PloneMeeting! Please contact system administrator!" msgstr "Impossible de se connecter à iA.Delib! Contactez votre administrateur système!" #. Default: "Unable to connect to PloneMeeting! The error message was : ${error}!" +#: ../config.py:12 msgid "Unable to connect to PloneMeeting! The error message was : ${error}!" msgstr "Impossible de se connecter à iA.Delib! Le message d'erreur est : ${error}!" #. Default: "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." +#: ../config.py:27 msgid "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." msgstr "Impossible de montrer le viewlet affichant les informations liées dans PloneMeeting car il y a une erreur dans l'expression TAL liée à la condition d'affichage du viewlet. Contactez votre administrateur système!" #. Default: "User ids mappings" +#: ../browser/settings.py:134 msgid "User ids mappings" msgstr "Correspondances pour les nom d'utilisateurs" +#: ../browser/settings.py:140 +msgid "User mappings" +msgstr "" + #. Default: "Viewlet display condition" +#: ../browser/settings.py:106 msgid "Viewlet display condition" msgstr "Condition d'affichage du viewlet" #. Default: "WS4PM Client settings" +#: ../browser/settings.py:160 +#: ../profiles/default/controlpanel.xml msgid "WS4PM Client settings" msgstr "Client webservices iA.Delib" +#: ../configure.zcml:29 +msgid "Webservices Client for PloneMeeting" +msgstr "" + +#: ../testing.zcml:15 +msgid "Webservices Client for PloneMeeting testing profile" +msgstr "" + #. Default: "You see these informations because the current element has been sent to PloneMeeting." +#: ../browser/templates/plonemeeting_infos.pt:10 msgid "pm_informations_help" msgstr "Vous voyez ces informations car cet élément a été envoyé vers iA.delib." #. Default: "Here is a resume of what will be sent to PloneMeeting" +#: ../browser/templates/display_data_to_send.pt:2 msgid "send_to_plonemeeting_descr" msgstr "Voici un résumé de ce qui va être envoyé" - -#. Default: "Please specify the proposingGroup to use" -msgid "send_to_plonemeeting_specify_proposing_group" -msgstr "Choisissez le groupe proposant avec lequel le point sera créé" diff --git a/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot b/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot index 87d22ae..a55f8cd 100644 --- a/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot +++ b/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot @@ -1,224 +1,310 @@ +# --- PLEASE EDIT THE LINES BELOW CORRECTLY --- +# SOME DESCRIPTIVE TITLE. +# FIRST AUTHOR , YEAR. msgid "" msgstr "" -"Project-Id-Version: imio.pm.wsclient\n" -"POT-Creation-Date: 2013-02-12 13:15-100\n" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2022-11-09 10:16+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0\n" -"Language-code: \n" -"Language-name: \n" -"Preferred-encodings: utf-8 latin1\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8 latin1\n" "Domain: imio.pm.wsclient\n" - -#. Default: "WS4PM Client settings" -msgid "WS4PM Client settings" +#: ../browser/settings.py:161 +msgid "" msgstr "" -#. Default: "PloneMeeting WSDL URL" -msgid "PloneMeeting WSDL URL" +#: ../config.py:23 +msgid "A filename is mandatory while generating a document! Please contact system administrator!" msgstr "" -#. Default: "PloneMeeting connection timeout" -msgid "PloneMeeting connection timeout" +#: ../browser/settings.py:149 +msgid "Actions" msgstr "" -#. Default: "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." -msgid "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." +#: ../browser/settings.py:146 +msgid "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." msgstr "" -#. Default: "PloneMeeting username to use" -msgid "PloneMeeting username to use" +#: ../browser/settings.py:128 +msgid "Allowed annex type" msgstr "" -#. Default: "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." -msgid "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." +#: ../browser/settings.py:126 +msgid "Allowed annexes types" msgstr "" -#. Default: "PloneMeeting password to use" -msgid "PloneMeeting password to use" +#: ../config.py:59 +msgid "An annex id is mandatory to download an annex!" msgstr "" -#. Default: "An element can be sent one time only" +#: ../browser/settings.py:102 msgid "An element can be sent one time only" msgstr "" -#. Default: "Viewlet display condition" -msgid "Viewlet display condition" +#: ../config.py:32 +msgid "An error occured during the item creation in PloneMeeting! The error message was : ${error}" +msgstr "" + +#: ../config.py:36 +msgid "An error occured during the item creation in PloneMeeting! The error message was : [{'field': 'proposingGroup', 'message': u'Proposing group is not available for user.', 'error': 'ValidationError'}]" +msgstr "" + +#: ../browser/settings.py:450 +msgid "An error occured while generating the document in PloneMeeting! The error message was : %s" +msgstr "" + +#: ../browser/viewlets.py:81 +msgid "An error occured while searching for linked items in PloneMeeting! The error message was : %s" +msgstr "" + +#: ../browser/settings.py:65 +msgid "Annex type" +msgstr "" + +#: ../browser/settings.py:135 +msgid "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." +msgstr "" + +#: ../browser/forms.py:169 +#: ../browser/settings.py:205 +msgid "Cancel" +msgstr "" + +#: ../browser/settings.py:201 +msgid "Changes saved" +msgstr "" + +#: ../config.py:19 +msgid "Could not detect correct mimetype for item template! Please contact system administrator!" msgstr "" -#. Default: "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." +#: ../config.py:41 +msgid "Could not get userInfos in PloneMeeting for user '${userId}'!" +msgstr "" + +#: ../browser/settings.py:207 +msgid "Edit cancelled" +msgstr "" + +#: ../browser/settings.py:107 msgid "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." msgstr "" -#. Default: "Field accessor mappings" +#: ../browser/settings.py:88 +msgid "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." +msgstr "" + +#: ../browser/settings.py:114 msgid "Field accessor mappings" msgstr "" -#. Default: "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." +#: ../browser/settings.py:121 +msgid "Field mappings" +msgstr "" + +#: ../browser/settings.py:115 msgid "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." msgstr "" -#. Default: "PloneMeeting field name" -msgid "PloneMeeting field name" +#: ../browser/settings.py:145 +msgid "Generated actions" msgstr "" -#. Default: "TAL expression to evaluate for the corresponding PloneMeeting field name" -msgid "TAL expression to evaluate for the corresponding PloneMeeting field name" +#: ../browser/settings.py:127 +msgid "List here the annexes types allowed to be display in the linked meeting item viewlet" msgstr "" -#. Default: "User ids mappings" -msgid "User ids mappings" +#: ../browser/settings.py:72 +msgid "Local user id" msgstr "" -#. Default: "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." -msgid "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." +#: ../browser/forms.py:59 +msgid "Meeting config id" msgstr "" -#. Default: "Local user id" -msgid "Local user id" +#: ../config.py:47 +msgid "No configuration informations found!" +msgstr "" + +#: ../config.py:42 +msgid "No field_mappings defined in the WS4PMClient configuration!" +msgstr "" + +#: ../config.py:52 +msgid "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." +msgstr "" + +#: ../browser/settings.py:42 +msgid "Permissions" +msgstr "" + +#: ../browser/settings.py:84 +msgid "PloneMeeting URL" +msgstr "" + +#: ../browser/settings.py:87 +msgid "PloneMeeting connection timeout" msgstr "" -#. Default: "PloneMeeting corresponding user id" +#: ../browser/settings.py:75 msgid "PloneMeeting corresponding user id" msgstr "" -#. Default: "Generated actions" -msgid "Generated actions" +#: ../browser/settings.py:54 +msgid "PloneMeeting field name" msgstr "" -#. Default: "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." -msgid "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." +#: ../browser/templates/plonemeeting_infos.pt:9 +msgid "PloneMeeting informations" msgstr "" -#. Default: "PloneMeeting meetingConfig id" +#: ../browser/settings.py:46 msgid "PloneMeeting meetingConfig id" msgstr "" -#. Default: "TAL Condition" -msgid "TAL Condition" +#: ../browser/settings.py:99 +msgid "PloneMeeting password to use" msgstr "" -#. Default: "Send to ${meetingConfigTitle}" -msgid "Send to" +#: ../browser/settings.py:94 +msgid "PloneMeeting username to use" msgstr "" -#. Default: "Please specify the proposingGroup to use" -msgid "send_to_plonemeeting_specify_proposing_group" +#: ../configure.zcml:29 +msgid "Registration of the Webservices Client for PloneMeeting" msgstr "" -#. Default: "Here is a resume of what will be sent to PloneMeeting" -msgid "send_to_plonemeeting_descr" +#: ../testing.zcml:15 +msgid "Registration of the Webservices Client for PloneMeeting for the tests" +msgstr "" + +#: ../browser/settings.py:193 +msgid "Save" msgstr "" -#. Default: "Select the proposing group to use for the created item in PloneMeeting" +#: ../browser/forms.py:75 msgid "Select the annexes to send" msgstr "" +#: ../browser/forms.py:66 +msgid "Select the category to use for the created item item in PloneMeeting" +msgstr "" + +#: ../browser/forms.py:70 msgid "Select the desired meeting date for the created item in PloneMeeting" msgstr "" -#. Default: "Select the proposing group to use for the created item in PloneMeeting" +#: ../browser/forms.py:61 msgid "Select the proposing group to use for the created item in PloneMeeting" msgstr "" -#. Default: "Select the category to use for the created item item in PloneMeeting" -msgid "Select the category to use for the created item item in PloneMeeting" +#: ../browser/forms.py:159 +msgid "Send" +msgstr "" + +#: ../browser/forms.py:152 +msgid "Send to" msgstr "" -#. Default: "Send to PloneMeeting" +#: ../browser/forms.py:138 msgid "Send to PloneMeeting" msgstr "" -#. Default: "Send" -msgid "Send" +#: ../browser/settings.py:39 +msgid "TAL Condition" msgstr "" -#. Default: "Save" -msgid "Save" +#: ../browser/settings.py:58 +msgid "TAL expression to evaluate for the corresponding PloneMeeting field name" msgstr "" -#. Default: "Cancel" -msgid "Cancel" +#: ../config.py:48 +msgid "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" msgstr "" -#. Default: "PloneMeeting informations" -msgid "PloneMeeting informations" +#: ../config.py:43 +msgid "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" msgstr "" -#. Default: "You see these informations because the current element has been sent to PloneMeeting." -msgid "pm_informations_help" +#: ../config.py:11 +msgid "The item has been correctly sent to PloneMeeting." msgstr "" -#. Default: "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." -msgid "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." +#: ../config.py:60 +msgid "The requested file could not be found on the item" msgstr "" -#. Default: "The item has been correctly sent to PloneMeeting." -msgid "The item has been correctly sent to PloneMeeting." +#: ../browser/settings.py:95 +msgid "The user must be at least a 'MeetingManager'. Nevertheless, items will be created regarding the User ids mappings defined here under." msgstr "" -#. Default: "Unable to connect to PloneMeeting! The error message was : ${error}!" -msgid "Unable to connect to PloneMeeting! The error message was : ${error}!" +#: ../config.py:7 +msgid "There was NO WARNING message during item creation." msgstr "" -#. Default: "Unable to connect to PloneMeeting! Please contact system administrator!" -msgid "Unable to connect to PloneMeeting! Please contact system administrator!" +#: ../config.py:15 +msgid "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." msgstr "" -#. Default: "This element has already been sent to PloneMeeting!" +#: ../config.py:14 msgid "This element has already been sent to PloneMeeting!" msgstr "" -#. Default: "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." -msgid "There was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : ${error}. Please contact system administrator." +#: ../config.py:10 +msgid "This element is linked to item(s) in PloneMeeting but your are not allowed to see it." msgstr "" -#. Default: "Could not detect correct mimetype for item template! Please contact system administrator!" -msgid "Could not detect correct mimetype for item template! Please contact system administrator!" +#: ../config.py:13 +msgid "Unable to connect to PloneMeeting! Please contact system administrator!" msgstr "" -#. Default: "A filename is mandatory while generating a document! Please contact system administrator!" -msgid "A filename is mandatory while generating a document! Please contact system administrator!" +#: ../config.py:12 +msgid "Unable to connect to PloneMeeting! The error message was : ${error}!" msgstr "" -#. Default: "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." +#: ../config.py:27 msgid "Unable to display informations about the potentially linked item in PloneMeeting because there was an error evaluating the TAL expression '${expr}' for the field '${field_name}'! The error was : '${error}'. Please contact system administrator." msgstr "" -#. Default: "An error occured during the item creation in PloneMeeting! The error message was : ${error}" -msgid "An error occured during the item creation in PloneMeeting! The error message was : ${error}" -msgstr "" - -#. Default: "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" -msgid "The configuration specify that user '${userId}' will create the item in PloneMeeting but this user can not create item for any proposingGroup in PloneMeeting!" +#: ../browser/settings.py:134 +msgid "User ids mappings" msgstr "" -#. Default: "Could not get userInfos in PloneMeeting for user '${userId}'!" -msgid "Could not get userInfos in PloneMeeting for user '${userId}'!" +#: ../browser/settings.py:140 +msgid "User mappings" msgstr "" -#. Default: "No field_mappings defined in the WS4PMClient configuration!" -msgid "No field_mappings defined in the WS4PMClient configuration!" +#: ../browser/settings.py:106 +msgid "Viewlet display condition" msgstr "" -#. Default: "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" -msgid "The current user can not create an item with the proposingGroup forced thru the configuration! Please contact system administrator!" +#: ../browser/settings.py:160 +#: ../profiles/default/controlpanel.xml +msgid "WS4PM Client settings" msgstr "" -#. Default: "No user informations found for current user or the current user is not a creator in PloneMeeting!" -msgid "No user informations found for current user or the current user is not a creator in PloneMeeting!" +#: ../configure.zcml:29 +msgid "Webservices Client for PloneMeeting" msgstr "" -#. Default: "No configuration informations found!" -msgid "No configuration informations found!" +#: ../testing.zcml:15 +msgid "Webservices Client for PloneMeeting testing profile" msgstr "" -#. Default: "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" -msgid "The current user can not create an item with the category forced thru the configuration! Please contact system administrator!" +#. Default: "You see these informations because the current element has been sent to PloneMeeting." +#: ../browser/templates/plonemeeting_infos.pt:10 +msgid "pm_informations_help" msgstr "" -#. Default: "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." -msgid "No sufficient field mappings are defined in the configuration. It is recommended to define at least the 'title' mapping, but 'description' and 'decision' should also be defined. It will ne be possible to create the item in PloneMeeting." +#. Default: "Here is a resume of what will be sent to PloneMeeting" +#: ../browser/templates/display_data_to_send.pt:2 +msgid "send_to_plonemeeting_descr" msgstr "" From 683c3cd74f7aa0b75b4de1c58fdad5bdab13bc0a Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 9 Nov 2022 14:14:57 +0100 Subject: [PATCH 51/65] Adapt test data to reflect REST API results --- src/imio/pm/wsclient/tests/testVocabularies.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/imio/pm/wsclient/tests/testVocabularies.py b/src/imio/pm/wsclient/tests/testVocabularies.py index 6d5a064..b14d2a3 100644 --- a/src/imio/pm/wsclient/tests/testVocabularies.py +++ b/src/imio/pm/wsclient/tests/testVocabularies.py @@ -31,16 +31,10 @@ def test_desired_meetingdates_vocabulary( self, _rest_getMeetingsAcceptingItems, _rest_getConfigInfos ): """Ensure that vocabularies values are the expected""" - _rest_getConfigInfos.return_value = type( - "ConfigInfos", - (object,), - { - "configInfo": [ - {"id": u"plonegov-assembly", "title": u"PloneGov Assembly"}, - {"id": u"plonemeeting-assembly", "title": u"PloneMeeting Assembly"}, - ] - }, - )() + _rest_getConfigInfos.return_value = [ + {"id": u"plonegov-assembly", "title": u"PloneGov Assembly"}, + {"id": u"plonemeeting-assembly", "title": u"PloneMeeting Assembly"}, + ] _rest_getMeetingsAcceptingItems.return_value = [ { u"@extra_includes": [], From e9318b75d50437b61c6940125ca6d178f1c2237d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Wed, 9 Nov 2022 14:36:14 +0100 Subject: [PATCH 52/65] Return and display linked items --- src/imio/pm/wsclient/browser/viewlets.py | 35 ++++--- src/imio/pm/wsclient/tests/testViewlets.py | 107 ++++++++++++++++++++- 2 files changed, 126 insertions(+), 16 deletions(-) diff --git a/src/imio/pm/wsclient/browser/viewlets.py b/src/imio/pm/wsclient/browser/viewlets.py index ab8620c..b8acc40 100644 --- a/src/imio/pm/wsclient/browser/viewlets.py +++ b/src/imio/pm/wsclient/browser/viewlets.py @@ -67,6 +67,16 @@ def available(self): return linkedInfos return True + def get_item_info(self, item): + return self.ws4pmSettings._rest_getItemInfos( + { + 'UID': item['UID'], + 'extra_include': 'meeting,pod_templates,annexes,config', + 'extra_include_meeting_additional_values': '*', + 'fullobjects': None, + } + )[0] + @memoize def getPloneMeetingLinkedInfos(self): """Search items created for context. @@ -76,7 +86,13 @@ def getPloneMeetingLinkedInfos(self): with getConfigInfos. If we encounter an error, we return a tuple as 'usual' like in self.available""" try: - items = self.ws4pmSettings._rest_searchItems({'externalIdentifier': self.context.UID()}) + items = self.ws4pmSettings._rest_searchItems( + { + 'externalIdentifier': self.context.UID(), + 'extra_include': 'linked_items', + 'extra_include_linked_items_mode': 'every_successors', + }, + ) except Exception, exc: return (_(u"An error occured while searching for linked items in PloneMeeting! " "The error message was : %s" % exc), 'error') @@ -95,14 +111,7 @@ def getPloneMeetingLinkedInfos(self): allowed_annexes_types = [line.values()[0] for line in settings.allowed_annexes_types] shownItemsMeetingConfigId = [] for item in items: - res.append(self.ws4pmSettings._rest_getItemInfos( - { - 'UID': item['UID'], - 'extra_include': 'meeting,pod_templates,annexes,config', - 'extra_include_meeting_additional_values': '*', - 'fullobjects': None, - } - )[0]) + res.append(self.get_item_info(item)) lastAddedItem = res[-1] shownItemsMeetingConfigId.append(lastAddedItem['extra_include_config']['id']) # XXX special case if something went wrong and there is an item in PM @@ -113,6 +122,9 @@ def getPloneMeetingLinkedInfos(self): existingSentTo.append(lastAddedItemMeetingConfigId) annotations[WS4PMCLIENT_ANNOTATION_KEY] = existingSentTo sent_to = annotations[WS4PMCLIENT_ANNOTATION_KEY] + if "extra_include_linked_items" in item and item["extra_include_linked_items"]: + for linked_item in item["extra_include_linked_items"]: + res.append(self.get_item_info(linked_item)) # if the number of items found is inferior to elements sent, it means # that some infos are not viewable by current user, we add special message @@ -132,9 +144,8 @@ def getPloneMeetingLinkedInfos(self): # sort res to comply with sent order, for example sent first to college then council def sortByMeetingConfigId(x, y): - return cmp(sent_to.index(x['extra_include_config']['id']), - sent_to.index(y['extra_include_config']['id'])) - res.sort(sortByMeetingConfigId) + return cmp(x["created"], y["created"]) + res.sort(sortByMeetingConfigId, reverse=True) return res def displayMeetingDate(self, meeting_date): diff --git a/src/imio/pm/wsclient/tests/testViewlets.py b/src/imio/pm/wsclient/tests/testViewlets.py index 8e8e194..9e403a2 100644 --- a/src/imio/pm/wsclient/tests/testViewlets.py +++ b/src/imio/pm/wsclient/tests/testViewlets.py @@ -5,18 +5,17 @@ # GNU General Public License (GPL) # -from datetime import datetime -from dateutil import tz +from Products.statusmessages.interfaces import IStatusMessage from imio.pm.wsclient.browser.viewlets import PloneMeetingInfosViewlet from imio.pm.wsclient.config import CAN_NOT_SEE_LINKED_ITEMS_INFO from imio.pm.wsclient.config import CORRECTLY_SENT_TO_PM_INFO from imio.pm.wsclient.config import UNABLE_TO_CONNECT_ERROR from imio.pm.wsclient.config import WS4PMCLIENT_ANNOTATION_KEY +from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import WS4PMCLIENTTestCase from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import cleanMemoize from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import createDocument from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import setCorrectSettingsConfig -from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import WS4PMCLIENTTestCase -from Products.statusmessages.interfaces import IStatusMessage +from mock import patch from zope.annotation import IAnnotations import transaction @@ -100,6 +99,106 @@ def test_getPloneMeetingLinkedInfos(self): # we received informations about the created item self.assertTrue(viewlet.getPloneMeetingLinkedInfos()[0]['UID'] == item.UID()) + @patch("imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_searchItems") + @patch("imio.pm.wsclient.browser.settings.WS4PMClientSettings._rest_getItemInfos") + def test_getPloneMeetingLinkedInfos_with_linked_items( + self, _rest_getItemInfos, _rest_searchItems + ): + """ + Test getPloneMeetingLinkedInfos method when there is linked items + """ + _rest_searchItems.return_value = [{ + "@id": "http://nohost/pu-1234", + "@type": "MeetingItemCollege", + "UID": "1234", + "created": "2022-01-01T00:00:00+00:00", + "modified": "2022-01-01T00:00:00+00:00", + "id": "pu-1234", + "title": "PU/1244", + "review_state": "accepted", + "description": "Description", + "extra_include_linked_items": [ + { + "@id": "http://nohost/pu-1234-1", + "@type": "MeetingItemCollege", + "UID": "2345", + "created": "2022-01-02T00:00:00+00:00", + "modified": "2022-01-02T00:00:00+00:00", + "id": "pu-1234-1", + "title": "PU/1244-1", + "review_state": "accepted", + "description": "Description", + }, + { + "@id": "http://nohost/pu-1234-2", + "@type": "MeetingItemCollege", + "UID": "3456", + "created": "2022-01-03T00:00:00+00:00", + "modified": "2022-01-03T00:00:00+00:00", + "id": "pu-1234-2", + "title": "PU/1244-2", + "review_state": "accepted", + "description": "Description", + }, + ], + "extra_include_linked_items_items_total": "2", + }] + _rest_getItemInfos.side_effect = [ + [{ + "@id": "http://nohost/pu-1234", + "@type": "MeetingItemCollege", + "UID": "1234", + "created": "2022-01-01T00:00:00+00:00", + "modified": "2022-01-01T00:00:00+00:00", + "id": "pu-1234", + "title": "PU/1244", + "review_state": "accepted", + "description": "Description", + "extra_include_config": { + "id": "plonemeeting-assembly", + } + }], + [{ + "@id": "http://nohost/pu-1234-1", + "@type": "MeetingItemCollege", + "UID": "2345", + "created": "2022-01-02T00:00:00+00:00", + "modified": "2022-01-02T00:00:00+00:00", + "id": "pu-1234-1", + "title": "PU/1244-1", + "review_state": "accepted", + "description": "Description", + "extra_include_config": { + "id": "plonemeeting-assembly", + } + }], + [{ + "@id": "http://nohost/pu-1234-2", + "@type": "MeetingItemCollege", + "UID": "3456", + "created": "2022-01-03T00:00:00+00:00", + "modified": "2022-01-03T00:00:00+00:00", + "id": "pu-1234-2", + "title": "PU/1244-2", + "review_state": "accepted", + "description": "Description", + "extra_include_config": { + "id": "plonegov-assembly", + } + }], + ] + self.changeUser('admin') + document = createDocument(self.portal) + self._sendToPloneMeeting(document) + viewlet = PloneMeetingInfosViewlet(document, self.request, None, None) + viewlet.update() + items = viewlet.getPloneMeetingLinkedInfos() + self.assertEqual(3, len(items)) + self.assertEqual( + ["pu-1234-2", "pu-1234-1", "pu-1234"], + [e["id"] for e in items], + ) + def test_canNotSeeLinkedInfos(self): """ If the element has been sent to PloneMeeting but current user can not see these From da508fa58db6d36b14fbb57c86fe22db52331f7d Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 18 Nov 2022 14:48:18 +0100 Subject: [PATCH 53/65] Fix test --- src/imio/pm/wsclient/tests/testForms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imio/pm/wsclient/tests/testForms.py b/src/imio/pm/wsclient/tests/testForms.py index 3256019..967610d 100644 --- a/src/imio/pm/wsclient/tests/testForms.py +++ b/src/imio/pm/wsclient/tests/testForms.py @@ -212,7 +212,7 @@ def test_checkAlreadySentToPloneMeeting(self): self.assertFalse(settings.only_one_sending) view._finishedSent = False self.request.response.status = 200 # if status in 300, render is not called by z3cform - self.assertIn('Send to PloneMeeting Assembly', view()) + self.assertIn('form-buttons-send_to_plonemeeting', view()) self.assertEqual(len(messages.show()), 0) # if we remove the item in PloneMeeting, the view is aware of it itemUID = str(ws4pmSettings._rest_searchItems({'externalIdentifier': document.UID()})[0]['UID']) From 214292ed18a5bd5990952429e369750ec6d89ee2 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 18 Nov 2022 14:52:54 +0100 Subject: [PATCH 54/65] Add `ignore_validation_for` parameter --- src/imio/pm/wsclient/browser/settings.py | 4 ++ src/imio/pm/wsclient/tests/testSOAPMethods.py | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index c86e1e6..a5587c7 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -463,6 +463,7 @@ def _rest_getItemCreationAvailableData(self): u"externalIdentifier", u"extraAttrs", u"groupsInCharge", + u"ignore_validation_for", u"motivation", u"optionalAdvisers", u"preferredMeeting", @@ -508,6 +509,9 @@ def _rest_createItem(self, meetingConfigId, proposingGroupId, creationData): "in_name_of": inTheNameOf, } # For backward compatibility + if "ignore_validation_for" in creationData: + ignored = creationData.pop("ignore_validation_for") + creationData["ignore_validation_for"] = ignored.split(",") if "extraAttrs" in creationData: extra_attrs = creationData.pop("extraAttrs") for value in extra_attrs: diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testSOAPMethods.py index a2c58ad..77edf94 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testSOAPMethods.py @@ -118,6 +118,7 @@ def test_rest_getItemCreationAvailableData(self): u'externalIdentifier', u'extraAttrs', u'groupsInCharge', + u'ignore_validation_for', u'motivation', u'optionalAdvisers', u'preferredMeeting', @@ -221,6 +222,55 @@ def test_rest_searchItems(self): self.assertTrue(len(result), 1) self.assertTrue(result[0]["UID"] == item2.UID()) + def test_rest_createItem_with_ignore_validation(self): + """Check item creation with `ignore_validation_for` parameter""" + cfg2 = self.meetingConfig2 + cfg2Id = cfg2.getId() + ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') + setCorrectSettingsConfig(self.portal, minimal=True) + self.changeUser('pmManager') + self.setMeetingConfig(cfg2Id) + self._enableField("internalNotes") + test_meeting = self.create('Meeting') + self.freezeMeeting(test_meeting) + self.changeUser('pmCreator1') + # create the 'pmCreator1' member area to be able to create an item + pmFolder = self.tool.getPloneMeetingFolder(cfg2Id) + # we have to commit() here or portal used behing the SOAP call + # does not have the freshly created item... + transaction.commit() + # create an item for 'pmCreator1' + data = {'title': u'My sample item', + 'description': u'

My description

', + # also use accents, this was failing with suds-jurko 0.5 + 'decision': u'

My d\xe9cision

', + 'preferredMeeting': test_meeting.UID(), + 'externalIdentifier': u'my-external-identifier', + 'extraAttrs': [{'key': 'internalNotes', + 'value': '

Internal notes

'}]} + result = ws4pmSettings._rest_createItem(cfg2Id, 'developers', data) + self.assertIsNone(result) + messages = IStatusMessage(self.request) + self.assertEqual( + messages.show()[-1].message, + u"An error occured during the item creation in PloneMeeting! The error " + "message was : [{'field': 'category', 'message': u'Please select a " + "category.', 'error': 'ValidationError'}]" + ) + + data["ignore_validation_for"] = "category" + result = ws4pmSettings._rest_createItem(cfg2Id, 'developers', data) + # commit again so the item is really created + transaction.commit() + # the item is created and his UID is returned + # check that the item is actually created inTheNameOf 'pmCreator1' + self.assertIsNotNone(result) + itemUID = result[0] + item = self.portal.uid_catalog(UID=itemUID)[0].getObject() + self.assertTrue(item.aq_inner.aq_parent.UID(), pmFolder.UID()) + self.assertTrue(item.owner_info()['id'] == 'pmCreator1') + self.assertEqual(item.Title(), data['title']) + def test_rest_createItem(self): """Check item creation. Item creation will automatically use currently connected user From fdcf02ba49520a9151d177f193a83979ac1e0a5c Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 18 Nov 2022 15:57:44 +0100 Subject: [PATCH 55/65] Remove `content_category` attribute for annexes --- src/imio/pm/wsclient/browser/forms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index 9e2afa4..3e0c5b9 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -401,7 +401,6 @@ def _buildAnnexesData(self): { "@type": "annex", "title": unidecode(annex.title.decode('utf-8')), - "content_category": "item-annex", "file": { "filename": unidecode(annex_name), "data": base64.b64encode(annex_file.read()), From 596576f11f2958a0842d32908471a949eafc994b Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 18 Nov 2022 16:23:18 +0100 Subject: [PATCH 56/65] Prepare release --- CHANGES.rst | 7 ++++--- setup.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0242e71..612cd83 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,10 +1,11 @@ Changelog ========= -1.18 (unreleased) ------------------ +2.0.0b1 (unreleased) +-------------------- -- Nothing changed yet. +- Migrate to REST api + [mpeeters] 1.17 (2022-06-29) diff --git a/setup.py b/setup.py index 327c128..aac5906 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = '1.18.dev0' +version = '2.0.0b1.dev0' setup(name='imio.pm.wsclient', version=version, From 00a88f6792438283796e967e5e82261509c691f8 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 18 Nov 2022 16:23:48 +0100 Subject: [PATCH 57/65] Preparing release 2.0.0b1 --- CHANGES.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 612cd83..fc654d5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Changelog ========= -2.0.0b1 (unreleased) +2.0.0b1 (2022-11-18) -------------------- - Migrate to REST api diff --git a/setup.py b/setup.py index aac5906..f5a35b5 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = '2.0.0b1.dev0' +version = '2.0.0b1' setup(name='imio.pm.wsclient', version=version, From a3fccff77f6cc5d2a4afbdec8125fa39bcab3816 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 18 Nov 2022 16:25:18 +0100 Subject: [PATCH 58/65] Back to development: 2.0.0b2 --- CHANGES.rst | 6 ++++++ setup.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index fc654d5..22c6085 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog ========= +2.0.0b2 (unreleased) +-------------------- + +- Nothing changed yet. + + 2.0.0b1 (2022-11-18) -------------------- diff --git a/setup.py b/setup.py index f5a35b5..82f7939 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = '2.0.0b1' +version = '2.0.0b2.dev0' setup(name='imio.pm.wsclient', version=version, From 604f2de02e20e56b3933795a50513ef1a00f33a0 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 24 Nov 2023 09:38:53 +0100 Subject: [PATCH 59/65] Upgrade config for REST api --- CHANGES.rst | 3 ++- src/imio/pm/wsclient/browser/configure.zcml | 8 ------- src/imio/pm/wsclient/configure.zcml | 17 +++++++++++++++ .../pm/wsclient/profiles/default/metadata.xml | 2 +- src/imio/pm/wsclient/upgrade.py | 21 +++++++++++++++++++ 5 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 src/imio/pm/wsclient/upgrade.py diff --git a/CHANGES.rst b/CHANGES.rst index 22c6085..23a9318 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,8 @@ Changelog 2.0.0b2 (unreleased) -------------------- -- Nothing changed yet. +- Upgrade config for REST api + [mpeeters] 2.0.0b1 (2022-11-18) diff --git a/src/imio/pm/wsclient/browser/configure.zcml b/src/imio/pm/wsclient/browser/configure.zcml index 59c32f0..31cc226 100644 --- a/src/imio/pm/wsclient/browser/configure.zcml +++ b/src/imio/pm/wsclient/browser/configure.zcml @@ -140,12 +140,4 @@ directory="javascripts" /> - - diff --git a/src/imio/pm/wsclient/configure.zcml b/src/imio/pm/wsclient/configure.zcml index a31a067..5edb667 100644 --- a/src/imio/pm/wsclient/configure.zcml +++ b/src/imio/pm/wsclient/configure.zcml @@ -28,4 +28,21 @@ provides="Products.GenericSetup.interfaces.EXTENSION" /> + + + + diff --git a/src/imio/pm/wsclient/profiles/default/metadata.xml b/src/imio/pm/wsclient/profiles/default/metadata.xml index 9c45c2f..d259bca 100644 --- a/src/imio/pm/wsclient/profiles/default/metadata.xml +++ b/src/imio/pm/wsclient/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 1.13 + 200 profile-plone.app.registry:default profile-collective.z3cform.datagridfield:default diff --git a/src/imio/pm/wsclient/upgrade.py b/src/imio/pm/wsclient/upgrade.py new file mode 100644 index 0000000..671d9b9 --- /dev/null +++ b/src/imio/pm/wsclient/upgrade.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from plone import api + +import logging + + +def upgrade_to_200(context): + logger = logging.getLogger("imio.pm.wsclient: Upgrade to REST API") + logger.info("starting upgrade steps") + url = api.portal.get_registry_record( + "imio.pm.wsclient.browser.settings.IWS4PMClientSettings.pm_url", + default=None, + ) + if url: + parts = url.split("ws4pm.wsdl") + api.portal.set_registry_record( + "imio.pm.wsclient.browser.settings.IWS4PMClientSettings.pm_url", + parts[0], + ) + logger.info("upgrade step done!") From 00be49a90a64d25a1ca394db963a0e895577a1a1 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 24 Nov 2023 15:02:10 +0100 Subject: [PATCH 60/65] Avoid an error if the user can not access to meetings --- CHANGES.rst | 2 ++ src/imio/pm/wsclient/browser/vocabularies.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 23a9318..e44b0bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,8 @@ Changelog 2.0.0b2 (unreleased) -------------------- +- Avoid an error if the user can not access to meetings + [mpeeters] - Upgrade config for REST api [mpeeters] diff --git a/src/imio/pm/wsclient/browser/vocabularies.py b/src/imio/pm/wsclient/browser/vocabularies.py index 5f641fe..1245571 100644 --- a/src/imio/pm/wsclient/browser/vocabularies.py +++ b/src/imio/pm/wsclient/browser/vocabularies.py @@ -277,6 +277,8 @@ def __call__(self, context): data = {'meetingConfigId': meeting_config_id} possible_meetings = ws4pmsettings._rest_getMeetingsAcceptingItems(data) local = pytz.timezone("Europe/Brussels") + if not possible_meetings: + return SimpleVocabulary([]) for meeting in possible_meetings: meeting["date"] = datetime.strptime(meeting["date"], "%Y-%m-%dT%H:%M:%S") meeting["date"] = local.localize(meeting["date"]) From 6b84fc32f0686816c88691185eca298b2cf701c2 Mon Sep 17 00:00:00 2001 From: Martin Peeters Date: Fri, 24 Nov 2023 15:09:13 +0100 Subject: [PATCH 61/65] Fix translation of overlay title --- CHANGES.rst | 2 ++ src/imio/pm/wsclient/browser/forms.py | 16 ++++++++++------ .../locales/en/LC_MESSAGES/imio.pm.wsclient.po | 7 +++---- .../locales/fr/LC_MESSAGES/imio.pm.wsclient.po | 5 ++--- .../pm/wsclient/locales/imio.pm.wsclient.pot | 6 +++--- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index e44b0bf..7df298a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,8 @@ Changelog 2.0.0b2 (unreleased) -------------------- +- Fix translation of overlay title + [mpeeters] - Avoid an error if the user can not access to meetings [mpeeters] - Upgrade config for REST api diff --git a/src/imio/pm/wsclient/browser/forms.py b/src/imio/pm/wsclient/browser/forms.py index 3e0c5b9..bd7f5f7 100644 --- a/src/imio/pm/wsclient/browser/forms.py +++ b/src/imio/pm/wsclient/browser/forms.py @@ -149,12 +149,16 @@ def __init__(self, context, request): self.portal = self.portal_state.portal() self.ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') # manage the label to display to wich meetingConfig we are sending the element... - self.label = translate(_('Send to'), - domain='imio.pm.wsclient', - mapping={'meetingConfigTitle': - self.ws4pmSettings.getMeetingConfigTitle(self.meetingConfigId), - }, - context=self.request) + self.label = translate( + _( + 'Send to ${meetingConfigTitle}', + mapping={ + 'meetingConfigTitle': self.ws4pmSettings.getMeetingConfigTitle(self.meetingConfigId), + }, + ), + domain='imio.pm.wsclient', + context=self.request, + ) @button.buttonAndHandler(_('Send'), name='send_to_plonemeeting') def handleSendToPloneMeeting(self, action): diff --git a/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po b/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po index 98fd7fa..d717d46 100644 --- a/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po +++ b/src/imio/pm/wsclient/locales/en/LC_MESSAGES/imio.pm.wsclient.po @@ -62,7 +62,7 @@ msgstr "" msgid "An error occured while generating the document in PloneMeeting! The error message was : %s" msgstr "" -#: ../browser/viewlets.py:81 +#: ../browser/viewlets.py:97 msgid "An error occured while searching for linked items in PloneMeeting! The error message was : %s" msgstr "" @@ -236,10 +236,9 @@ msgstr "Select the proposing group to use for the created item in PloneMeeting" msgid "Send" msgstr "Send" -#. Default: "Send to ${meetingConfigTitle}" #: ../browser/forms.py:152 -msgid "Send to" -msgstr "Send to ${meetingConfigTitle}" +msgid "Send to ${meetingConfigTitle}" +msgstr "" #. Default: "Send to PloneMeeting" #: ../browser/forms.py:138 diff --git a/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po b/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po index 7c78a3a..c159465 100644 --- a/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po +++ b/src/imio/pm/wsclient/locales/fr/LC_MESSAGES/imio.pm.wsclient.po @@ -62,7 +62,7 @@ msgstr "" msgid "An error occured while generating the document in PloneMeeting! The error message was : %s" msgstr "" -#: ../browser/viewlets.py:81 +#: ../browser/viewlets.py:97 msgid "An error occured while searching for linked items in PloneMeeting! The error message was : %s" msgstr "" @@ -236,9 +236,8 @@ msgstr "Sélectionnez le groupe proposant avec lequel le point sera créé dans msgid "Send" msgstr "Envoyer" -#. Default: "Send to ${meetingConfigTitle}" #: ../browser/forms.py:152 -msgid "Send to" +msgid "Send to ${meetingConfigTitle}" msgstr "Envoyer vers ${meetingConfigTitle}" #. Default: "Send to PloneMeeting" diff --git a/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot b/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot index a55f8cd..2b865bb 100644 --- a/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot +++ b/src/imio/pm/wsclient/locales/imio.pm.wsclient.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2022-11-09 10:16+0000\n" +"POT-Creation-Date: 2023-11-24 09:33+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -61,7 +61,7 @@ msgstr "" msgid "An error occured while generating the document in PloneMeeting! The error message was : %s" msgstr "" -#: ../browser/viewlets.py:81 +#: ../browser/viewlets.py:97 msgid "An error occured while searching for linked items in PloneMeeting! The error message was : %s" msgstr "" @@ -211,7 +211,7 @@ msgid "Send" msgstr "" #: ../browser/forms.py:152 -msgid "Send to" +msgid "Send to ${meetingConfigTitle}" msgstr "" #: ../browser/forms.py:138 From 281801b2687b3216f422f7d598952b4008d0a99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Duch=C3=AAne?= Date: Fri, 19 Jul 2024 11:35:18 +0200 Subject: [PATCH 62/65] Setup CI --- .github/workflows/tests.yml | 60 +++++++++++++++++++++++++++++++++++++ requirements-4.3.txt | 2 ++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 requirements-4.3.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a01443c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,60 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Tests + +on: [ push ] + +jobs: + tests: + runs-on: "ubuntu-latest" + strategy: + fail-fast: false + matrix: + include: + - python-version: "2.7.18" + plone-version: "4.3" + # - python-version: "3.8.15" + # plone-version: "5.2" + # - python-version: "3.11.2" + # plone-version: "6.0" + services: + libreoffice: + image: imiobe/libreoffice:7.3 + ports: + - 2002:2002 + volumes: + - /tmp:/tmp + - /var/tmp:/var/tmp + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Env + run: | + mkdir -p -m 777 /tmp/appy/ + sudo add-apt-repository ppa:libreoffice/ppa -y + sudo apt update -qq -y + sudo apt-get install -qq -y libreoffice libreoffice-script-provider-python + mkdir -p buildout-cache/{eggs,downloads} + - name: Set up pyenv and Python + uses: "gabrielfalcao/pyenv-action@v17" + with: + default: "${{ matrix.python-version }}" + - name: Setup Python env + run: | + pip install --upgrade pip + pip install -r requirements-${{ matrix.plone-version }}.txt + - name: Cache eggs + uses: actions/cache@v2 + env: + cache-name: cache-eggs + with: + path: ~/buildout-cache/eggs + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ matrix.plone-version }} + restore-keys: ${{ runner.os }}-test-${{ env.cache-name }}-${{ matrix.plone-version }} + - name: buildout + run: | + buildout + - name: test + run: | + bin/test -t !robot diff --git a/requirements-4.3.txt b/requirements-4.3.txt new file mode 100644 index 0000000..5c0111f --- /dev/null +++ b/requirements-4.3.txt @@ -0,0 +1,2 @@ +zc.buildout==2.13.3 +setuptools==44.1.1 From 562a7d8fdded5de5847db218f64f3bfbf4a035df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Duch=C3=AAne?= Date: Fri, 19 Jul 2024 15:13:47 +0200 Subject: [PATCH 63/65] Fix some tests --- .github/workflows/tests.yml | 3 --- ...{testSOAPMethods.py => testRESTMethods.py} | 20 ++++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) rename src/imio/pm/wsclient/tests/{testSOAPMethods.py => testRESTMethods.py} (96%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a01443c..65514db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,3 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - name: Tests on: [ push ] diff --git a/src/imio/pm/wsclient/tests/testSOAPMethods.py b/src/imio/pm/wsclient/tests/testRESTMethods.py similarity index 96% rename from src/imio/pm/wsclient/tests/testSOAPMethods.py rename to src/imio/pm/wsclient/tests/testRESTMethods.py index 77edf94..0e59fd8 100644 --- a/src/imio/pm/wsclient/tests/testSOAPMethods.py +++ b/src/imio/pm/wsclient/tests/testRESTMethods.py @@ -13,9 +13,9 @@ import transaction -class testSOAPMethods(WS4PMCLIENTTestCase): +class testRESTMethods(WS4PMCLIENTTestCase): """ - Tests the browser.settings SOAP client methods + Tests the browser.settings REST client methods """ def test_rest_connectToPloneMeeting(self): @@ -98,21 +98,22 @@ def test_rest_getConfigInfos(self): self.assertTrue(configInfos[0]['id'], u'plonemeeting-assembly') self.assertTrue(configInfos[1]['id'], u'plonegov-assembly') # by default, no categories - self.assertFalse(configInfos[0].get('categories', False)) + # plonemeeting-assembly has no categories BTW, so we use plonegov-assembly + self.assertFalse(configInfos[1].get('categories', False)) # we can ask categories by passing a showCategories=True to _rest_getConfigInfos configInfos = ws4pmSettings._rest_getConfigInfos(showCategories=True) - self.assertTrue(configInfos[0].get('categories', False)) + self.assertEqual(len(configInfos[1].get('categories')), 6) def test_rest_getItemCreationAvailableData(self): """Check that we receive the list of available data for creating an item.""" ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') setCorrectSettingsConfig(self.portal, minimal=True) - availableData = ws4pmSettings._rest_getItemCreationAvailableData() - availableData.sort() - self.assertEqual(availableData, [u'annexes', + availableData = set(ws4pmSettings._rest_getItemCreationAvailableData()) + self.assertEqual(availableData, {u'annexes', u'associatedGroups', u'category', + u'copyGroups', u'decision', u'description', u'externalIdentifier', @@ -124,7 +125,7 @@ def test_rest_getItemCreationAvailableData(self): u'preferredMeeting', u'proposingGroup', u'title', - u'toDiscuss']) + u'toDiscuss'}) def test_rest_getItemInfos(self): """Check the fact of getting informations about an existing item.""" @@ -385,6 +386,7 @@ def test_rest_getMeetingAcceptingItems(self): # freeze meeting_2 => it still should be in the accepting items meetings self.changeUser('pmManager') api.content.transition(meeting_2, 'freeze') + self.portal._volatile_cache_keys._p_changed = False # Avoid ConflictError transaction.commit() self.changeUser('pmCreator1') meetings = ws4pmSettings._rest_getMeetingsAcceptingItems( @@ -425,5 +427,5 @@ def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() # add a prefix because we heritate from testMeeting and we do not want every tests of testMeeting to be run here... - suite.addTest(makeSuite(testSOAPMethods, prefix='test_')) + suite.addTest(makeSuite(testRESTMethods, prefix='test_')) return suite From ee79f4fd7f70e04a741c968c7c80f326845ca421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Duch=C3=AAne?= Date: Fri, 19 Jul 2024 15:39:56 +0200 Subject: [PATCH 64/65] Fix last broken tests --- src/imio/pm/wsclient/browser/settings.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index a5587c7..7c84119 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -290,9 +290,12 @@ def _rest_checkIsLinked(self, data): **{k: v for k, v in data.items() if k != "inTheNameOf"} ) response = session.get(url) - if response.status_code == 200: - return response.json() - return [] + if response.status_code != 200: + return False + if response.json().get("items_total") == 0: + # When there is no item found, we still get a response but items_total is 0 + return False + return response.json() @memoize def _rest_getConfigInfos(self, showCategories=False): From 4cfa2c3c02c68dc6bfd22b71fcbc3a2cd7299232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Duch=C3=AAne?= Date: Tue, 23 Jul 2024 09:17:10 +0200 Subject: [PATCH 65/65] Implement _rest_getDecidedMeetingDate and add tests --- CHANGES.rst | 4 + src/imio/pm/wsclient/browser/settings.py | 31 ++++++++ src/imio/pm/wsclient/tests/testRESTMethods.py | 73 ++++++++++++++++++- 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7df298a..88342f7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,6 +10,10 @@ Changelog [mpeeters] - Upgrade config for REST api [mpeeters] +- Fixed all unit tests. + [aduchene] +- Add a new helper method `_rest_getDecidedMeetingDate` to get the actual decided meeting date. + [aduchene] 2.0.0b1 (2022-11-18) diff --git a/src/imio/pm/wsclient/browser/settings.py b/src/imio/pm/wsclient/browser/settings.py index 7c84119..266fdca 100644 --- a/src/imio/pm/wsclient/browser/settings.py +++ b/src/imio/pm/wsclient/browser/settings.py @@ -2,6 +2,7 @@ from collective.z3cform.datagridfield import DataGridFieldFactory from collective.z3cform.datagridfield.registry import DictRow +from datetime import datetime from imio.pm.wsclient import WS4PMClientMessageFactory as _ from imio.pm.wsclient.config import ACTION_SUFFIX from imio.pm.wsclient.config import CONFIG_CREATE_ITEM_PM_ERROR @@ -408,6 +409,36 @@ def _rest_getMeetingsAcceptingItems(self, data): if response.status_code == 200: return response.json()["items"] + def _rest_getDecidedMeetingDate(self, + data, + item_portal_type, + decided_states=('accepted', 'accepted_but_modified', 'accepted_and_returned')): + """ + Get the actual decided meeting date. It handles delayed and sentTo items appropriately. + Use item_portal_type parameter to get the decided meeting date for this portal_type. + It returns a datetime object if a meeting has been found, or None otherwise. + TODO: handle decided_states correctly, fetching decided states from PloneMeeting configuration + """ + brains = self._rest_searchItems(data) + if not brains: + return # Item has been deleted or has not been sent to PloneMeeting + item = self._rest_getItemInfos( + {"UID": brains[0]['UID'], "showExtraInfos": True, + 'extra_include': 'meeting,linked_items', + 'extra_include_meeting_additional_values': '*', + 'extra_include_linked_items_mode': 'every_successors'} + )[0] + if item_portal_type == item["@type"] and item['review_state'] in decided_states: + return datetime.strptime(item['extra_include_meeting']['date'], "%Y-%m-%dT%H:%M:%S") + elif item['extra_include_linked_items']: + for linked_item in item['extra_include_linked_items']: + if item_portal_type == linked_item["@type"] and linked_item['review_state'] in decided_states: + item = self._rest_getItemInfos( + {"UID": linked_item['UID'], "showExtraInfos": True, 'extra_include': 'meeting'} + )[0] + return datetime.strptime(item['extra_include_meeting']['date'], "%Y-%m-%dT%H:%M:%S") + + def _rest_getItemTemplate(self, data): """Query the getItemTemplate REST server method.""" session = self._rest_connectToPloneMeeting() diff --git a/src/imio/pm/wsclient/tests/testRESTMethods.py b/src/imio/pm/wsclient/tests/testRESTMethods.py index 0e59fd8..70a0806 100644 --- a/src/imio/pm/wsclient/tests/testRESTMethods.py +++ b/src/imio/pm/wsclient/tests/testRESTMethods.py @@ -6,9 +6,10 @@ from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import setCorrectSettingsConfig from imio.pm.wsclient.tests.WS4PMCLIENTTestCase import WS4PMCLIENTTestCase from plone import api +from Products.Archetypes.event import ObjectEditedEvent from Products.statusmessages.interfaces import IStatusMessage from zope.component import getMultiAdapter - +from zope.event import notify import transaction @@ -422,6 +423,76 @@ def test_rest_getMeetingAcceptingItems(self): # Ensure that meetings have a date self.assertEqual("2013-03-03T00:00:00", meetings[0]["date"]) + def test_rest_getDecidedMeetingDate(self): + """Test the _rest_getDecidedMeetingDate method that should return + the date of which the item has been actually decided.""" + setCorrectSettingsConfig(self.portal, minimal=True) + cfg = self.meetingConfig + cfg2= self.meetingConfig2 + cfg2Id = cfg2.getId() + ws4pmSettings = getMultiAdapter((self.portal, self.request), name='ws4pmclient-settings') + + self.changeUser('admin') + self._activate_wfas(('delayed', 'postpone_next_meeting', )) + # Setting up sending to another meetingConfig + cfg.setMeetingConfigsToCloneTo(({'meeting_config': '%s' % cfg2Id, + 'trigger_workflow_transitions_until': '%s.%s' % + (cfg2Id, 'validated')},)) + cfg.setItemAutoSentToOtherMCStates((u'accepted', )) + notify(ObjectEditedEvent(cfg)) + self.changeUser("pmManager") + # Situation 1: item 'item_delayed' sent to PM will be delayed in a meeting 'meeting_delayed' + # It will be replaced by 'item_decided' and decided in a next meeting 'meeting_decided' + # It will also be sent to meetingConfig2 + meeting_delayed = self.create('Meeting', date=datetime(2024, 7, 20)) + item_delayed = self.create('MeetingItem', decision="A Decision", externalIdentifier=u'external1') + item_delayed.setOtherMeetingConfigsClonableTo((cfg2Id,)) + item_delayed.reindexObject(idxs=['sentToInfos', "externalIdentifier",]) + self.presentItem(item_delayed) + self.decideMeeting(meeting_delayed) + self.do(item_delayed, 'delay') + item_decided = item_delayed.get_successor() + meeting_decided_date = datetime(2024, 7, 21) + meeting_decided = self.create('Meeting', date=meeting_decided_date) + self.presentItem(item_decided) + self.decideMeeting(meeting_decided) + self.do(item_decided, 'accept') + transaction.commit() + self.assertEqual( + ws4pmSettings._rest_getDecidedMeetingDate( + {'externalIdentifier': 'external1', 'inTheNameOf': 'pmManager'}, + item_portal_type=cfg.getItemTypeName() + ), + meeting_decided_date + ) + # Situation 2: 'item_decided' has been sent to meetingConfig2 as an other item 'item_sent' + # and will be decided there too. We want the date of the meeting `meeting2_decided` + # in which 'item_sent' is decided. + item_sent = item_decided.get_successor() + item_sent.category = 'deployment' # categories are activated in cfg2 + self.setMeetingConfig(cfg2Id) + meeting2_decided_date = datetime(2024, 7, 22) + meeting2_decided = self.create('Meeting', date=meeting2_decided_date) + self.presentItem(item_sent) + self.decideMeeting(meeting2_decided) + self.do(item_sent, 'accept') + transaction.commit() + self.assertEqual( + ws4pmSettings._rest_getDecidedMeetingDate( + {'externalIdentifier': 'external1', 'inTheNameOf': 'pmManager'}, + item_portal_type=cfg2.getItemTypeName() + ), + meeting2_decided_date + ) + # It should still give us the meeting_decided_date for situation 1 + self.assertEqual( + ws4pmSettings._rest_getDecidedMeetingDate( + {'externalIdentifier': 'external1', 'inTheNameOf': 'pmManager'}, + item_portal_type=cfg.getItemTypeName() + ), + meeting_decided_date + ) + def test_suite(): from unittest import TestSuite, makeSuite