Skip to content

Commit 3803e9a

Browse files
authored
Merge pull request #151 from graphql-python/fix-filter-and-resolver
Fixed FilterConnectionFields with limit in the FilterSet
2 parents 005bb7f + dbf0069 commit 3803e9a

File tree

5 files changed

+296
-9
lines changed

5 files changed

+296
-9
lines changed

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ after_success:
4545
fi
4646
env:
4747
matrix:
48-
- TEST_TYPE=build DJANGO_VERSION=1.10
48+
- TEST_TYPE=build DJANGO_VERSION=1.11
4949
matrix:
5050
fast_finish: true
5151
include:
@@ -57,6 +57,8 @@ matrix:
5757
env: TEST_TYPE=build DJANGO_VERSION=1.8
5858
- python: '2.7'
5959
env: TEST_TYPE=build DJANGO_VERSION=1.9
60+
- python: '2.7'
61+
env: TEST_TYPE=build DJANGO_VERSION=1.10
6062
- python: '2.7'
6163
env: TEST_TYPE=lint
6264
deploy:

graphene_django/fields.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,20 @@ def get_manager(self):
4646
else:
4747
return self.model._default_manager
4848

49-
@staticmethod
50-
def connection_resolver(resolver, connection, default_manager, root, args, context, info):
49+
@classmethod
50+
def merge_querysets(cls, default_queryset, queryset):
51+
return default_queryset & queryset
52+
53+
@classmethod
54+
def connection_resolver(cls, resolver, connection, default_manager, root, args, context, info):
5155
iterable = resolver(root, args, context, info)
5256
if iterable is None:
5357
iterable = default_manager
5458
iterable = maybe_queryset(iterable)
5559
if isinstance(iterable, QuerySet):
5660
if iterable is not default_manager:
57-
iterable &= maybe_queryset(default_manager)
61+
default_queryset = maybe_queryset(default_manager)
62+
iterable = cls.merge_querysets(default_queryset, iterable)
5863
_len = iterable.count()
5964
else:
6065
_len = len(iterable)

graphene_django/filter/fields.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,37 @@ def filtering_args(self):
4545
return get_filtering_args_from_filterset(self.filterset_class, self.node_type)
4646

4747
@staticmethod
48-
def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args,
48+
def merge_querysets(default_queryset, queryset):
49+
# There could be the case where the default queryset (returned from the filterclass)
50+
# and the resolver queryset have some limits on it.
51+
# We only would be able to apply one of those, but not both
52+
# at the same time.
53+
54+
# See related PR: https://github.com/graphql-python/graphene-django/pull/126
55+
56+
assert not (default_queryset.query.low_mark and queryset.query.low_mark), (
57+
'Received two sliced querysets (low mark) in the connection, please slice only in one.'
58+
)
59+
assert not (default_queryset.query.high_mark and queryset.query.high_mark), (
60+
'Received two sliced querysets (high mark) in the connection, please slice only in one.'
61+
)
62+
low = default_queryset.query.low_mark or queryset.query.low_mark
63+
high = default_queryset.query.high_mark or queryset.query.high_mark
64+
default_queryset.query.clear_limits()
65+
queryset = default_queryset & queryset
66+
queryset.query.set_limits(low, high)
67+
return queryset
68+
69+
@classmethod
70+
def connection_resolver(cls, resolver, connection, default_manager, filterset_class, filtering_args,
4971
root, args, context, info):
5072
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
5173
qs = filterset_class(
5274
data=filter_kwargs,
5375
queryset=default_manager.get_queryset()
5476
).qs
55-
return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info)
77+
return super(DjangoFilterConnectionField, cls).connection_resolver(
78+
resolver, connection, qs, root, args, context, info)
5679

5780
def get_resolver(self, parent_resolver):
5881
return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(),

graphene_django/filter/tests/test_fields.py

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414

