Переглянути джерело

added support for parsing multiple queries (#10342)

4the4ryushin 1 рік тому
батько
коміт
ecce6bc1c0

+ 1 - 0
CHANGELOG.txt

@@ -6,6 +6,7 @@ Changelog
 
 
  * Add support for read-only FieldPanels (Andy Babic)
  * Add support for read-only FieldPanels (Andy Babic)
  * Mark calls to `md5` as not being used for secure purposes, to avoid flagging on FIPS-mode systems (Sean Kelly)
  * Mark calls to `md5` as not being used for secure purposes, to avoid flagging on FIPS-mode systems (Sean Kelly)
+ * Return filters from `parse_query_string` as a `QueryDict` to support multiple values (Aman Pandey)
  * Fix: Prevent choosers from failing when initial value is an unrecognised ID, e.g. when moving a page from a location where `parent_page_types` would disallow it (Dan Braghis)
  * Fix: Prevent choosers from failing when initial value is an unrecognised ID, e.g. when moving a page from a location where `parent_page_types` would disallow it (Dan Braghis)
  * Docs: Document how to add non-ModelAdmin views to a `ModelAdminGroup` (Onno Timmerman)
  * Docs: Document how to add non-ModelAdmin views to a `ModelAdminGroup` (Onno Timmerman)
  * Docs: Document how to add StructBlock data to a StreamField (Ramon Wenger)
  * Docs: Document how to add StructBlock data to a StreamField (Ramon Wenger)

+ 1 - 0
docs/releases/5.1.md

@@ -18,6 +18,7 @@ FieldPanels can now be marked as read-only with the `read_only=True` keyword arg
 ### Other features
 ### Other features
 
 
  * Mark calls to `md5` as not being used for secure purposes, to avoid flagging on FIPS-mode systems (Sean Kelly)
  * Mark calls to `md5` as not being used for secure purposes, to avoid flagging on FIPS-mode systems (Sean Kelly)
+ * Return filters from `parse_query_string` as a `QueryDict` to support multiple values (Aman Pandey)
 
 
 ### Bug fixes
 ### Bug fixes
 
 

+ 13 - 3
docs/topics/search/searching.md

@@ -269,20 +269,30 @@ support writing phrase queries by wrapping the phrase with double-quotes. In add
 add filters into the query using the colon syntax (`hello world published:yes`).
 add filters into the query using the colon syntax (`hello world published:yes`).
 
 
 These two features can be implemented using the `parse_query_string` utility function. This function takes a query string that a user
 These two features can be implemented using the `parse_query_string` utility function. This function takes a query string that a user
-typed and returns a query object and dictionary of filters:
+typed and returns a query object and a [QueryDict](django:django.http.QueryDict) of filters:
 
 
 For example:
 For example:
 
 
 ```python
 ```python
 >>> from wagtail.search.utils import parse_query_string
 >>> 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 key:value1 key:value2', operator='and')
 
 
 # Alternatively..
 # Alternatively..
-# 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 key:test1 key:test2", operator='and')
 
 
 >>> filters
 >>> filters
+<QueryDict: {'this_is_a': ['filter'], 'key': ['value1', 'value2']}>>
+
+# Get a list of values associated to a particular key using getlist method
+>>> filters.getlist('key')
+['value1', 'value2']
+
+# Get a dict representation using dict method
+# NOTE: dict method will reduce multiple values for a particular key to the last assigned value
+>>> filters.dict()
 {
 {
     'this_is_a': 'filter',
     'this_is_a': 'filter',
+    'key': 'value2'
 }
 }
 
 
 >>> query
 >>> query

+ 30 - 21
wagtail/search/tests/test_queries.py

@@ -186,37 +186,37 @@ class TestSeparateFiltersFromQuery(SimpleTestCase):
     def test_only_query(self):
     def test_only_query(self):
         filters, query = separate_filters_from_query("hello world")
         filters, query = separate_filters_from_query("hello world")
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(query, "hello world")
         self.assertEqual(query, "hello world")
 
 
     def test_filter(self):
     def test_filter(self):
         filters, query = separate_filters_from_query("author:foo")
         filters, query = separate_filters_from_query("author:foo")
 
 
-        self.assertDictEqual(filters, {"author": "foo"})
+        self.assertDictEqual(filters.dict(), {"author": "foo"})
         self.assertEqual(query, "")
         self.assertEqual(query, "")
 
 
     def test_filter_with_quotation_mark(self):
     def test_filter_with_quotation_mark(self):
         filters, query = separate_filters_from_query('author:"foo bar"')
         filters, query = separate_filters_from_query('author:"foo bar"')
 
 
-        self.assertDictEqual(filters, {"author": "foo bar"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar"})
         self.assertEqual(query, "")
         self.assertEqual(query, "")
 
 
     def test_filter_and_query(self):
     def test_filter_and_query(self):
         filters, query = separate_filters_from_query("author:foo hello world")
         filters, query = separate_filters_from_query("author:foo hello world")
 
 
-        self.assertDictEqual(filters, {"author": "foo"})
+        self.assertDictEqual(filters.dict(), {"author": "foo"})
         self.assertEqual(query, "hello world")
         self.assertEqual(query, "hello world")
 
 
     def test_filter_with_quotation_mark_and_query(self):
     def test_filter_with_quotation_mark_and_query(self):
         filters, query = separate_filters_from_query('author:"foo bar" hello world')
         filters, query = separate_filters_from_query('author:"foo bar" hello world')
 
 
-        self.assertDictEqual(filters, {"author": "foo bar"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar"})
         self.assertEqual(query, "hello world")
         self.assertEqual(query, "hello world")
 
 
     def test_filter_with_unclosed_quotation_mark_and_query(self):
     def test_filter_with_unclosed_quotation_mark_and_query(self):
         filters, query = separate_filters_from_query('author:"foo bar hello world')
         filters, query = separate_filters_from_query('author:"foo bar hello world')
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(query, 'author:"foo bar hello world')
         self.assertEqual(query, 'author:"foo bar hello world')
 
 
     def test_two_filters_and_query(self):
     def test_two_filters_and_query(self):
@@ -224,7 +224,7 @@ class TestSeparateFiltersFromQuery(SimpleTestCase):
             'author:"foo bar" hello world bar:beer'
             'author:"foo bar" hello world bar:beer'
         )
         )
 
 
-        self.assertDictEqual(filters, {"author": "foo bar", "bar": "beer"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
         self.assertEqual(query, "hello world")
         self.assertEqual(query, "hello world")
 
 
     def test_two_filters_with_quotation_marks_and_query(self):
     def test_two_filters_with_quotation_marks_and_query(self):
@@ -232,14 +232,23 @@ class TestSeparateFiltersFromQuery(SimpleTestCase):
             'author:"foo bar" hello world bar:"two beers"'
             'author:"foo bar" hello world bar:"two beers"'
         )
         )
 
 
-        self.assertDictEqual(filters, {"author": "foo bar", "bar": "two beers"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "two beers"})
         self.assertEqual(query, "hello world")
         self.assertEqual(query, "hello world")
 
 
         filters, query = separate_filters_from_query(
         filters, query = separate_filters_from_query(
             "author:'foo bar' hello world bar:'two beers'"
             "author:'foo bar' hello world bar:'two beers'"
         )
         )
 
 
-        self.assertDictEqual(filters, {"author": "foo bar", "bar": "two beers"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "two beers"})
+        self.assertEqual(query, "hello world")
+
+    def test_return_list_of_multiple_instances_for_same_filter_key(self):
+        filters, query = separate_filters_from_query(
+            'foo:test1 hello world foo:test2 foo:"test3" foo2:test4'
+        )
+
+        self.assertDictEqual(filters.dict(), {"foo": "test3", "foo2": "test4"})
+        self.assertListEqual(filters.getlist("foo"), ["test1", "test2", "test3"])
         self.assertEqual(query, "hello world")
         self.assertEqual(query, "hello world")
 
 
 
 
@@ -247,31 +256,31 @@ class TestParseQueryString(SimpleTestCase):
     def test_simple_query(self):
     def test_simple_query(self):
         filters, query = parse_query_string("hello world")
         filters, query = parse_query_string("hello world")
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(repr(query), repr(PlainText("hello world")))
         self.assertEqual(repr(query), repr(PlainText("hello world")))
 
 
     def test_with_phrase(self):
     def test_with_phrase(self):
         filters, query = parse_query_string('"hello world"')
         filters, query = parse_query_string('"hello world"')
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(repr(query), repr(Phrase("hello world")))
         self.assertEqual(repr(query), repr(Phrase("hello world")))
 
 
         filters, query = parse_query_string("'hello world'")
         filters, query = parse_query_string("'hello world'")
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(repr(query), repr(Phrase("hello world")))
         self.assertEqual(repr(query), repr(Phrase("hello world")))
 
 
     def test_with_simple_and_phrase(self):
     def test_with_simple_and_phrase(self):
         filters, query = parse_query_string('this is simple "hello world"')
         filters, query = parse_query_string('this is simple "hello world"')
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(
         self.assertEqual(
             repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
             repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
         )
         )
 
 
         filters, query = parse_query_string("this is simple 'hello world'")
         filters, query = parse_query_string("this is simple 'hello world'")
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(
         self.assertEqual(
             repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
             repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
         )
         )
@@ -281,7 +290,7 @@ class TestParseQueryString(SimpleTestCase):
             'this is simple "hello world"', operator="or"
             'this is simple "hello world"', operator="or"
         )
         )
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(
         self.assertEqual(
             repr(query),
             repr(query),
             repr(
             repr(
@@ -293,7 +302,7 @@ class TestParseQueryString(SimpleTestCase):
             "this is simple 'hello world'", operator="or"
             "this is simple 'hello world'", operator="or"
         )
         )
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(
         self.assertEqual(
             repr(query),
             repr(query),
             repr(
             repr(
@@ -304,23 +313,23 @@ class TestParseQueryString(SimpleTestCase):
     def test_with_phrase_unclosed(self):
     def test_with_phrase_unclosed(self):
         filters, query = parse_query_string('"hello world')
         filters, query = parse_query_string('"hello world')
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(repr(query), repr(Phrase("hello world")))
         self.assertEqual(repr(query), repr(Phrase("hello world")))
 
 
         filters, query = parse_query_string("'hello world")
         filters, query = parse_query_string("'hello world")
 
 
-        self.assertDictEqual(filters, {})
+        self.assertDictEqual(filters.dict(), {})
         self.assertEqual(repr(query), repr(Phrase("hello world")))
         self.assertEqual(repr(query), repr(Phrase("hello world")))
 
 
     def test_phrase_with_filter(self):
     def test_phrase_with_filter(self):
         filters, query = parse_query_string('"hello world" author:"foo bar" bar:beer')
         filters, query = parse_query_string('"hello world" author:"foo bar" bar:beer')
 
 
-        self.assertDictEqual(filters, {"author": "foo bar", "bar": "beer"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
         self.assertEqual(repr(query), repr(Phrase("hello world")))
         self.assertEqual(repr(query), repr(Phrase("hello world")))
 
 
         filters, query = parse_query_string("'hello world' author:'foo bar' bar:beer")
         filters, query = parse_query_string("'hello world' author:'foo bar' bar:beer")
 
 
-        self.assertDictEqual(filters, {"author": "foo bar", "bar": "beer"})
+        self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
         self.assertEqual(repr(query), repr(Phrase("hello world")))
         self.assertEqual(repr(query), repr(Phrase("hello world")))
 
 
     def test_multiple_phrases(self):
     def test_multiple_phrases(self):
@@ -341,7 +350,7 @@ class TestParseQueryString(SimpleTestCase):
             """"lord of the rings" army_1:"elves" army_2:'humans'"""
             """"lord of the rings" army_1:"elves" army_2:'humans'"""
         )
         )
 
 
-        self.assertDictEqual(filters, {"army_1": "elves", "army_2": "humans"})
+        self.assertDictEqual(filters.dict(), {"army_1": "elves", "army_2": "humans"})
         self.assertEqual(
         self.assertEqual(
             repr(query),
             repr(query),
             repr(Phrase("lord of the rings")),
             repr(Phrase("lord of the rings")),

+ 8 - 3
wagtail/search/utils.py

@@ -4,6 +4,7 @@ from functools import partial
 
 
 from django.apps import apps
 from django.apps import apps
 from django.db import connections
 from django.db import connections
+from django.http import QueryDict
 
 
 from wagtail.search.index import RelatedFields, SearchField
 from wagtail.search.index import RelatedFields, SearchField
 
 
@@ -84,11 +85,15 @@ def normalise_query_string(query_string):
 def separate_filters_from_query(query_string):
 def separate_filters_from_query(query_string):
     filters_regexp = r'(\w+):(\w+|"[^"]+"|\'[^\']+\')'
     filters_regexp = r'(\w+):(\w+|"[^"]+"|\'[^\']+\')'
 
 
-    filters = {}
+    filters = QueryDict(mutable=True)
     for match_object in re.finditer(filters_regexp, query_string):
     for match_object in re.finditer(filters_regexp, query_string):
         key, value = match_object.groups()
         key, value = match_object.groups()
-        filters[key] = (
-            value.strip('"') if value.strip('"') is not value else value.strip("'")
+        filters.update(
+            {
+                key: value.strip('"')
+                if value.strip('"') is not value
+                else value.strip("'")
+            }
         )
         )
 
 
     query_string = re.sub(filters_regexp, "", query_string).strip()
     query_string = re.sub(filters_regexp, "", query_string).strip()