Skip to content

Commit 64475f2

Browse files
committed
WIP: feat: add search_fields argument
allow for more specific search queries by specifying the search_fields. TODO: My searchFields tests don't seem to be working quite the way I'd like. I tried setting up a body field to specify alongside title, but it didn't work. At first I didn't realize why. In my customized grapple implementation, I use my own custom page type as the root for all queries rather the wagtail.model.Page that has all my common search_fields... I'm realizing that doesn't work with grapple atm as you can't specify an alternate root model. Where grapple uses a [mixin with qs=WagtailPageObjects.all()](https://github.com/torchbox/wagtail-grapple/blob/0d20a8cd8de6cceb260a1ade564effa6c2db1e79/grapple/types/pages.py#L377), I use standalone function that looks like ``` def _resolve_search_pages( info, content_types=[], page=1, per_page=10, root=TnPage.objects.live().specific(), categories=[], tags=[], ancestor=None, parent=None, in_menu=None, **kwargs ): """ root allows an alternative queryset to be passed in to allow for inheriting types to filter the queryset first. """ ``` I need to resolve this issue before this will be usable to end users... possible solutions... 1. pass a 'root' into PagesQuery to overide the base model for pages and add a way to specify the root model. 2. find a way to get this to work with .get_specific_page I don't have any other ideas at the moment.
1 parent 7463002 commit 64475f2

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

grapple/types/structures.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class QuerySetList(graphene.List):
4848
* ``offset``
4949
* ``search_query``
5050
* ``search_operator``
51+
* ``search_fields``
5152
* ``order``
5253
5354
:param enable_in_menu: Enable in_menu filter.
@@ -58,6 +59,8 @@ class QuerySetList(graphene.List):
5859
:type enable_offset: bool
5960
:param enable_search: Enable search query argument.
6061
:type enable_search: bool
62+
:param enable_search_fields: Enable search fields argument, enable_search must also be True
63+
:type enable_search_fields: bool
6164
:param enable_search_operator: Enable search operator argument, enable_search must also be True
6265
:type enable_search_operator: bool
6366
:param enable_order: Enable ordering via query argument.
@@ -70,6 +73,7 @@ def __init__(self, of_type, *args, **kwargs):
7073
enable_offset = kwargs.pop("enable_offset", True)
7174
enable_order = kwargs.pop("enable_order", True)
7275
enable_search = kwargs.pop("enable_search", True)
76+
enable_search_fields = kwargs.pop("enable_search_fields", True)
7377
enable_search_operator = kwargs.pop("enable_search_operator", True)
7478

7579
# Check if the type is a Django model type. Do not perform the
@@ -134,6 +138,14 @@ def __init__(self, of_type, *args, **kwargs):
134138
default_value="and",
135139
)
136140

141+
if enable_search_fields:
142+
kwargs["search_fields"] = graphene.Argument(
143+
graphene.List(graphene.String),
144+
description=_(
145+
"A list of fields to search in. see: https://docs.wagtail.org/en/stable/topics/search/searching.html#specifying-the-fields-to-search"
146+
),
147+
)
148+
137149
if "id" not in kwargs:
138150
kwargs["id"] = graphene.Argument(graphene.ID, description=_("Filter by ID"))
139151

@@ -187,10 +199,13 @@ def PaginatedQuerySet(of_type, type_class, **kwargs):
187199
* ``per_page``
188200
* ``search_query``
189201
* ``search_operator``
202+
* ``search_fields``
190203
* ``order``
191204
192205
:param enable_search: Enable search query argument.
193206
:type enable_search: bool
207+
:param enable_search_fields: Enable search fields argument, enable_search must also be True
208+
:type enable_search_fields: bool
194209
:param enable_search_operator: Enable search operator argument, enable_search must also be True
195210
:type enable_search_operator: bool
196211
:param enable_order: Enable ordering via query argument.
@@ -199,6 +214,7 @@ def PaginatedQuerySet(of_type, type_class, **kwargs):
199214

200215
enable_in_menu = kwargs.pop("enable_in_menu", False)
201216
enable_search = kwargs.pop("enable_search", True)
217+
enable_search_fields = kwargs.pop("enable_search_fields", True)
202218
enable_search_operator = kwargs.pop("enable_search_operator", True)
203219
enable_order = kwargs.pop("enable_order", True)
204220
required = kwargs.get("required", False)
@@ -265,6 +281,14 @@ def PaginatedQuerySet(of_type, type_class, **kwargs):
265281
default_value="and",
266282
)
267283

284+
if enable_search_fields:
285+
kwargs["search_fields"] = graphene.Argument(
286+
graphene.List(graphene.String),
287+
description=_(
288+
"A comma-separated list of fields to search in. see: https://docs.wagtail.org/en/stable/topics/search/searching.html#specifying-the-fields-to-search"
289+
),
290+
)
291+
268292
if "id" not in kwargs:
269293
kwargs["id"] = graphene.Argument(graphene.ID, description=_("Filter by ID"))
270294

