From 9daf3717756574bb64844fc63925c643d059cbc6 Mon Sep 17 00:00:00 2001 From: David Erni Date: Mon, 31 Aug 2020 14:07:31 +0200 Subject: [PATCH 1/4] Update schema docs after schema changed. --- docs/public/dev-manual/api/schemas/ftw.mail.mail.inc | 2 +- .../dev-manual/api/schemas/opengever.document.document.inc | 2 +- .../api/schemas/opengever.dossier.businesscasedossier.inc | 2 +- .../api/schemas/opengever.repository.repositoryfolder.inc | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/public/dev-manual/api/schemas/ftw.mail.mail.inc b/docs/public/dev-manual/api/schemas/ftw.mail.mail.inc index 9cc2248312..bc9d066f2b 100644 --- a/docs/public/dev-manual/api/schemas/ftw.mail.mail.inc +++ b/docs/public/dev-manual/api/schemas/ftw.mail.mail.inc @@ -59,7 +59,7 @@ :Feldname: :field-title:`Bearbeitungsinformation` :Datentyp: ``Text`` - :Default: "" + :Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier diff --git a/docs/public/dev-manual/api/schemas/opengever.document.document.inc b/docs/public/dev-manual/api/schemas/opengever.document.document.inc index 61cb57d147..2a98a0abe0 100644 --- a/docs/public/dev-manual/api/schemas/opengever.document.document.inc +++ b/docs/public/dev-manual/api/schemas/opengever.document.document.inc @@ -69,7 +69,7 @@ :Feldname: :field-title:`Bearbeitungsinformation` :Datentyp: ``Text`` - :Default: "" + :Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier diff --git a/docs/public/dev-manual/api/schemas/opengever.dossier.businesscasedossier.inc b/docs/public/dev-manual/api/schemas/opengever.dossier.businesscasedossier.inc index 294bdf0ea9..ab73169eb3 100644 --- a/docs/public/dev-manual/api/schemas/opengever.dossier.businesscasedossier.inc +++ b/docs/public/dev-manual/api/schemas/opengever.dossier.businesscasedossier.inc @@ -209,7 +209,7 @@ :Feldname: :field-title:`Bearbeitungsinformation` :Datentyp: ``Text`` - :Default: "" + :Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier diff --git a/docs/public/dev-manual/api/schemas/opengever.repository.repositoryfolder.inc b/docs/public/dev-manual/api/schemas/opengever.repository.repositoryfolder.inc index 0f86994a15..a7e054e042 100644 --- a/docs/public/dev-manual/api/schemas/opengever.repository.repositoryfolder.inc +++ b/docs/public/dev-manual/api/schemas/opengever.repository.repositoryfolder.inc @@ -9,7 +9,7 @@ :Feldname: :field-title:`Beschreibung` :Datentyp: ``Text`` - :Default: null + :Beschreibung: Eine kurze Beschreibung des Inhalts. @@ -129,7 +129,7 @@ :Feldname: :field-title:`Bearbeitungsinformation` :Datentyp: ``Text`` - :Default: "" + :Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier From 221620d04ae873dc0789a1e7f93eaf48e746b96e Mon Sep 17 00:00:00 2001 From: David Erni Date: Mon, 31 Aug 2020 11:29:44 +0200 Subject: [PATCH 2/4] Extract helper for @querysource urls in tests. --- opengever/api/tests/test_vocabularies.py | 17 +++++++++++------ opengever/testing/integration_test_case.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/opengever/api/tests/test_vocabularies.py b/opengever/api/tests/test_vocabularies.py index efad9acb99..f5bfb6c15a 100644 --- a/opengever/api/tests/test_vocabularies.py +++ b/opengever/api/tests/test_vocabularies.py @@ -3,7 +3,6 @@ from opengever.testing import IntegrationTestCase from opengever.testing import SolrIntegrationTestCase from plone import api -from urllib import urlencode NON_SENSITIVE_VOCABUALRIES = [ @@ -266,7 +265,8 @@ class TestGetQuerySources(IntegrationTestCase): @browsing def test_get_querysource_for_edit(self, browser): self.login(self.regular_user, browser) - url = self.empty_dossier.absolute_url() + '/@querysources/responsible?query=nicole' + url = self.query_source_url( + self.empty_dossier, 'responsible', query='nicole') response = browser.open( url, method='GET', @@ -281,7 +281,12 @@ def test_get_querysource_for_edit(self, browser): @browsing def test_get_vocabulary_for_add(self, browser): self.login(self.regular_user, browser) - url = self.leaf_repofolder.absolute_url() + '/@querysources/opengever.dossier.businesscasedossier/responsible?query=nicole' + url = self.query_source_url( + self.leaf_repofolder, + 'responsible', + add='opengever.dossier.businesscasedossier', + query='nicole', + ) response = browser.open( url, method='GET', @@ -296,7 +301,8 @@ def test_get_vocabulary_for_add(self, browser): @browsing def test_get_keywords_querysource_for_edit(self, browser): self.login(self.regular_user, browser) - url = self.empty_dossier.absolute_url() + '/@querysources/keywords?query=secret' + url = self.query_source_url( + self.empty_dossier, 'keywords', query='secret') response = browser.open( url, method='GET', @@ -314,8 +320,7 @@ class TestGetQuerySourcesSolr(SolrIntegrationTestCase): @browsing def test_get_task_issuer_non_ascii_char_handling(self, browser): self.login(self.regular_user, browser) - url = self.task.absolute_url() + '/@querysources/issuer?{}'.format( - urlencode({'query': u'k\xf6vin'.encode('utf-8')})) + url = self.query_source_url(self.task, 'issuer', query=u'k\xf6vin') response = browser.open( url, method='GET', diff --git a/opengever/testing/integration_test_case.py b/opengever/testing/integration_test_case.py index 252cb3b2af..a7506c3283 100644 --- a/opengever/testing/integration_test_case.py +++ b/opengever/testing/integration_test_case.py @@ -26,8 +26,8 @@ from opengever.meeting.wrapper import MeetingWrapper from opengever.ogds.base.utils import get_current_admin_unit from opengever.ogds.base.utils import get_current_org_unit -from opengever.ogds.models.service import ogds_service from opengever.ogds.models.org_unit import OrgUnit +from opengever.ogds.models.service import ogds_service from opengever.private import enable_opengever_private from opengever.task.interfaces import ISuccessorTaskController from opengever.task.task import ITask @@ -47,7 +47,9 @@ from plone.portlets.constants import CONTEXT_CATEGORY from plone.portlets.interfaces import ILocalPortletAssignmentManager from plone.portlets.interfaces import IPortletManager +from Products.CMFDiffTool.utils import safe_utf8 from sqlalchemy.sql.expression import desc +from urllib import urlencode from z3c.relationfield.relation import RelationValue from zope.component import getMultiAdapter from zope.component import getUtility @@ -642,6 +644,19 @@ def agenda_item_url(self, agenda_item, endpoint): agenda_item.agenda_item_id, endpoint) + def query_source_url(self, context, field, add=None, **kwargs): + base_url = context.absolute_url() + if add: + components = [base_url, '@querysources', add, field] + else: + components = [base_url, '@querysources', field] + + params = {key: safe_utf8(value) for key, value in kwargs.items()} + url = '/'.join(components) + if params: + return '{}?{}'.format(url, urlencode(params)) + return url + def get_ogds_user(self, user): return ogds_service().fetch_user(user.getId()) From 19a71c0f2ee0bf94fe571d0e233d468a109f45a0 Mon Sep 17 00:00:00 2001 From: David Erni Date: Mon, 31 Aug 2020 14:08:19 +0200 Subject: [PATCH 3/4] Add missing description of schema intent. Only the basics are covered yet, but that is better that nothing. --- docs/public/dev-manual/api/index.rst | 1 + docs/public/dev-manual/api/vocabularies.rst | 40 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 docs/public/dev-manual/api/vocabularies.rst diff --git a/docs/public/dev-manual/api/index.rst b/docs/public/dev-manual/api/index.rst index 327b99de33..c1978c71c4 100644 --- a/docs/public/dev-manual/api/index.rst +++ b/docs/public/dev-manual/api/index.rst @@ -23,6 +23,7 @@ Inhalt: extract_attachments.rst trash.rst workflow.rst + vocabularies.rst scanin.rst content_types.rst metadata.rst diff --git a/docs/public/dev-manual/api/vocabularies.rst b/docs/public/dev-manual/api/vocabularies.rst new file mode 100644 index 0000000000..20ff96799f --- /dev/null +++ b/docs/public/dev-manual/api/vocabularies.rst @@ -0,0 +1,40 @@ +.. _vocabularies: + +Vokabulare +========== + +Die Grundlagen für den Umgang mit Vokabularen und Sourcen findet man in der +`plone.restapi Dokumentation Vocabularies and Sources `_. + +In ``plone.restapi`` wird jedoch nur das Editieren bestehender Inhalte +abgebildet. GEVER erweitert dies um eine weitere Aufruf-Syntax für ``@sources``, +`@querysources`` und ``@vocabularies``. Für diese drei Endpoints wird eine neue +Aufruf-Syntax eingeführt, welche die Add-Semantik wiederspiegelt. + +.. sourcecode:: http + + GET (container)/@sources/(portal_type)/(field_name) HTTP/1.1 + + +.. sourcecode:: http + + GET (container)/@querysources/(portal_type)/(field_name) HTTP/1.1 + + +.. sourcecode:: http + + GET (container)/@vocabularies/(portal_type)/(field_name) HTTP/1.1 + + +Die Endpoints wurden überschrieben und so umgebaut, dass sie entweder einenn +oder zwei Pfad-Parameter akzeptieren: + +- 1 Parameter: Der parameter ist ``field_name``. Dies impliziert Edit intent. +- 2 Parameter: Die Parameter sind ``portal_type`` und ``field_name``, in dieser + Reihenfolge. Dies impliziert Add intent. + +Abhängig von der Aufruf-Syntax soll das Schema, von dem die Source, Querysource +oder Vocabulary geholt wird, unterschiedlich bestimmt werden: + +- Edit - es soll der ``portal_type`` des context ausgelesen werden +- Add - es soll der ``portal_type`` Pfad-Parameter verwendet werden From a79583dc4776c25d8a5888f80544c7879db0ce8f Mon Sep 17 00:00:00 2001 From: David Erni Date: Thu, 27 Aug 2020 18:56:10 +0200 Subject: [PATCH 4/4] Support querying query sources by token. --- docs/HISTORY.txt | 1 + docs/public/dev-manual/api/vocabularies.rst | 36 +++++++ opengever/api/schema/querysources.py | 28 ++++-- opengever/api/tests/test_vocabularies.py | 102 ++++++++++++++++++++ 4 files changed, 161 insertions(+), 6 deletions(-) diff --git a/docs/HISTORY.txt b/docs/HISTORY.txt index 972d0b943c..8d2f4097c6 100644 --- a/docs/HISTORY.txt +++ b/docs/HISTORY.txt @@ -6,6 +6,7 @@ Changelog - Adjust the policy generator for easier policy generation. [elioschmutz] - Provide create_forwarding action in API for documents in inboxes. [deiferni] +- Allow to query by token in @querysources API endpoint. [deiferni] 2020.8.0 (2020-08-26) diff --git a/docs/public/dev-manual/api/vocabularies.rst b/docs/public/dev-manual/api/vocabularies.rst index 20ff96799f..67889f99d8 100644 --- a/docs/public/dev-manual/api/vocabularies.rst +++ b/docs/public/dev-manual/api/vocabularies.rst @@ -38,3 +38,39 @@ oder Vocabulary geholt wird, unterschiedlich bestimmt werden: - Edit - es soll der ``portal_type`` des context ausgelesen werden - Add - es soll der ``portal_type`` Pfad-Parameter verwendet werden + + +Eine Query-Source nach token abfragen +------------------------------------- + +Zusätzlich zum schon in ``plone.restapi`` zur Verfügung gestellten Paremeter +``query`` lässt sich eine ``@querysource`` in GEVER auch nach einem bereits +bekannten ``token`` abfragen. Dieses muss als Query-String Paremeter angegeben +werden. Es darf nur entweder ``token`` oder ``query`` verwendet werden, nicht +beides zugleich. + +**Beispiel-Request**: + +.. sourcecode:: http + + GET /dossier-15/@querysources/responsible?token=hans.muster HTTP/1.1 + Accept: application/json + + +**Beispiel-Response**: + +.. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "@id": "/dossier-15/@querysources/responsible?token=hans.muster", + "items": [ + { + "title": "Hans Muster (hans.muster)", + "token": "hans.muster" + } + ], + "items_total": 1 + } diff --git a/opengever/api/schema/querysources.py b/opengever/api/schema/querysources.py index deecbc14ae..5fdbc774d7 100644 --- a/opengever/api/schema/querysources.py +++ b/opengever/api/schema/querysources.py @@ -75,15 +75,31 @@ def reply(self): "Field %r does not have an IQuerySource" % fieldname ) - if 'query' not in self.request.form: + query = self.request.form.get("query", None) + token = self.request.form.get("token", None) + if not query and not token: return self._error( - 400, "Bad Request", - u'Enumerating querysources is not supported. Please search ' - u'the source using the ?query= QS parameter' + 400, + "Bad Request", + u"Enumerating querysources is not supported. Please search " + u"the source using the ?query= or ?token = QS parameters" + ) + if query and token: + return self._error( + 400, + "Bad Request", + u"Please only search the source using either the ?query= or " + "?token = QS parameters, using both parameters at the same " + "time is unsupported" ) - query = safe_unicode(self.request.form['query']) - result = source.search(query) + if query: + result = source.search(safe_unicode(query)) + else: + try: + result = [source.getTermByToken(safe_unicode(token))] + except LookupError: + result = [] terms = [] for term in result: diff --git a/opengever/api/tests/test_vocabularies.py b/opengever/api/tests/test_vocabularies.py index f5bfb6c15a..054deca039 100644 --- a/opengever/api/tests/test_vocabularies.py +++ b/opengever/api/tests/test_vocabularies.py @@ -314,6 +314,108 @@ def test_get_keywords_querysource_for_edit(self, browser): self.assertItemsEqual([u'secret'], [item['token'] for item in response.get('items')]) + @browsing + def test_query_source_by_token_for_add(self, browser): + self.login(self.secretariat_user, browser) + url = self.query_source_url( + self.inbox, + 'responsible', + add='opengever.inbox.forwarding', + token='inbox:fa', + ) + response = browser.open( + url, + method='GET', + headers=self.api_headers, + ).json + + self.assertEqual(url, response.get('@id')) + self.assertEqual(1, response.get('items_total')) + self.assertItemsEqual([u'inbox:fa'], + [item['token'] for item in response.get('items')]) + + @browsing + def test_query_source_by_token_for_edit(self, browser): + self.login(self.secretariat_user, browser) + url = self.query_source_url( + self.task, + 'issuer', + token='nicole.kohler', + ) + response = browser.open( + url, + method='GET', + headers=self.api_headers, + ).json + + self.assertEqual(url, response.get('@id')) + self.assertEqual(1, response.get('items_total')) + self.assertItemsEqual([u'nicole.kohler'], + [item['token'] for item in response.get('items')]) + + @browsing + def test_query_source_returns_empty_list_for_inexisting_token(self, browser): + self.login(self.regular_user, browser) + url = self.query_source_url( + self.dossier, + 'responsible', + token='i.do.not.exist', + ) + response = browser.open( + url, + method='GET', + headers=self.api_headers, + ).json + + self.assertEqual(url, response.get('@id')) + self.assertEqual(0, response.get('items_total')) + self.assertItemsEqual([], response.get('items')) + + @browsing + def test_query_source_disallows_no_query_parameter(self, browser): + self.login(self.regular_user, browser) + url = self.query_source_url( + self.dossier, + 'responsible', + ) + with browser.expect_http_error(400): + browser.open( + url, + method='GET', + headers=self.api_headers, + ) + self.assertEqual( + {u'error': + {u'message': u'Enumerating querysources is not supported. ' + 'Please search the source using the ?query= or ' + '?token = QS parameters', + u'type': u'Bad Request'}}, + browser.json) + + @browsing + def test_query_source_disallows_both_query_parameter(self, browser): + self.login(self.regular_user, browser) + url = self.query_source_url( + self.dossier, + 'responsible', + query='foo', + token='bar', + ) + with browser.expect_http_error(400): + browser.open( + url, + method='GET', + headers=self.api_headers, + ) + self.assertEqual( + {u'error': + {u'message': u'Please only search the source using either ' + 'the ?query= or ?token = QS parameters, using ' + 'both parameters at the same time is ' + 'unsupported', + u'type': u'Bad Request'}}, + browser.json) + class TestGetQuerySourcesSolr(SolrIntegrationTestCase):