Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add search operator and fields arguments #321

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions grapple/types/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ def parse_literal(ast, _variables=None):
return return_value


class SearchOperatorEnum(graphene.Enum):
"""
Enum for search operator.
"""

AND = "and"
OR = "or"

def __str__(self):
# the core search parser expects the operator to be a string.
# the default __str__ returns SearchOperatorEnum.AND/OR,
# this __str__ returns the value and/or for compatibility.
return self.value


class QuerySetList(graphene.List):
"""
List type with arguments used by Django's query sets.
Expand All @@ -32,6 +47,8 @@ class QuerySetList(graphene.List):
* ``limit``
* ``offset``
* ``search_query``
* ``search_operator``
* ``search_fields``
* ``order``

:param enable_in_menu: Enable in_menu filter.
Expand All @@ -42,6 +59,10 @@ class QuerySetList(graphene.List):
:type enable_offset: bool
:param enable_search: Enable search query argument.
:type enable_search: bool
:param enable_search_fields: Enable search fields argument, enable_search must also be True
:type enable_search_fields: bool
:param enable_search_operator: Enable search operator argument, enable_search must also be True
:type enable_search_operator: bool
:param enable_order: Enable ordering via query argument.
:type enable_order: bool
"""
Expand All @@ -50,8 +71,10 @@ def __init__(self, of_type, *args, **kwargs):
enable_in_menu = kwargs.pop("enable_in_menu", False)
enable_limit = kwargs.pop("enable_limit", True)
enable_offset = kwargs.pop("enable_offset", True)
enable_search = kwargs.pop("enable_search", True)
enable_order = kwargs.pop("enable_order", True)
enable_search = kwargs.pop("enable_search", True)
enable_search_fields = kwargs.pop("enable_search_fields", True)
enable_search_operator = kwargs.pop("enable_search_operator", True)

# Check if the type is a Django model type. Do not perform the
# check if value is lazy.
Expand Down Expand Up @@ -106,6 +129,22 @@ def __init__(self, of_type, *args, **kwargs):
graphene.String,
description=_("Filter the results using Wagtail's search."),
)
if enable_search_operator:
kwargs["search_operator"] = graphene.Argument(
SearchOperatorEnum,
description=_(
"Specify search operator (and/or), see: https://docs.wagtail.org/en/stable/topics/search/searching.html#search-operator"
),
default_value="and",
)

if enable_search_fields:
kwargs["search_fields"] = graphene.Argument(
graphene.List(graphene.String),
description=_(
"A list of fields to search in. see: https://docs.wagtail.org/en/stable/topics/search/searching.html#specifying-the-fields-to-search"
),
)

if "id" not in kwargs:
kwargs["id"] = graphene.Argument(graphene.ID, description=_("Filter by ID"))
Expand Down Expand Up @@ -152,23 +191,31 @@ def PaginatedQuerySet(of_type, type_class, **kwargs):
"""
Paginated QuerySet type with arguments used by Django's query sets.

This type setts the following arguments on itself:
This type sets the following arguments on itself:

* ``id``
* ``in_menu``
* ``page``
* ``per_page``
* ``search_query``
* ``search_operator``
* ``search_fields``
* ``order``

:param enable_search: Enable search query argument.
:type enable_search: bool
:param enable_search_fields: Enable search fields argument, enable_search must also be True
:type enable_search_fields: bool
:param enable_search_operator: Enable search operator argument, enable_search must also be True
:type enable_search_operator: bool
:param enable_order: Enable ordering via query argument.
:type enable_order: bool
"""

enable_in_menu = kwargs.pop("enable_in_menu", False)
enable_search = kwargs.pop("enable_search", True)
enable_search_fields = kwargs.pop("enable_search_fields", True)
enable_search_operator = kwargs.pop("enable_search_operator", True)
enable_order = kwargs.pop("enable_order", True)
required = kwargs.get("required", False)
type_name = type_class if isinstance(type_class, str) else type_class.__name__
Expand Down Expand Up @@ -225,6 +272,22 @@ def PaginatedQuerySet(of_type, type_class, **kwargs):
kwargs["search_query"] = graphene.Argument(
graphene.String, description=_("Filter the results using Wagtail's search.")
)
if enable_search_operator:
kwargs["search_operator"] = graphene.Argument(
SearchOperatorEnum,
description=_(
"Specify search operator (and/or), see: https://docs.wagtail.org/en/stable/topics/search/searching.html#search-operator"
),
default_value="and",
)

