From 2a39f5d8eaba3f7772c63b012a974bb9a841fb9f Mon Sep 17 00:00:00 2001 From: Charles Haro Date: Mon, 30 Oct 2017 14:35:29 -0700 Subject: [PATCH 1/4] Allow abstract Connection Class to DjangoObjectType referred to as connection_class, it will instantiate the connection from the provided class or default to graphene.Connection if not supplied --- graphene_django/tests/test_types.py | 15 ++++++++++++++- graphene_django/types.py | 8 ++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/graphene_django/tests/test_types.py b/graphene_django/tests/test_types.py index f0185d4..83d9b40 100644 --- a/graphene_django/tests/test_types.py +++ b/graphene_django/tests/test_types.py @@ -1,6 +1,6 @@ from mock import patch -from graphene import Interface, ObjectType, Schema +from graphene import Interface, ObjectType, Schema, Connection, String from graphene.relay import Node from .. import registry @@ -17,11 +17,23 @@ class Reporter(DjangoObjectType): model = ReporterModel +class ArticleConnection(Connection): + '''Article Connection''' + test = String() + + def resolve_test(): + return 'test' + + class Meta: + abstract = True + + class Article(DjangoObjectType): '''Article description''' class Meta: model = ArticleModel interfaces = (Node, ) + connection_class = ArticleConnection class RootQuery(ObjectType): @@ -74,6 +86,7 @@ type Article implements Node { type ArticleConnection { pageInfo: PageInfo! edges: [ArticleEdge]! + test: String } type ArticleEdge { diff --git a/graphene_django/types.py b/graphene_django/types.py index aeef7a6..684863a 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -45,7 +45,7 @@ class DjangoObjectType(ObjectType): @classmethod def __init_subclass_with_meta__(cls, model=None, registry=None, skip_registry=False, only_fields=(), exclude_fields=(), filter_fields=None, connection=None, - use_connection=None, interfaces=(), **options): + connection_class=None, use_connection=None, interfaces=(), **options): assert is_valid_django_model(model), ( 'You need to pass a valid Django Model in {}.Meta, received "{}".' ).format(cls.__name__, model) @@ -71,7 +71,11 @@ class DjangoObjectType(ObjectType): if use_connection and not connection: # We create the connection automatically - connection = Connection.create_type('{}Connection'.format(cls.__name__), node=cls) + if not connection_class: + connection_class = Connection + + connection = connection_class.create_type( + '{}Connection'.format(cls.__name__), node=cls) if connection is not None: assert issubclass(connection, Connection), ( From 1d0eb7b4ab634c745437d0643fa6f8f93061ac1d Mon Sep 17 00:00:00 2001 From: Charles Haro Date: Fri, 3 Nov 2017 12:10:15 -0700 Subject: [PATCH 2/4] add test to ensure .reverse() is respected --- graphene_django/filter/tests/test_fields.py | 85 ++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py index 9a0ba21..e4538c1 100644 --- a/graphene_django/filter/tests/test_fields.py +++ b/graphene_django/filter/tests/test_fields.py @@ -2,7 +2,7 @@ from datetime import datetime import pytest -from graphene import Field, ObjectType, Schema, Argument, Float +from graphene import Field, ObjectType, Schema, Argument, Float, Boolean from graphene.relay import Node from graphene_django import DjangoObjectType from graphene_django.forms import (GlobalIDFormField, @@ -509,7 +509,7 @@ def test_should_query_filter_node_double_limit_raises(): a_choice=2 ) r = Reporter.objects.create( - first_name='John', + first_name='a', last_name='Doe', email='johndoe@example.com', a_choice=1 @@ -534,3 +534,84 @@ def test_should_query_filter_node_double_limit_raises(): assert str(result.errors[0]) == ( 'Received two sliced querysets (high mark) in the connection, please slice only in one.' ) + + + +def test_order_by_is_perserved(): + class ReporterType(DjangoObjectType): + class Meta: + model = Reporter + interfaces = (Node, ) + filter_fields = () + + class Query(ObjectType): + all_reporters = DjangoFilterConnectionField(ReporterType, reverse_order=Boolean()) + + def resolve_all_reporters(self, info, reverse_order=False, **args): + reporters = Reporter.objects.order_by('first_name') + + if reverse_order: + return reporters.reverse() + + return reporters + + Reporter.objects.create( + first_name='b', + ) + r = Reporter.objects.create( + first_name='a', + ) + + schema = Schema(query=Query) + query = ''' + query NodeFilteringQuery { + allReporters(first: 1) { + edges { + node { + firstName + } + } + } + } + ''' + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'firstName': 'a', + } + }] + } + } + + result = schema.execute(query) + assert not result.errors + assert result.data == expected + + + reverse_query = ''' + query NodeFilteringQuery { + allReporters(first: 1, reverseOrder: true) { + edges { + node { + firstName + } + } + } + } + ''' + + reverse_expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'firstName': 'b', + } + }] + } + } + + reverse_result = schema.execute(reverse_query) + + assert not reverse_result.errors + assert reverse_result.data == reverse_expected From 8ec261e09b5dc16b334a0ba291c858bf54e69f54 Mon Sep 17 00:00:00 2001 From: Charles Haro Date: Fri, 3 Nov 2017 12:12:22 -0700 Subject: [PATCH 3/4] add test to show annotation is not being perserved --- graphene_django/filter/tests/test_fields.py | 59 ++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py index e4538c1..2c361d7 100644 --- a/graphene_django/filter/tests/test_fields.py +++ b/graphene_django/filter/tests/test_fields.py @@ -2,7 +2,7 @@ from datetime import datetime import pytest -from graphene import Field, ObjectType, Schema, Argument, Float, Boolean +from graphene import Field, ObjectType, Schema, Argument, Float, Boolean, String from graphene.relay import Node from graphene_django import DjangoObjectType from graphene_django.forms import (GlobalIDFormField, @@ -10,6 +10,10 @@ from graphene_django.forms import (GlobalIDFormField, from graphene_django.tests.models import Article, Pet, Reporter from graphene_django.utils import DJANGO_FILTER_INSTALLED +# for annotation test +from django.db.models import TextField, Value +from django.db.models.functions import Concat + pytestmark = [] if DJANGO_FILTER_INSTALLED: @@ -615,3 +619,56 @@ def test_order_by_is_perserved(): assert not reverse_result.errors assert reverse_result.data == reverse_expected + +def test_annotation_is_perserved(): + class ReporterType(DjangoObjectType): + full_name = String() + + def resolve_full_name(instance, info, **args): + return instance.full_name + + class Meta: + model = Reporter + interfaces = (Node, ) + filter_fields = () + + class Query(ObjectType): + all_reporters = DjangoFilterConnectionField(ReporterType) + + def resolve_all_reporters(self, info, **args): + return Reporter.objects.annotate( + full_name=Concat('first_name', Value(' '), 'last_name', output_field=TextField()) + ) + + Reporter.objects.create( + first_name='John', + last_name='Doe', + ) + + schema = Schema(query=Query) + + query = ''' + query NodeFilteringQuery { + allReporters(first: 1) { + edges { + node { + fullName + } + } + } + } + ''' + expected = { + 'allReporters': { + 'edges': [{ + 'node': { + 'fullName': 'John Doe', + } + }] + } + } + + result = schema.execute(query) + + assert not result.errors + assert result.data == expected \ No newline at end of file From aa55a86f0d8cd2bb4637ab5aa4ca6e857f51403b Mon Sep 17 00:00:00 2001 From: Charles Haro Date: Fri, 3 Nov 2017 12:12:50 -0700 Subject: [PATCH 4/4] fix bug causing order and annotations to not be perserved --- graphene_django/filter/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphene_django/filter/fields.py b/graphene_django/filter/fields.py index a80d8d7..b93977b 100644 --- a/graphene_django/filter/fields.py +++ b/graphene_django/filter/fields.py @@ -61,7 +61,7 @@ class DjangoFilterConnectionField(DjangoConnectionField): low = default_queryset.query.low_mark or queryset.query.low_mark high = default_queryset.query.high_mark or queryset.query.high_mark default_queryset.query.clear_limits() - queryset = default_queryset & queryset + queryset = queryset & default_queryset queryset.query.set_limits(low, high) return queryset