Add support for double quotes query string searches

- Fixes #9951
pull/10245/head^2
4the4ryushin 2023-01-25 16:46:07 +05:30 zatwierdzone przez LB (Ben Johnston)
rodzic df9437b4de
commit 66a086da29
5 zmienionych plików z 75 dodań i 5 usunięć

Wyświetl plik

@ -19,6 +19,7 @@ Changelog
* Switch lock/unlock side panel toggle to a switch, with more appropriate confirmation message status (Sage Abdullah)
* Ensure that changed or cleared selection from choosers will dispatch a DOM `change` event (George Sakkis)
* Add the ability to disable model indexing by setting `search_fields = []` (Daniel Kirkham)
* Enhance `wagtail.search.utils.parse_query_string` to allow inner single quotes for key/value parsing (Aman Pandey)
* Fix: Ensure `label_format` on StructBlock gracefully handles missing variables (Aadi jindal)
* Fix: Adopt a no-JavaScript and more accessible solution for the 'Reset to default' switch to Gravatar when editing user profile (Loveth Omokaro)
* Fix: Ensure `Site.get_site_root_paths` works on cache backends that do not preserve Python objects (Jaap Roes)

Wyświetl plik

@ -31,6 +31,7 @@ Support for adding custom validation logic to StreamField blocks has been formal
* Switch lock/unlock side panel toggle to a switch, with more appropriate confirmation message status (Sage Abdullah)
* Ensure that changed or cleared selection from choosers will dispatch a DOM `change` event (George Sakkis)
* Add the ability to [disable model indexing](wagtailsearch_disable_indexing) by setting `search_fields = []` (Daniel Kirkham)
* Enhance `wagtail.search.utils.parse_query_string` to allow inner single quotes for key/value parsing (Aman Pandey)
### Bug fixes

Wyświetl plik

@ -272,11 +272,14 @@ For example:
```python
>>> from wagtail.search.utils import parse_query_string
>>> filters, query = parse_query_string('my query string "this is a phrase" this-is-a:filter', operator='and')
>>> filters, query = parse_query_string('my query string "this is a phrase" this_is_a:filter', operator='and')
# Alternatively..
# filters, query = parse_query_string("my query string 'this is a phrase' this_is_a:filter", operator='and')
>>> filters
{
'this-is-a': 'filter',
'this_is_a': 'filter',
}
>>> query

Wyświetl plik

@ -235,6 +235,13 @@ class TestSeparateFiltersFromQuery(SimpleTestCase):
self.assertDictEqual(filters, {"author": "foo bar", "bar": "two beers"})
self.assertEqual(query, "hello world")
filters, query = separate_filters_from_query(
"author:'foo bar' hello world bar:'two beers'"
)
self.assertDictEqual(filters, {"author": "foo bar", "bar": "two beers"})
self.assertEqual(query, "hello world")
class TestParseQueryString(SimpleTestCase):
def test_simple_query(self):
@ -249,6 +256,11 @@ class TestParseQueryString(SimpleTestCase):
self.assertDictEqual(filters, {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
filters, query = parse_query_string("'hello world'")
self.assertDictEqual(filters, {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
def test_with_simple_and_phrase(self):
filters, query = parse_query_string('this is simple "hello world"')
@ -257,6 +269,13 @@ class TestParseQueryString(SimpleTestCase):
repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
)
filters, query = parse_query_string("this is simple 'hello world'")
self.assertDictEqual(filters, {})
self.assertEqual(
repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
)
def test_operator(self):
filters, query = parse_query_string(
'this is simple "hello world"', operator="or"
@ -270,18 +289,40 @@ class TestParseQueryString(SimpleTestCase):
),
)
filters, query = parse_query_string(
"this is simple 'hello world'", operator="or"
)
self.assertDictEqual(filters, {})
self.assertEqual(
repr(query),
repr(
Or([PlainText("this is simple", operator="or"), Phrase("hello world")])
),
)
def test_with_phrase_unclosed(self):
filters, query = parse_query_string('"hello world')
self.assertDictEqual(filters, {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
filters, query = parse_query_string("'hello world")
self.assertDictEqual(filters, {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
def test_phrase_with_filter(self):
filters, query = parse_query_string('"hello world" author:"foo bar" bar:beer')
self.assertDictEqual(filters, {"author": "foo bar", "bar": "beer"})
self.assertEqual(repr(query), repr(Phrase("hello world")))
filters, query = parse_query_string("'hello world' author:'foo bar' bar:beer")
self.assertDictEqual(filters, {"author": "foo bar", "bar": "beer"})
self.assertEqual(repr(query), repr(Phrase("hello world")))
def test_multiple_phrases(self):
filters, query = parse_query_string('"hello world" "hi earth"')
@ -289,6 +330,23 @@ class TestParseQueryString(SimpleTestCase):
repr(query), repr(And([Phrase("hello world"), Phrase("hi earth")]))
)
filters, query = parse_query_string("'hello world' 'hi earth'")
self.assertEqual(
repr(query), repr(And([Phrase("hello world"), Phrase("hi earth")]))
)
def test_mixed_phrases_with_filters(self):
filters, query = parse_query_string(
""""lord of the rings" army_1:"elves" army_2:'humans'"""
)
self.assertDictEqual(filters, {"army_1": "elves", "army_2": "humans"})
self.assertEqual(
repr(query),
repr(Phrase("lord of the rings")),
)
class TestBalancedReduce(SimpleTestCase):
# For simple values, this should behave exactly the same as Pythons reduce()

Wyświetl plik

@ -82,12 +82,14 @@ def normalise_query_string(query_string):
def separate_filters_from_query(query_string):
filters_regexp = r'(\w+):(\w+|"[^"]+")'
filters_regexp = r'(\w+):(\w+|"[^"]+"|\'[^\']+\')'
filters = {}
for match_object in re.finditer(filters_regexp, query_string):
key, value = match_object.groups()
filters[key] = value.strip('"')
filters[key] = (
value.strip('"') if value.strip('"') is not value else value.strip("'")
)
query_string = re.sub(filters_regexp, "", query_string).strip()
@ -112,7 +114,12 @@ def parse_query_string(query_string, operator=None, zero_terms=MATCH_NONE):
is_phrase = False
tokens = []
for part in query_string.split('"'):
if '"' in query_string:
parts = query_string.split('"')
else:
parts = query_string.split("'")
for part in parts:
part = part.strip()
if part: