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

Add querystring_search get method #1616

Merged
merged 10 commits into from
Apr 6, 2023
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
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):
robgietema marked this conversation as resolved.
Show resolved Hide resolved
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