diff --git a/src/plone/restapi/search/query.py b/src/plone/restapi/search/query.py index ded16393e9..09c4673ee6 100644 --- a/src/plone/restapi/search/query.py +++ b/src/plone/restapi/search/query.py @@ -171,13 +171,16 @@ def parse_complex_query(self, idx_query): idx_query = idx_query.copy() parsed_query = {} - try: - qv = idx_query.pop("query") - parsed_query["query"] = self.parse_simple_query(qv) - except KeyError: + if "query" not in idx_query and "not" not in idx_query: raise QueryParsingError( - "Query for index %r is missing a 'query' key!" % self.index + "Query for index %r is missing a 'query' or 'not' key!" % self.index ) + if "query" in idx_query: + qv = idx_query.pop("query") + parsed_query["query"] = self.parse_simple_query(qv) + if "not" in idx_query: + nt = idx_query.pop("not") + parsed_query["not"] = self.parse_simple_query(nt) for opt_key, opt_value in idx_query.items(): if opt_key in self.query_options: diff --git a/src/plone/restapi/tests/test_search.py b/src/plone/restapi/tests/test_search.py index e4ddb4c38d..be1c2ccb3c 100644 --- a/src/plone/restapi/tests/test_search.py +++ b/src/plone/restapi/tests/test_search.py @@ -416,6 +416,50 @@ def test_keyword_index_str_query_and(self): self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) + def test_keyword_index_not_as_list(self): + query = {"test_list_field.not": ["Keyword1", "Keyword2"]} + response = self.api_session.get("/@search", params=query) + + self.assertEqual( + sorted( + [ + "/plone", + "/plone/doc-outside-folder", + "/plone/folder", + "/plone/folder2", + "/plone/folder2/doc", + ] + ), + sorted(result_paths(response.json())), + ) + + def test_keyword_index_not_as_str(self): + query = {"test_list_field.not": "Keyword1"} + response = self.api_session.get("/@search", params=query) + self.assertEqual( + sorted( + [ + "/plone", + "/plone/folder", + "/plone/folder/other-document", + "/plone/folder2", + "/plone/folder2/doc", + "/plone/doc-outside-folder", + ] + ), + sorted(result_paths(response.json())), + ) + + def test_keyword_index_query_and_not(self): + query = { + "test_list_field.query": "Keyword2", + "test_list_field.not": "Keyword1", + } + response = self.api_session.get("/@search", params=query) + self.assertEqual( + ["/plone/folder/other-document"], result_paths(response.json()) + ) + # BooleanIndex def test_boolean_index_query(self): @@ -446,6 +490,32 @@ def test_field_index_int_range_query(self): self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) + def test_field_index_not_as_list(self): + query = {"portal_type.not": ["DXTestDocument", "Plone Site"]} + response = self.api_session.get("/@search", params=query) + + self.assertEqual( + sorted(["/plone/folder", "/plone/folder2"]), + sorted(result_paths(response.json())), + ) + + def test_field_index_not_as_str(self): + query = {"portal_type.not": ["DXTestDocument"]} + response = self.api_session.get("/@search", params=query) + + self.assertEqual( + sorted(["/plone/folder", "/plone/folder2", "/plone"]), + sorted(result_paths(response.json())), + ) + + def test_field_index_query_and_not(self): + query = { + "id.query": ["folder", "folder2"], + "id.not": "folder2", + } + response = self.api_session.get("/@search", params=query) + self.assertEqual(["/plone/folder"], result_paths(response.json())) + # ExtendedPathIndex def test_extended_path_index_query(self): @@ -555,6 +625,44 @@ def test_date_index_ranged_query(self): self.assertEqual(["/plone/folder/doc"], result_paths(response.json())) + def test_date_index_not_as_list(self): + query = { + "start.not": [date(1950, 1, 1).isoformat(), date(1975, 1, 1).isoformat()] + } + response = self.api_session.get("/@search", params=query) + + self.assertEqual( + sorted( + [ + "/plone", + "/plone/folder", + "/plone/folder2", + "/plone/doc-outside-folder", + ] + ), + sorted(result_paths(response.json())), + ) + + def test_date_index_not_as_date(self): + query = { + "start.not": date(1950, 1, 1).isoformat(), + } + response = self.api_session.get("/@search", params=query) + + self.assertEqual( + sorted( + [ + "/plone", + "/plone/folder", + "/plone/folder/other-document", + "/plone/folder2", + "/plone/folder2/doc", + "/plone/doc-outside-folder", + ] + ), + sorted(result_paths(response.json())), + ) + # DateRangeIndex def test_date_range_index_query(self):