Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to query by token in @querysources API endpoint. #6608

Merged
merged 4 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/HISTORY.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/public/dev-manual/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Inhalt:
extract_attachments.rst
trash.rst
workflow.rst
vocabularies.rst
scanin.rst
content_types.rst
metadata.rst
Expand Down
2 changes: 1 addition & 1 deletion docs/public/dev-manual/api/schemas/ftw.mail.mail.inc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
:Feldname: :field-title:`Bearbeitungsinformation`
:Datentyp: ``Text``

:Default: ""

:Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
:Feldname: :field-title:`Bearbeitungsinformation`
:Datentyp: ``Text``

:Default: ""

:Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@
:Feldname: :field-title:`Bearbeitungsinformation`
:Datentyp: ``Text``

:Default: ""

:Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
:Feldname: :field-title:`Beschreibung`
:Datentyp: ``Text``

:Default: null

:Beschreibung: Eine kurze Beschreibung des Inhalts.


Expand Down Expand Up @@ -129,7 +129,7 @@
:Feldname: :field-title:`Bearbeitungsinformation`
:Datentyp: ``Text``

:Default: ""

:Beschreibung: Datum Gesuch, Gesuchsteller, Datum Entscheid, Verweis auf GEVER-Gesuchdossier


Expand Down
76 changes: 76 additions & 0 deletions docs/public/dev-manual/api/vocabularies.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.. _vocabularies:

Vokabulare
==========

Die Grundlagen für den Umgang mit Vokabularen und Sourcen findet man in der
`plone.restapi Dokumentation Vocabularies and Sources <https://plonerestapi.readthedocs.io/en/latest/vocabularies.html>`_.

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


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
}
28 changes: 22 additions & 6 deletions opengever/api/schema/querysources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
119 changes: 113 additions & 6 deletions opengever/api/tests/test_vocabularies.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -308,14 +314,115 @@ 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):

@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',
Expand Down
17 changes: 16 additions & 1 deletion opengever/testing/integration_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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())

Expand Down