Skip to content

Commit 9b35f62

Browse files
authored
Merge pull request #279 from CentreForDigitalHumanities/bugfix/search-results
Fix bad merge
2 parents f74981f + 2b597c4 commit 9b35f62

5 files changed

Lines changed: 89 additions & 16 deletions

File tree

backend/event/tests/test_event_queries.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,20 @@ def test_episodes_ids_filter(graphql_client, episode, episode_2, anonymous_reque
7777
"""
7878
result = graphql_client.execute(query_2, context=anonymous_request)
7979
assert len(result["data"]["episodes"]) == 1
80+
81+
def test_episodes_search(graphql_client, episode, episode_2, anonymous_request):
82+
query = f"""
83+
query Test {{
84+
episodes(search: "") {{ id }}
85+
}}
86+
"""
87+
result = graphql_client.execute(query, context=anonymous_request)
88+
assert len(result["data"]["episodes"]) == 2
89+
90+
query_2 = f"""
91+
query Test {{
92+
episodes(search: "bert") {{ id }}
93+
}}
94+
"""
95+
result = graphql_client.execute(query_2, context=anonymous_request)
96+
assert len(result["data"]["episodes"]) == 1

backend/event/types/EpisodeType.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
from graphene import List, NonNull, ResolveInfo, Boolean
1+
from graphene import List, NonNull, ResolveInfo
22
from graphene_django import DjangoObjectType
33
from core.types.EntityDescriptionType import EntityDescriptionType
4-
from django.db.models import QuerySet, Q
4+
from django.db.models import QuerySet
55
from django_filters import FilterSet, CharFilter
66

77
from event.models import (
88
Episode, EpisodeCategory, EpisodeAgent, EpisodeSpace, EpisodeLetter, EpisodeGift,
99
)
1010
from event.types.EpisodeCategoryType import EpisodeCategoryType
11-
from user.permissions import can_edit_source, visible_sources
11+
from graphql_app.utils import search_filter
12+
1213

1314
class EpisodeFilter(FilterSet):
1415
search = CharFilter(method="search_episodes")
1516

17+
_search_fields = ['name', 'description', 'summary']
18+
1619
def search_episodes(self, queryset: QuerySet[Episode], name: str, value: str) -> QuerySet[Episode]:
1720
"""Filter episodes by name, description or summary."""
18-
return queryset.filter(
19-
Q(name__icontains=value)
20-
| Q(description__icontains=value)
21-
| Q(summary__icontains=value)
22-
)
21+
return queryset.filter(search_filter(value, self._search_fields))
22+
23+
2324
class EpisodeType(EntityDescriptionType, DjangoObjectType):
2425
categories = List(NonNull(EpisodeCategoryType), required=True)
2526
agents = List(

backend/graphql_app/utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import List
2+
from functools import reduce
3+
from operator import or_, and_
4+
5+
from django.db.models import Q
6+
7+
def search_filter(query: str, fields: List[str]) -> Q:
8+
'''Constructs a Q object for multi-term search through multiple fields'''
9+
terms = query.split()
10+
qs = [_term_filter(term, fields) for term in terms]
11+
return reduce(and_, qs) if len(qs) else Q()
12+
13+
14+
def _term_filter(term: str, fields: List[str]) -> Q:
15+
'''Constructs a Q object for a single search term through multiple fields'''
16+
qs = [
17+
Q(**{f'{field}__icontains': term})
18+
for field in fields
19+
]
20+
return reduce(or_, qs) if len(qs) else Q()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import pytest
2+
from source.models import Source
3+
from source.types.SourceType import SourceFilter
4+
5+
@pytest.fixture()
6+
def no_missing_source(db):
7+
Source.objects.get(name='MISSING SOURCE').delete()
8+
9+
@pytest.fixture()
10+
def source_2(db):
11+
return Source.objects.create(
12+
name='Pat & Mat',
13+
description_text='Shenanigans from two clumsy neighbours'
14+
)
15+
16+
@pytest.mark.parametrize('query,expected', [
17+
('', ['Sesame Street', 'Pat & Mat']),
18+
('Street', ['Sesame Street']),
19+
('street', ['Sesame Street']),
20+
('clumsy', ['Pat & Mat']),
21+
('street sesame', ['Sesame Street']),
22+
('pat clumsy', ['Pat & Mat']),
23+
('pat street', []),
24+
('nonsense', []),
25+
])
26+
def test_source_search(source, source_2, no_missing_source, query, expected):
27+
filter = SourceFilter(Source.objects.all())
28+
result = filter.search_sources(
29+
Source.objects.all(),
30+
'',
31+
query
32+
)
33+
assert set(source.name for source in result) == set(expected)

backend/source/types/SourceType.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Type, Optional
22
from graphene import Field, Int, List, NonNull, ResolveInfo, Boolean
3-
from django.db.models import QuerySet, Model, Q
3+
from django.db.models import QuerySet, Model
44
from graphene_django import DjangoObjectType
55
from django_filters import FilterSet, CharFilter, BooleanFilter
66

@@ -21,20 +21,22 @@
2121
from user.types.UserType import UserType
2222
from user.models import User
2323
from source.types.SourceImageType import SourceImageType
24+
from graphql_app.utils import search_filter
2425
from source.utils import source_contributor_ids
2526

2627
class SourceFilter(FilterSet):
2728
search = CharFilter(method="search_sources")
2829
is_public = BooleanFilter(field_name="is_public")
2930

31+
_search_fields = ['name', 'medieval_title', 'reference', 'description_text']
32+
33+
class Meta:
34+
model = Source
35+
fields = ['is_public']
36+
3037
def search_sources(self, queryset: QuerySet[Source], name: str, value: str) -> QuerySet[Source]:
31-
"""Filter sources by by searching through the name, reference, or description."""
32-
return queryset.filter(
33-
Q(name__icontains=value)
34-
| Q(medieval_title__icontains=value)
35-
| Q(reference__icontains=value)
36-
| Q(description_text__icontains=value)
37-
)
38+
"""Filter sources by searching through the name, medieval title, reference, or description."""
39+
return queryset.filter(search_filter(value, self._search_fields))
3840

3941

4042
class SourceType(DjangoObjectType):

0 commit comments

Comments
 (0)