Skip to content

Commit 7c26758

Browse files
committed
Support 'not' queries with @search endpoint.
1 parent 6182185 commit 7c26758

File tree

3 files changed

+126
-5
lines changed

3 files changed

+126
-5
lines changed

news/1752.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Support 'not' queries with @search endpoint.
2+
[mathias.leimgruber]

src/plone/restapi/search/query.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,16 @@ def parse_complex_query(self, idx_query):
171171
idx_query = idx_query.copy()
172172
parsed_query = {}
173173

174-
try:
175-
qv = idx_query.pop("query")
176-
parsed_query["query"] = self.parse_simple_query(qv)
177-
except KeyError:
174+
if "query" not in idx_query and "not" not in idx_query:
178175
raise QueryParsingError(
179-
"Query for index %r is missing a 'query' key!" % self.index
176+
"Query for index %r is missing a 'query' or 'not' key!" % self.index
180177
)
178+
if "query" in idx_query:
179+
qv = idx_query.pop("query")
180+
parsed_query["query"] = self.parse_simple_query(qv)
181+
if "not" in idx_query:
182+
nt = idx_query.pop("not")
183+
parsed_query["not"] = self.parse_simple_query(nt)
181184

182185
for opt_key, opt_value in idx_query.items():
183186
if opt_key in self.query_options:

src/plone/restapi/tests/test_search.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,53 @@ def test_keyword_index_str_query_and(self):
416416

417417
self.assertEqual(["/plone/folder/doc"], result_paths(response.json()))
418418

419+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
420+
def test_keyword_index_not_as_list(self):
421+
query = {"test_list_field.not": ["Keyword1", "Keyword2"]}
422+
response = self.api_session.get("/@search", params=query)
423+
424+
self.assertEqual(
425+
sorted(
426+
[
427+
"/plone",
428+
"/plone/doc-outside-folder",
429+
"/plone/folder",
430+
"/plone/folder2",
431+
"/plone/folder2/doc",
432+
]
433+
),
434+
sorted(result_paths(response.json())),
435+
)
436+
437+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
438+
def test_keyword_index_not_as_str(self):
439+
query = {"test_list_field.not": "Keyword1"}
440+
response = self.api_session.get("/@search", params=query)
441+
self.assertEqual(
442+
sorted(
443+
[
444+
"/plone",
445+
"/plone/folder",
446+
"/plone/folder/other-document",
447+
"/plone/folder2",
448+
"/plone/folder2/doc",
449+
"/plone/doc-outside-folder",
450+
]
451+
),
452+
sorted(result_paths(response.json())),
453+
)
454+
455+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
456+
def test_keyword_index_query_and_not(self):
457+
query = {
458+
"test_list_field.query": "Keyword2",
459+
"test_list_field.not": "Keyword1",
460+
}
461+
response = self.api_session.get("/@search", params=query)
462+
self.assertEqual(
463+
["/plone/folder/other-document"], result_paths(response.json())
464+
)
465+
419466
# BooleanIndex
420467

421468
def test_boolean_index_query(self):
@@ -446,6 +493,35 @@ def test_field_index_int_range_query(self):
446493

447494
self.assertEqual(["/plone/folder/doc"], result_paths(response.json()))
448495

496+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
497+
def test_field_index_not_as_list(self):
498+
query = {"portal_type.not": ["DXTestDocument", "Plone Site"]}
499+
response = self.api_session.get("/@search", params=query)
500+
501+
self.assertEqual(
502+
sorted(["/plone/folder", "/plone/folder2"]),
503+
sorted(result_paths(response.json())),
504+
)
505+
506+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
507+
def test_field_index_not_as_str(self):
508+
query = {"portal_type.not": ["DXTestDocument"]}
509+
response = self.api_session.get("/@search", params=query)
510+
511+
self.assertEqual(
512+
sorted(["/plone/folder", "/plone/folder2", "/plone"]),
513+
sorted(result_paths(response.json())),
514+
)
515+
516+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
517+
def test_field_index_query_and_not(self):
518+
query = {
519+
"id.query": ["folder", "folder2"],
520+
"id.not": "folder2",
521+
}
522+
response = self.api_session.get("/@search", params=query)
523+
self.assertEqual(["/plone/folder"], result_paths(response.json()))
524+
449525
# ExtendedPathIndex
450526

451527
def test_extended_path_index_query(self):
@@ -555,6 +631,46 @@ def test_date_index_ranged_query(self):
555631

556632
self.assertEqual(["/plone/folder/doc"], result_paths(response.json()))
557633

634+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
635+
def test_date_index_not_as_list(self):
636+
query = {
637+
"start.not": [date(1950, 1, 1).isoformat(), date(1975, 1, 1).isoformat()]
638+
}
639+
response = self.api_session.get("/@search", params=query)
640+
641+
self.assertEqual(
642+
sorted(
643+
[
644+
"/plone",
645+
"/plone/folder",
646+
"/plone/folder2",
647+
"/plone/doc-outside-folder",
648+
]
649+
),
650+
sorted(result_paths(response.json())),
651+
)
652+
653+
@unittest.skipUnless(HAS_PLONE_6, "'not' query support with Plone 6")
654+
def test_date_index_not_as_date(self):
655+
query = {
656+
"start.not": date(1950, 1, 1).isoformat(),
657+
}
658+
response = self.api_session.get("/@search", params=query)
659+
660+
self.assertEqual(
661+
sorted(
662+
[
663+
"/plone",
664+
"/plone/folder",
665+
"/plone/folder/other-document",
666+
"/plone/folder2",
667+
"/plone/folder2/doc",
668+
"/plone/doc-outside-folder",
669+
]
670+
),
671+
sorted(result_paths(response.json())),
672+
)
673+
558674
# DateRangeIndex
559675

560676
def test_date_range_index_query(self):

0 commit comments

Comments
 (0)