Skip to content

Commit

Permalink
Add querystring_search get method (#1616)
Browse files Browse the repository at this point in the history
* Add querystring_search get method.

* Add changelog.

* Fix doc generation.

* Fix doc tests.

* Fix doc test response.

* Fix black.

* Add docs.

* Update docs/source/endpoints/querystringsearch.md

Co-authored-by: Steve Piercy <web@stevepiercy.com>

* Apply suggestions from code review

Co-authored-by: Steve Piercy <web@stevepiercy.com>

* Fix quotes.

---------

Co-authored-by: Timo Stollenwerk <tisto@users.noreply.github.com>
Co-authored-by: Steve Piercy <web@stevepiercy.com>
  • Loading branch information
3 people authored Apr 6, 2023
1 parent ccecd23 commit c274e22
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 30 deletions.
43 changes: 16 additions & 27 deletions docs/source/endpoints/querystringsearch.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ myst:

The `@querystring-search` endpoint returns search results that can be filtered on search criteria.

Call the `/@querystring-search` endpoint with a `POST` request and a query in the request body:
Call the `/@querystring-search` endpoint with either a `POST` or `GET` request.

When using the `POST` request, provide a query in the request body:

```{eval-rst}
.. http:example:: curl httpie python-requests
Expand All @@ -24,6 +26,19 @@ The server will respond with the results that are filtered based on the query yo
:language: http
```

When using the `GET` request, provide the query as a JSON URL-encoded string as a parameter.

```{eval-rst}
.. http:example:: curl httpie python-requests
:request: ../../../src/plone/restapi/tests/http-examples/querystringsearch_get.req
```

The server will respond with the results that are filtered based on the query you provided:

```{literalinclude} ../../../src/plone/restapi/tests/http-examples/querystringsearch_get.resp
:language: http
```

Parameters the endpoint will accept:

- `query` - `plone.app.querystring` query, required
Expand All @@ -34,10 +49,8 @@ Parameters the endpoint will accept:
- `limit` - integer, limits the number of returned results
- `fullobjects` - boolean, if `true` then return the full objects instead of just the summary serialization


## Parameters


### Batch Start (`b_start`)

The `b_start` parameter defines the first item of the batch:
Expand All @@ -58,7 +71,6 @@ The `b_start` parameter defines the first item of the batch:
The `b_size` parameter is optional.
The default value is `0`.


### Batch Size (b_size)

The `b_size` parameter defines the number of elements a single batch returns:
Expand All @@ -79,7 +91,6 @@ The `b_size` parameter defines the number of elements a single batch returns:
The parameter is optional.
The default value is `25`.


### Sort on

The `sort_on` parameter defines the field that is used to sort the returned search results:
Expand All @@ -100,7 +111,6 @@ The `sort_on` parameter defines the field that is used to sort the returned sear
The `sort_on` parameter is optional.
The default value is `None`.


### Sort Order

The `sort_order` parameter defines the sort order when the `sort_on` field has been set:
Expand All @@ -126,7 +136,6 @@ The sort_order can be either `ascending` or `descending`.
`ascending` means from A to Z for a text field.
`reverse` is an alias equivalent to `descending`.


### Limit

Querystring `query` with a `limit` parameter:
Expand All @@ -147,7 +156,6 @@ Querystring `query` with a `limit` parameter:
The `limit` parameter is optional.
The default value is `1000`.


### Query

The `query` parameter is a list that contains an arbitrary number of `filters`:
Expand Down Expand Up @@ -176,10 +184,8 @@ The following types of filters are available:
- Date filters
- Text Filters


#### Metadata Filters


##### Creator

The `creator` of the content object.
Expand Down Expand Up @@ -212,7 +218,6 @@ You can either set the currently logged in user:
}
```


##### Shortname

`Shortname` is the ID of the object that is shown as the last part of the URL:
Expand All @@ -229,7 +234,6 @@ You can either set the currently logged in user:
}
```


##### Location

`Location` is the path of the content object on the site.
Expand Down Expand Up @@ -305,7 +309,6 @@ The path can contain a depth parameter that is separated with `::`:
}
```


##### Type

Filter by portal type:
Expand All @@ -322,7 +325,6 @@ Filter by portal type:
}
```


##### Review State

Filter results by review state:
Expand All @@ -339,7 +341,6 @@ Filter results by review state:
}
```


##### Show Inactive

Show inactive will return content objects that is expired for a given role:
Expand All @@ -356,10 +357,8 @@ Show inactive will return content objects that is expired for a given role:
}
```


#### Text Filters


##### Description

Filter content that contains a term in the Description field:
Expand All @@ -376,7 +375,6 @@ Filter content that contains a term in the Description field:
}
```


##### Searchable Text

Filter content that contains a term in the SearchableText (all searchable fields in the catalog):
Expand All @@ -393,7 +391,6 @@ Filter content that contains a term in the SearchableText (all searchable fields
}
```


##### Tag

Filter by a tag (subjects field):
Expand All @@ -410,7 +407,6 @@ Filter by a tag (subjects field):
}
```


##### Title

Filter by exact Title match:
Expand All @@ -425,10 +421,8 @@ Filter by exact Title match:
]
```


#### Date Filters


##### Creation Date

Filter by creation date:
Expand All @@ -445,7 +439,6 @@ Filter by creation date:
}
```


##### Effective Date

Filter by effective date:
Expand All @@ -463,7 +456,6 @@ Filter by effective date:
}
```


##### Event end date

Filter by event end date:
Expand All @@ -480,7 +472,6 @@ Filter by event end date:
}
```


##### Event start date

Filter by event start date:
Expand All @@ -497,7 +488,6 @@ Filter by event start date:
}
```