if enable_search_fields:
kwargs["search_fields"] = graphene.Argument(
graphene.List(graphene.String),
description=_(
"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"
),
)

if "id" not in kwargs:
kwargs["id"] = graphene.Argument(graphene.ID, description=_("Filter by ID"))
Expand Down
60 changes: 52 additions & 8 deletions grapple/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from wagtail import VERSION as WAGTAIL_VERSION
from wagtail.models import Site
from wagtail.search.index import class_is_indexed
from wagtail.search.utils import parse_query_string

from .settings import grapple_settings
from .types.structures import BasePaginatedType, PaginationType
Expand Down Expand Up @@ -101,6 +102,8 @@ def resolve_queryset(
order=None,
collection=None,
in_menu=None,
search_operator="and",
search_fields=None,
**kwargs,
):
"""
Expand All @@ -122,6 +125,11 @@ def resolve_queryset(
:type order: str
:param collection: Use Wagtail's collection id to filter images or documents
:type collection: int
:param search_operator: The operator to use when combining search terms.
Defaults to "and".
:type search_operator: "and" | "or"
:param search_fields: A list of fields to search. Defaults to all fields.
:type search_fields: list
"""

qs = qs.all() if id is None else qs.filter(pk=id)
Expand Down Expand Up @@ -152,7 +160,18 @@ def resolve_queryset(
query = Query.get(search_query)
query.add_hit()

qs = qs.search(search_query, order_by_relevance=order_by_relevance)
filters, parsed_query = parse_query_string(search_query, str(search_operator))

# check if search_fields is provided in the query string if it isn't provided as a graphQL argument
if search_fields is None:
search_fields = filters.getlist("fields", None)

qs = qs.search(
parsed_query,
order_by_relevance=order_by_relevance,
operator=search_operator,
fields=search_fields,
)
if connection.vendor != "sqlite":
qs = qs.annotate_score("search_score")

Expand Down Expand Up @@ -183,17 +202,26 @@ def get_paginated_result(qs, page, per_page):
count=len(page_obj.object_list),
per_page=per_page,
current_page=page_obj.number,
prev_page=page_obj.previous_page_number()
if page_obj.has_previous()
else None,
prev_page=(
page_obj.previous_page_number() if page_obj.has_previous() else None
),
next_page=page_obj.next_page_number() if page_obj.has_next() else None,
total_pages=paginator.num_pages,
),
)


def resolve_paginated_queryset(
qs, info, page=None, per_page=None, search_query=None, id=None, order=None, **kwargs
qs,
info,
page=None,
per_page=None,
id=None,
order=None,
search_query=None,
search_operator="and",
search_fields=None,
**kwargs,
):
"""
Add page, per_page and search capabilities to the query. This contains
Expand All @@ -207,11 +235,16 @@ def resolve_paginated_queryset(
:type id: int
:param per_page: The maximum number of items to include on a page.
:type per_page: int
:param order: Order the query set using the Django QuerySet order_by format.
:type order: str
:param search_query: Using Wagtail search, exclude objects that do not match
the search query.
:type search_query: str
:param order: Order the query set using the Django QuerySet order_by format.
:type order: str
:param search_operator: The operator to use when combining search terms.
Defaults to "and".
:type search_operator: "and" | "or"
:param search_fields: A list of fields to search. Defaults to all fields.
:type search_fields: list
"""
page = int(page or 1)
per_page = min(
Expand All @@ -236,7 +269,18 @@ def resolve_paginated_queryset(
query = Query.get(search_query)
query.add_hit()

qs = qs.search(search_query, order_by_relevance=order_by_relevance)
filters, parsed_query = parse_query_string(search_query, search_operator)

# check if search_fields is provided in the query string if it isn't provided as a graphQL argument
if search_fields is None:
search_fields = filters.getlist("fields", None)

qs = qs.search(
parsed_query,
order_by_relevance=order_by_relevance,
operator=search_operator,
fields=search_fields,
)
if connection.vendor != "sqlite":
qs = qs.annotate_score("search_score")

Expand Down
Loading
Loading