1515
if DJANGO_FILTER_INSTALLED:
1616
import django_filters
17+
from django_filters import FilterSet, NumberFilter
18+
1719
from graphene_django.filter import (GlobalIDFilter, DjangoFilterConnectionField,
1820
GlobalIDMultipleChoiceFilter)
1921
from graphene_django.filter.tests.filters import ArticleFilter, PetFilter, ReporterFilter
2022
else:
21-
pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed'))
23+
pytestmark.append(pytest.mark.skipif(True, reason='django_filters not installed or not compatible'))
2224

2325
pytestmark.append(pytest.mark.django_db)
2426

@@ -365,3 +367,170 @@ class Query(ObjectType):
365367
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
366368

367369
assert ReporterFilterNode._meta.fields['child_reporters'].node_type == ReporterFilterNode
370+
371+
372+
def test_should_query_filter_node_limit():
373+
class ReporterFilter(FilterSet):
374+
limit = NumberFilter(method='filter_limit')
375+
376+
def filter_limit(self, queryset, name, value):
377+
return queryset[:value]
378+
379+
class Meta:
380+
model = Reporter
381+
fields = ['first_name', ]
382+
383+
class ReporterType(DjangoObjectType):
384+
385+
class Meta:
386+
model = Reporter
387+
interfaces = (Node, )
388+
389+
class ArticleType(DjangoObjectType):
390+
391+
class Meta:
392+
model = Article
393+
interfaces = (Node, )
394+
filter_fields = ('lang', )
395+
396+
class Query(ObjectType):
397+
all_reporters = DjangoFilterConnectionField(
398+
ReporterType,
399+
filterset_class=ReporterFilter
400+
)
401+
402+
def resolve_all_reporters(self, args, context, info):
403+
return Reporter.objects.order_by('a_choice')
404+
405+
Reporter.objects.create(
406+
first_name='Bob',
407+
last_name='Doe',
408+
409+
a_choice=2
410+
)
411+
r = Reporter.objects.create(
412+
first_name='John',
413+
last_name='Doe',
414+
415+
a_choice=1
416+
)
417+
418+
Article.objects.create(
419+
headline='Article Node 1',
420+
pub_date=datetime.now(),
421+
reporter=r,
422+
editor=r,
423+
lang='es'
424+
)
425+
Article.objects.create(
426+
headline='Article Node 2',
427+
pub_date=datetime.now(),
428+
reporter=r,
429+
editor=r,
430+
lang='en'
431+
)
432+
433+
schema = Schema(query=Query)
434+
query = '''
435+
query NodeFilteringQuery {
436+
allReporters(limit: 1) {
437+
edges {
438+
node {
439+
id
440+
firstName
441+
articles(lang: "es") {
442+
edges {
443+
node {
444+
id
445+
lang
446+
}
447+
}
448+
}
449+
}
450+
}
451+
}
452+
}
453+
'''
454+
455+
expected = {
456+
'allReporters': {
457+
'edges': [{
458+
'node': {
459+
'id': 'UmVwb3J0ZXJUeXBlOjI=',
460+
'firstName': 'John',
461+
'articles': {
462+
'edges': [{
463+
'node': {
464+
'id': 'QXJ0aWNsZVR5cGU6MQ==',
465+
'lang': 'ES'
466+
}
467+
}]
468+
}
469+
}
470+
}]
471+
}
472+
}
473+
474+
result = schema.execute(query)
475+
assert not result.errors
476+
assert result.data == expected
477+
478+
479+
def test_should_query_filter_node_double_limit_raises():
480+
class ReporterFilter(FilterSet):
481+
limit = NumberFilter(method='filter_limit')
482+
483+
def filter_limit(self, queryset, name, value):
484+
return queryset[:value]
485+
486+
class Meta:
487+
model = Reporter
488+
fields = ['first_name', ]
489+
490+
class ReporterType(DjangoObjectType):
491+
492+
class Meta:
493+
model = Reporter
494+
interfaces = (Node, )
495+
496+
class Query(ObjectType):
497+
all_reporters = DjangoFilterConnectionField(
498+
ReporterType,
499+
filterset_class=ReporterFilter
500+
)
501+
502+
def resolve_all_reporters(self, args, context, info):
503+
return Reporter.objects.order_by('a_choice')[:2]
504+
505+
Reporter.objects.create(
506+
first_name='Bob',
507+
last_name='Doe',
508+
509+
a_choice=2
510+
)
511+
r = Reporter.objects.create(
512+
first_name='John',
513+
last_name='Doe',
514+
515+
a_choice=1
516+
)
517+
518+
schema = Schema(query=Query)
519+
query = '''
520+
query NodeFilteringQuery {
521+
allReporters(limit: 1) {
522+
edges {
523+
node {
524+
id
525+
firstName
526+
}
527+
}
528+
}
529+
}
530+
'''
531+
532+
result = schema.execute(query)
533+
assert len(result.errors) == 1
534+
assert str(result.errors[0]) == (
535+
'Received two sliced querysets (high mark) in the connection, please slice only in one.'
536+
)

