Skip to content

Commit 95834e9

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

File tree

3 files changed

+118
-5
lines changed

3 files changed

+118
-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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,50 @@ def test_keyword_index_str_query_and(self):
416416

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

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

421465
def test_boolean_index_query(self):
@@ -446,6 +490,32 @@ def test_field_index_int_range_query(self):
446490

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

493+
def test_field_index_not_as_list(self):
494+
query = {"portal_type.not": ["DXTestDocument", "Plone Site"]}
495+
response = self.api_session.get("/@search", params=query)
496+
497+
self.assertEqual(
498+
sorted(["/plone/folder", "/plone/folder2"]),
499+
sorted(result_paths(response.json())),
500+
)
501+
502+
def test_field_index_not_as_str(self):
503+
query = {"portal_type.not": ["DXTestDocument"]}
504+
response = self.api_session.get("/@search", params=query)
505+
506+
self.assertEqual(
507+
sorted(["/plone/folder", "/plone/folder2", "/plone"]),
508+
sorted(result_paths(response.json())),
509+
)
510+
511+
def test_field_index_query_and_not(self):
512+
query = {
513+
"id.query": ["folder", "folder2"],
514+
"id.not": "folder2",
515+
}
516+
response = self.api_session.get("/@search", params=query)
517+
self.assertEqual(["/plone/folder"], result_paths(response.json()))
518+
449519
# ExtendedPathIndex
450520

451521
def test_extended_path_index_query(self):
@@ -555,6 +625,44 @@ def test_date_index_ranged_query(self):
555625

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

628+
def test_date_index_not_as_list(self):
629+
query = {
630+
"start.not": [date(1950, 1, 1).isoformat(), date(1975, 1, 1).isoformat()]
631+
}
632+
response = self.api_session.get("/@search", params=query)
633+
634+
self.assertEqual(
635+
sorted(
636+
[
637+
"/plone",
638+
"/plone/folder",
639+
"/plone/folder2",
640+
"/plone/doc-outside-folder",
641+
]
642+
),
643+
sorted(result_paths(response.json())),
644+
)
645+
646+
def test_date_index_not_as_date(self):
647+
query = {
648+
"start.not": date(1950, 1, 1).isoformat(),
649+
}
650+
response = self.api_session.get("/@search", params=query)
651+
652+
self.assertEqual(
653+
sorted(
654+
[
655+
"/plone",
656+
"/plone/folder",
657+
"/plone/folder/other-document",
658+
"/plone/folder2",
659+
"/plone/folder2/doc",
660+
"/plone/doc-outside-folder",
661+
]
662+
),
663+
sorted(result_paths(response.json())),
664+
)
665+
558666
# DateRangeIndex
559667

560668
def test_date_range_index_query(self):

0 commit comments

Comments
 (0)