##### Expiration date

Filter by expiration date:
Expand All @@ -515,7 +505,6 @@ Filter by expiration date:
}
```


##### Modification date

Filter by modification date:
Expand Down
1 change: 1 addition & 0 deletions news/1616.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add querystring_search get method. @robgietema
16 changes: 16 additions & 0 deletions src/plone/restapi/services/querystringsearch/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@
permission="zope2.View"
name="@querystring-search"
/>

<plone:service
method="GET"
factory=".get.QuerystringSearchGet"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
permission="zope2.View"
name="@querystring-search"
/>

<plone:service
method="GET"
factory=".get.QuerystringSearchGet"
for="Products.CMFCore.interfaces.IContentish"
permission="zope2.View"
name="@querystring-search"
/>
</configure>
30 changes: 27 additions & 3 deletions src/plone/restapi/services/querystringsearch/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
from plone.restapi.deserializer import json_body
from plone.restapi.interfaces import ISerializeToJson
from plone.restapi.services import Service
from urllib import parse
from zope.component import getMultiAdapter

import json


zcatalog_version = get_distribution("Products.ZCatalog").version
if parse_version(zcatalog_version) >= parse_version("5.1"):
Expand All @@ -14,11 +17,14 @@
SUPPORT_NOT_UUID_QUERIES = False


class QuerystringSearchPost(Service):
class QuerystringSearch:
"""Returns the querystring search results given a p.a.querystring data."""

def reply(self):
data = json_body(self.request)
def __init__(self, context, request):
self.context = context
self.request = request

def __call__(self, data):
query = data.get("query", None)
b_start = int(data.get("b_start", 0))
b_size = int(data.get("b_size", 25))
Expand Down Expand Up @@ -60,3 +66,21 @@ def reply(self):
fullobjects=fullobjects
)
return results


class QuerystringSearchPost(Service):
"""Returns the querystring search results given a p.a.querystring data."""

def reply(self):
querystring_search = QuerystringSearch(self.context, self.request)
return querystring_search(data=json_body(self.request))


class QuerystringSearchGet(Service):
"""Returns the querystring search results given a p.a.querystring data."""

def reply(self):
querystring_search = QuerystringSearch(self.context, self.request)
return querystring_search(
data=json.loads(parse.unquote(self.request.form.get("query", "{}")))
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
GET /plone/@querystring-search?query=%257B%2522query%2522%253A%255B%257B%2522i%2522%253A%2522portal_type%2522%252C%2522o%2522%253A%2520%2522plone.app.querystring.operation.selection.any%2522%252C%2522v%2522%253A%255B%2522Document%2522%255D%257D%255D%257D HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
23 changes: 23 additions & 0 deletions src/plone/restapi/tests/http-examples/querystringsearch_get.resp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
HTTP/1.1 200 OK
Content-Type: application/json

{
"@id": "http://localhost:55001/plone/@querystring-search?query=%257B%2522query%2522%253A%255B%257B%2522i%2522%253A%2522portal_type%2522%252C%2522o%2522%253A%2520%2522plone.app.querystring.operation.selection.any%2522%252C%2522v%2522%253A%255B%2522Document%2522%255D%257D%255D%257D",
"items": [
{
"@id": "http://localhost:55001/plone/front-page",
"@type": "Document",
"description": "Congratulations! You have successfully installed Plone.",
"review_state": "private",
"title": "Welcome to Plone"
},
{
"@id": "http://localhost:55001/plone/testdocument",
"@type": "Document",
"description": "",
"review_state": "private",
"title": "Test Document"
}
],
"items_total": 2
}
12 changes: 12 additions & 0 deletions src/plone/restapi/tests/test_documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1686,6 +1686,18 @@ def test_querystringsearch_post(self):
)
save_request_and_response_for_docs("querystringsearch_post", response)

def test_querystringsearch_get(self):
query = {
"query": "%7B%22query%22%3A%5B%7B%22i%22%3A%22portal_type%22%2C%22o%22%3A%20%22plone.app.querystring.operation.selection.any%22%2C%22v%22%3A%5B%22Document%22%5D%7D%5D%7D"
}
url = "/@querystring-search"

self.portal.invokeFactory("Document", "testdocument", title="Test Document")
transaction.commit()

response = self.api_session.get(url, params=query)
save_request_and_response_for_docs("querystringsearch_get", response)

def test_system_get(self):
response = self.api_session.get("/@system")
save_request_for_docs("system_get", response)
Expand Down
12 changes: 12 additions & 0 deletions src/plone/restapi/tests/test_services_querystringsearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ def test_querystringsearch_basic(self):
self.assertEqual(len(response.json()["items"]), 1)
self.assertNotIn("effective", response.json()["items"][0])

def test_querystringsearch_basic_get(self):
response = self.api_session.get(
"/@querystring-search?query=%7B%22query%22%3A%5B%7B%22i%22%3A%22portal_type%22%2C%22o%22%3A%20%22plone.app.querystring.operation.selection.any%22%2C%22v%22%3A%5B%22Document%22%5D%7D%5D%7D"
)

self.assertEqual(response.status_code, 200)
self.assertIn("items", response.json())
self.assertIn("items_total", response.json())
self.assertEqual(response.json()["items_total"], 1)
self.assertEqual(len(response.json()["items"]), 1)
self.assertNotIn("effective", response.json()["items"][0])

def test_querystringsearch_fullobjects(self):
response = self.api_session.post(
"/@querystring-search",
Expand Down

0 comments on commit c274e22

Please sign in to comment.