graphene_django/tests/test_query.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ class Meta:
4242
model = Reporter
4343
only_fields = ('id', )
4444

45-
4645
class Query(graphene.ObjectType):
4746
reporter = graphene.Field(ReporterType)
4847

@@ -360,7 +359,96 @@ class Query(graphene.ObjectType):
360359
}]
361360
}
362361
}
363-
362+
363+
result = schema.execute(query)
364+
assert not result.errors
365+
assert result.data == expected
366+
367+
368+
@pytest.mark.skipif(not DJANGO_FILTER_INSTALLED,
369+
reason="django-filter should be installed")
370+
def test_should_query_node_multiple_filtering():
371+
class ReporterType(DjangoObjectType):
372+
373+
class Meta:
374+
model = Reporter
375+
interfaces = (Node, )
376+
377+
class ArticleType(DjangoObjectType):
378+
379+
class Meta:
380+
model = Article
381+
interfaces = (Node, )
382+
filter_fields = ('lang', 'headline')
383+
384+
class Query(graphene.ObjectType):
385+
all_reporters = DjangoConnectionField(ReporterType)
386+
387+
r = Reporter.objects.create(
388+
first_name='John',
389+
last_name='Doe',
390+
391+
a_choice=1
392+
)
393+
Article.objects.create(
394+
headline='Article Node 1',
395+
pub_date=datetime.date.today(),
396+
reporter=r,
397+
editor=r,
398+
lang='es'
399+
)
400+
Article.objects.create(
401+
headline='Article Node 2',
402+
pub_date=datetime.date.today(),
403+
reporter=r,
404+
editor=r,
405+
lang='es'
406+
)
407+
Article.objects.create(
408+
headline='Article Node 3',
409+
pub_date=datetime.date.today(),
410+
reporter=r,
411+
editor=r,
412+
lang='en'
413+
)
414+
415+
schema = graphene.Schema(query=Query)
416+
query = '''
417+
query NodeFilteringQuery {
418+
allReporters {
419+
edges {
420+
node {
421+
id
422+
articles(lang: "es", headline: "Article Node 1") {
423+
edges {
424+
node {
425+
id
426+
}
427+
}
428+
}
429+
}
430+
}
431+
}
432+
}
433+
'''
434+
435+
expected = {
436+
'allReporters': {
437+
'edges': [{
438+
'node': {
439+
'id': 'UmVwb3J0ZXJUeXBlOjE=',
440+
'articles': {
441+
'edges': [{
442+
'node': {
443+
'id': 'QXJ0aWNsZVR5cGU6MQ=='
444+
}
445+
}]
446+
}
447+
}
448+
}]
449+
}
450+
}
451+
364452
result = schema.execute(query)
365453
assert not result.errors
366454
assert result.data == expected

0 commit comments

Comments
 (0)