From 7085437a12e6ae98e67d5183fc26057923b251b3 Mon Sep 17 00:00:00 2001 From: Niall Date: Sun, 5 Mar 2017 17:13:09 +0000 Subject: [PATCH 1/7] Fix filtering with a resolver and DjangoFilter filter. --- graphene_django/fields.py | 2 +- graphene_django/tests/test_query.py | 109 +++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 2519562ef..3e7c378e4 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -54,7 +54,7 @@ def connection_resolver(resolver, connection, default_manager, root, args, conte iterable = maybe_queryset(iterable) if isinstance(iterable, QuerySet): if iterable is not default_manager: - iterable &= maybe_queryset(default_manager) + iterable = maybe_queryset(default_manager) _len = iterable.count() else: _len = len(iterable) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 06b2bb3fe..486e5ffe1 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -5,12 +5,15 @@ from django.utils.functional import SimpleLazyObject from py.test import raises +from django_filters import FilterSet, NumberFilter + import graphene from graphene.relay import Node from ..utils import DJANGO_FILTER_INSTALLED from ..compat import MissingType, JSONField from ..fields import DjangoConnectionField +from ..filter.fields import DjangoFilterConnectionField from ..types import DjangoObjectType from .models import Article, Reporter @@ -42,7 +45,6 @@ class Meta: model = Reporter only_fields = ('id', ) - class Query(graphene.ObjectType): reporter = graphene.Field(ReporterType) @@ -360,7 +362,110 @@ class Query(graphene.ObjectType): }] } } - + + result = schema.execute(query) + assert not result.errors + assert result.data == expected + + +def test_should_query_filter_node_limit(): + class ReporterFilter(FilterSet): + limit = NumberFilter(method='filter_limit') + + def filter_limit(self, queryset, name, value): + return queryset[:value] + + class Meta: + model = Reporter + fields = ['first_name', ] + + class ReporterType(DjangoObjectType): + + class Meta: + model = Reporter + interfaces = (Node, ) + + class ArticleType(DjangoObjectType): + + class Meta: + model = Article + interfaces = (Node, ) + filter_fields = ('lang', ) + + class Query(graphene.ObjectType): + all_reporters = DjangoFilterConnectionField( + ReporterType, + filterset_class=ReporterFilter + ) + + def resolve_all_reporters(self, args, context, info): + return Reporter.objects.all() + + r = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) + Reporter.objects.create( + first_name='Bob', + last_name='Doe', + email='bobdoe@example.com', + a_choice=1 + ) + + Article.objects.create( + headline='Article Node 1', + pub_date=datetime.date.today(), + reporter=r, + editor=r, + lang='es' + ) + Article.objects.create( + headline='Article Node 2', + pub_date=datetime.date.today(), + reporter=r, + editor=r, + lang='en' + ) + + schema = graphene.Schema(query=Query) + query = ''' + query NodeFilteringQuery { + allReporters(limit: 1) { + edges { + node { + id + articles(lang: "es") { + edges { + node { + id + } + } + } + } + } + } + } + ''' + + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjE=', + 'articles': { + 'edges': [{ + 'node': { + 'id': 'QXJ0aWNsZVR5cGU6MQ==' + } + }] + } + } + }] + } + } + result = schema.execute(query) assert not result.errors assert result.data == expected From 67804fdc091ff000807bec247365184e10ebcea3 Mon Sep 17 00:00:00 2001 From: Niall Date: Sun, 5 Mar 2017 19:17:00 +0000 Subject: [PATCH 2/7] Add broken test --- .gitignore | 1 + examples/starwars/tests/test_mutation.py | 1 + graphene_django/tests/models.py | 1 + graphene_django/tests/test_query.py | 92 ++++++++++++++++++++++++ 4 files changed, 95 insertions(+) diff --git a/.gitignore b/.gitignore index 0b256254b..4e81c34a4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] +.virtualenv # C extensions *.so diff --git a/examples/starwars/tests/test_mutation.py b/examples/starwars/tests/test_mutation.py index aa312ff53..7c8019a3e 100644 --- a/examples/starwars/tests/test_mutation.py +++ b/examples/starwars/tests/test_mutation.py @@ -75,5 +75,6 @@ def test_mutations(): } } result = schema.execute(query) + print(result.data) assert not result.errors assert result.data == expected diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index 0c62f28ba..03ca59d88 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -45,6 +45,7 @@ class Article(models.Model): ], default='es') importance = models.IntegerField('Importance', null=True, blank=True, choices=[(1, u'Very important'), (2, u'Not as important')]) + tag = models.CharField(max_length=100) def __str__(self): # __unicode__ on Python 2 return self.headline diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 486e5ffe1..1f4809b14 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -368,6 +368,98 @@ class Query(graphene.ObjectType): assert result.data == expected +@pytest.mark.skipif(not DJANGO_FILTER_INSTALLED, + reason="django-filter should be installed") +def test_should_query_node_multiple_filtering(): + class ReporterType(DjangoObjectType): + + class Meta: + model = Reporter + interfaces = (Node, ) + + class ArticleType(DjangoObjectType): + + class Meta: + model = Article + interfaces = (Node, ) + filter_fields = ('lang', 'tag') + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(ReporterType) + + r = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', + a_choice=1 + ) + Article.objects.create( + headline='Article Node 1', + pub_date=datetime.date.today(), + reporter=r, + editor=r, + lang='es', + tag='one' + ) + Article.objects.create( + headline='Article Node 2', + pub_date=datetime.date.today(), + reporter=r, + editor=r, + lang='en', + tag='two' + ) + Article.objects.create( + headline='Article Node 3', + pub_date=datetime.date.today(), + reporter=r, + editor=r, + lang='en', + tag='three' + ) + + schema = graphene.Schema(query=Query) + query = ''' + query NodeFilteringQuery { + allReporters { + edges { + node { + id + articles(lang: "es", tag: "two") { + edges { + node { + id + } + } + } + } + } + } + } + ''' + + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'id': 'UmVwb3J0ZXJUeXBlOjE=', + 'articles': { + 'edges': [{ + 'node': { + 'id': 'QXJ0aWNsZVR5cGU6MQ==' + } + }] + } + } + }] + } + } + + result = schema.execute(query) + assert not result.errors + assert result.data == expected + + def test_should_query_filter_node_limit(): class ReporterFilter(FilterSet): limit = NumberFilter(method='filter_limit') From 69457cffdf2041b69fc46ca9fdfc6ae131fd51c9 Mon Sep 17 00:00:00 2001 From: Niall Date: Mon, 6 Mar 2017 18:13:40 +0000 Subject: [PATCH 3/7] Attempt fix. Breaks tests --- graphene_django/fields.py | 4 ++-- graphene_django/filter/fields.py | 27 ++++++++++++++++++++++----- graphene_django/tests/test_query.py | 13 +++++-------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 3e7c378e4..9ba999c9f 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -53,8 +53,8 @@ def connection_resolver(resolver, connection, default_manager, root, args, conte iterable = default_manager iterable = maybe_queryset(iterable) if isinstance(iterable, QuerySet): - if iterable is not default_manager: - iterable = maybe_queryset(default_manager) + if default_manager is not None and iterable is not default_manager: + iterable &= maybe_queryset(default_manager) _len = iterable.count() else: _len = len(iterable) diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py index 363e1d9ce..1b2c1c871 100644 --- a/graphene_django/filter/fields.py +++ b/graphene_django/filter/fields.py @@ -1,6 +1,8 @@ from collections import OrderedDict from functools import partial +from django.db.models.query import QuerySet + # from graphene.relay import is_node from graphene.types.argument import to_arguments from ..fields import DjangoConnectionField @@ -44,15 +46,30 @@ def filterset_class(self): def filtering_args(self): return get_filtering_args_from_filterset(self.filterset_class, self.node_type) + # @staticmethod + # def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args, + # root, args, context, info): + # filter_kwargs = {k: v for k, v in args.items() if k in filtering_args} + # qs = filterset_class( + # data=filter_kwargs, + # queryset=default_manager.get_queryset() + # ).qs + # return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) + @staticmethod def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args, root, args, context, info): filter_kwargs = {k: v for k, v in args.items() if k in filtering_args} - qs = filterset_class( - data=filter_kwargs, - queryset=default_manager.get_queryset() - ).qs - return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) + + def new_resolver(root, args, context, info): + qs = resolver(root, args, context, info) + if qs is None or not isinstance(qs, QuerySet): + qs = default_manager.get_queryset() + qs = filterset_class(data=filter_kwargs, queryset=qs).qs + + return qs + + return DjangoConnectionField.connection_resolver(new_resolver, connection, None, root, args, context, info) def get_resolver(self, parent_resolver): return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(), diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 1f4809b14..6d2f8c8ae 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -382,7 +382,7 @@ class ArticleType(DjangoObjectType): class Meta: model = Article interfaces = (Node, ) - filter_fields = ('lang', 'tag') + filter_fields = ('lang', 'headline') class Query(graphene.ObjectType): all_reporters = DjangoConnectionField(ReporterType) @@ -398,24 +398,21 @@ class Query(graphene.ObjectType): pub_date=datetime.date.today(), reporter=r, editor=r, - lang='es', - tag='one' + lang='es' ) Article.objects.create( headline='Article Node 2', pub_date=datetime.date.today(), reporter=r, editor=r, - lang='en', - tag='two' + lang='en' ) Article.objects.create( headline='Article Node 3', pub_date=datetime.date.today(), reporter=r, editor=r, - lang='en', - tag='three' + lang='en' ) schema = graphene.Schema(query=Query) @@ -425,7 +422,7 @@ class Query(graphene.ObjectType): edges { node { id - articles(lang: "es", tag: "two") { + articles(lang: "es", headline: "Article Node 2") { edges { node { id From e2284fefb52954b4b012db1d565931f087aaf3d0 Mon Sep 17 00:00:00 2001 From: Niall Date: Mon, 6 Mar 2017 18:20:31 +0000 Subject: [PATCH 4/7] Clean up --- .gitignore | 1 - examples/starwars/tests/test_mutation.py | 1 - graphene_django/tests/models.py | 1 - 3 files changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 4e81c34a4..0b256254b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] -.virtualenv # C extensions *.so diff --git a/examples/starwars/tests/test_mutation.py b/examples/starwars/tests/test_mutation.py index 7c8019a3e..aa312ff53 100644 --- a/examples/starwars/tests/test_mutation.py +++ b/examples/starwars/tests/test_mutation.py @@ -75,6 +75,5 @@ def test_mutations(): } } result = schema.execute(query) - print(result.data) assert not result.errors assert result.data == expected diff --git a/graphene_django/tests/models.py b/graphene_django/tests/models.py index 03ca59d88..0c62f28ba 100644 --- a/graphene_django/tests/models.py +++ b/graphene_django/tests/models.py @@ -45,7 +45,6 @@ class Article(models.Model): ], default='es') importance = models.IntegerField('Importance', null=True, blank=True, choices=[(1, u'Very important'), (2, u'Not as important')]) - tag = models.CharField(max_length=100) def __str__(self): # __unicode__ on Python 2 return self.headline From fda876fdc20756c712ffa579f8f37ffb8c158c3b Mon Sep 17 00:00:00 2001 From: Niall Date: Mon, 6 Mar 2017 19:41:04 +0000 Subject: [PATCH 5/7] Long-winded intersection using sets --- graphene_django/fields.py | 8 +++++--- graphene_django/filter/fields.py | 27 +++++---------------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 9ba999c9f..c7d996812 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -53,9 +53,11 @@ def connection_resolver(resolver, connection, default_manager, root, args, conte iterable = default_manager iterable = maybe_queryset(iterable) if isinstance(iterable, QuerySet): - if default_manager is not None and iterable is not default_manager: - iterable &= maybe_queryset(default_manager) - _len = iterable.count() + if iterable is not default_manager: + iterable = list(set(iterable).intersection(maybe_queryset(default_manager))) + _len = len(iterable) + else: + _len = iterable.count() else: _len = len(iterable) connection = connection_from_list_slice( diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py index 1b2c1c871..363e1d9ce 100644 --- a/graphene_django/filter/fields.py +++ b/graphene_django/filter/fields.py @@ -1,8 +1,6 @@ from collections import OrderedDict from functools import partial -from django.db.models.query import QuerySet - # from graphene.relay import is_node from graphene.types.argument import to_arguments from ..fields import DjangoConnectionField @@ -46,30 +44,15 @@ def filterset_class(self): def filtering_args(self): return get_filtering_args_from_filterset(self.filterset_class, self.node_type) - # @staticmethod - # def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args, - # root, args, context, info): - # filter_kwargs = {k: v for k, v in args.items() if k in filtering_args} - # qs = filterset_class( - # data=filter_kwargs, - # queryset=default_manager.get_queryset() - # ).qs - # return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) - @staticmethod def connection_resolver(resolver, connection, default_manager, filterset_class, filtering_args, root, args, context, info): filter_kwargs = {k: v for k, v in args.items() if k in filtering_args} - - def new_resolver(root, args, context, info): - qs = resolver(root, args, context, info) - if qs is None or not isinstance(qs, QuerySet): - qs = default_manager.get_queryset() - qs = filterset_class(data=filter_kwargs, queryset=qs).qs - - return qs - - return DjangoConnectionField.connection_resolver(new_resolver, connection, None, root, args, context, info) + qs = filterset_class( + data=filter_kwargs, + queryset=default_manager.get_queryset() + ).qs + return DjangoConnectionField.connection_resolver(resolver, connection, qs, root, args, context, info) def get_resolver(self, parent_resolver): return partial(self.connection_resolver, parent_resolver, self.type, self.get_manager(), From 7210e308ec8476360e5aa1bee797e594d85f45dd Mon Sep 17 00:00:00 2001 From: Niall Date: Mon, 6 Mar 2017 20:00:01 +0000 Subject: [PATCH 6/7] Fix test --- graphene_django/tests/test_query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 6d2f8c8ae..45532596d 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -405,7 +405,7 @@ class Query(graphene.ObjectType): pub_date=datetime.date.today(), reporter=r, editor=r, - lang='en' + lang='es' ) Article.objects.create( headline='Article Node 3', @@ -422,7 +422,7 @@ class Query(graphene.ObjectType): edges { node { id - articles(lang: "es", headline: "Article Node 2") { + articles(lang: "es", headline: "Article Node 1") { edges { node { id From 2117cb2b017334adc367098baa8d94e5cecdbdb8 Mon Sep 17 00:00:00 2001 From: Niall Date: Mon, 6 Mar 2017 20:19:39 +0000 Subject: [PATCH 7/7] Example for order_by being ignored --- graphene_django/tests/test_query.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 45532596d..75eb8c883 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -488,18 +488,18 @@ class Query(graphene.ObjectType): ) def resolve_all_reporters(self, args, context, info): - return Reporter.objects.all() + return Reporter.objects.order_by('a_choice') - r = Reporter.objects.create( - first_name='John', - last_name='Doe', - email='johndoe@example.com', - a_choice=1 - ) Reporter.objects.create( first_name='Bob', last_name='Doe', email='bobdoe@example.com', + a_choice=2 + ) + r = Reporter.objects.create( + first_name='John', + last_name='Doe', + email='johndoe@example.com', a_choice=1 )