grapple/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def resolve_queryset(
103103
collection=None,
104104
in_menu=None,
105105
search_operator="and",
106+
search_fields=None,
106107
**kwargs,
107108
):
108109
"""
@@ -127,6 +128,8 @@ def resolve_queryset(
127128
:param search_operator: The operator to use when combining search terms.
128129
Defaults to "and".
129130
:type search_operator: "and" | "or"
131+
:param search_fields: A list of fields to search. Defaults to all fields.
132+
:type search_fields: list
130133
"""
131134

132135
qs = qs.all() if id is None else qs.filter(pk=id)
@@ -159,10 +162,15 @@ def resolve_queryset(
159162

160163
filters, parsed_query = parse_query_string(search_query, str(search_operator))
161164

165+
# check if search_fields is provided in the query string if it isn't provided as a graphQL argument
166+
if search_fields is None:
167+
search_fields = filters.getlist("fields", None)
168+
162169
qs = qs.search(
163170
parsed_query,
164171
order_by_relevance=order_by_relevance,
165172
operator=search_operator,
173+
fields=search_fields,
166174
)
167175
if connection.vendor != "sqlite":
168176
qs = qs.annotate_score("search_score")
@@ -212,6 +220,7 @@ def resolve_paginated_queryset(
212220
order=None,
213221
search_query=None,
214222
search_operator="and",
223+
search_fields=None,
215224
**kwargs,
216225
):
217226
"""
@@ -234,6 +243,8 @@ def resolve_paginated_queryset(
234243
:param search_operator: The operator to use when combining search terms.
235244
Defaults to "and".
236245
:type search_operator: "and" | "or"
246+
:param search_fields: A list of fields to search. Defaults to all fields.
247+
:type search_fields: list
237248
"""
238249
page = int(page or 1)
239250
per_page = min(
@@ -260,10 +271,15 @@ def resolve_paginated_queryset(
260271

261272
filters, parsed_query = parse_query_string(search_query, search_operator)
262273

274+
# check if search_fields is provided in the query string if it isn't provided as a graphQL argument
275+
if search_fields is None:
276+
search_fields = filters.getlist("fields", None)
277+
263278
qs = qs.search(
264279
parsed_query,
265280
order_by_relevance=order_by_relevance,
266281
operator=search_operator,
282+
fields=search_fields,
267283
)
268284
if connection.vendor != "sqlite":
269285
qs = qs.annotate_score("search_score")

tests/test_grapple.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,52 @@ def test_search_operator_or(self):
672672
self.assertEqual(page_data[8]["title"], "Gamma Alpha")
673673
self.assertEqual(page_data[9]["title"], "Gamma Beta")
674674

675+
def test_search_fields_unset(self):
676+
query = """
677+
query {
678+
pages(searchQuery: "Sigma") {
679+
title
680+
}
681+
}
682+
"""
683+
executed = self.client.execute(query)
684+
page_data = executed["data"].get("pages")
685+
self.assertEqual(len(page_data), 6)
686+
self.assertEqual(page_data[0]["title"], "Alpha")
687+
self.assertEqual(page_data[1]["title"], "Alpha Alpha")
688+
self.assertEqual(page_data[2]["title"], "Alpha Beta")
689+
self.assertEqual(page_data[3]["title"], "Alpha Gamma")
690+
self.assertEqual(page_data[4]["title"], "Beta Alpha")
691+
self.assertEqual(page_data[5]["title"], "Gamma Alpha")
692+
693+
def test_search_fields_graphql_arg(self):
694+
query = """
695+
query {
696+
pages(searchQuery: "Sigma", searchFields: "title") {
697+
title
698+
}
699+
}
700+
"""
701+
executed = self.client.execute(query)
702+
page_data = executed["data"].get("pages")
703+
self.assertEqual(len(page_data), 0)
704+
705+
def test_search_fields_filter(self):
706+
query = """
707+
query($searchQuery: String) {
708+
pages(searchQuery: $searchQuery) {
709+
title
710+
searchScore
711+
}
712+
}
713+
"""
714+
executed = self.client.execute(
715+
query,
716+
variables={"searchQuery": "Sigma fields:title"},
717+
)
718+
page_data = executed["data"].get("pages")
719+
self.assertEqual(len(page_data), 0)
720+
675721

676722
class PageUrlPathTest(BaseGrappleTest):
677723
def _query_by_path(self, path, *, in_site=False):

tests/testapp/models/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from wagtail.fields import RichTextField, StreamField
1616
from wagtail.models import Orderable, Page
17+
from wagtail.search import index
1718
from wagtail.snippets.models import register_snippet
1819
from wagtail_headless_preview.models import HeadlessPreviewMixin
1920
from wagtailmedia.edit_handlers import MediaChooserPanel
@@ -163,6 +164,8 @@ def custom_property(self):
163164
"author": self.author.name if self.author else "Unknown",
164165
}
165166

167+
search_fields = Page.search_fields + [index.SearchField("body")]
168+
166169
graphql_fields = [
167170
GraphQLString("date", required=True),
168171
GraphQLRichText("summary"),

0 commit comments

Comments
 (0)