Skip to content

Commit 965ebde

Browse files
committed
Merge branch 'master' into v3
2 parents 88f6ec4 + 8ddad41 commit 965ebde

File tree

10 files changed

+290
-22
lines changed

10 files changed

+290
-22
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
graphene>=2.1,<3
22
graphene-django>=2.1,<3
33
graphql-core>=2.1,<3
4-
django==3.0.3
4+
django==3.0.7

examples/cookbook/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
graphene>=2.1,<3
22
graphene-django>=2.1,<3
33
graphql-core>=2.1,<3
4-
django==3.0.3
4+
django==3.0.7
55
django-filter>=2

graphene_django/converter.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,15 @@ def dynamic_type():
281281

282282
@convert_django_field.register(ArrayField)
283283
def convert_postgres_array_to_list(field, registry=None):
284-
base_type = convert_django_field(field.base_field)
285-
if not isinstance(base_type, (List, NonNull)):
286-
base_type = type(base_type)
284+
inner_type = convert_django_field(field.base_field)
285+
if not isinstance(inner_type, (List, NonNull)):
286+
inner_type = (
287+
NonNull(type(inner_type))
288+
if inner_type.kwargs["required"]
289+
else type(inner_type)
290+
)
287291
return List(
288-
base_type,
292+
inner_type,
289293
description=get_django_field_description(field),
290294
required=not field.null,
291295
)
@@ -303,7 +307,11 @@ def convert_postgres_field_to_string(field, registry=None):
303307
def convert_postgres_range_to_string(field, registry=None):
304308
inner_type = convert_django_field(field.base_field)
305309
if not isinstance(inner_type, (List, NonNull)):
306-
inner_type = type(inner_type)
310+
inner_type = (
311+
NonNull(type(inner_type))
312+
if inner_type.kwargs["required"]
313+
else type(inner_type)
314+
)
307315
return List(
308316
inner_type,
309317
description=get_django_field_description(field),

graphene_django/debug/tests/test_query.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import graphene
2+
import pytest
23
from graphene.relay import Node
34
from graphene_django import DjangoConnectionField, DjangoObjectType
45

@@ -54,7 +55,10 @@ def resolve_reporter(self, info, **args):
5455
assert result.data == expected
5556

5657

57-
def test_should_query_nested_field():
58+
@pytest.mark.parametrize("max_limit", [None, 100])
59+
def test_should_query_nested_field(graphene_settings, max_limit):
60+
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
61+
5862
r1 = Reporter(last_name="ABA")
5963
r1.save()
6064
r2 = Reporter(last_name="Griffin")
@@ -165,7 +169,10 @@ def resolve_all_reporters(self, info, **args):
165169
assert result.data == expected
166170

167171

168-
def test_should_query_connection():
172+
@pytest.mark.parametrize("max_limit", [None, 100])
173+
def test_should_query_connection(graphene_settings, max_limit):
174+
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
175+
169176
r1 = Reporter(last_name="ABA")
170177
r1.save()
171178
r2 = Reporter(last_name="Griffin")
@@ -207,12 +214,16 @@ def resolve_all_reporters(self, info, **args):
207214
)
208215
assert not result.errors
209216
assert result.data["allReporters"] == expected["allReporters"]
217+
assert len(result.data["_debug"]["sql"]) == 2
210218
assert "COUNT" in result.data["_debug"]["sql"][0]["rawSql"]
211219
query = str(Reporter.objects.all()[:1].query)
212220
assert result.data["_debug"]["sql"][1]["rawSql"] == query
213221

214222

215-
def test_should_query_connectionfilter():
223+
@pytest.mark.parametrize("max_limit", [None, 100])
224+
def test_should_query_connectionfilter(graphene_settings, max_limit):
225+
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
226+
216227
from ...filter import DjangoFilterConnectionField
217228

218229
r1 = Reporter(last_name="ABA")
@@ -257,6 +268,7 @@ def resolve_all_reporters(self, info, **args):
257268
)
258269
assert not result.errors
259270
assert result.data["allReporters"] == expected["allReporters"]
271+
assert len(result.data["_debug"]["sql"]) == 2
260272
assert "COUNT" in result.data["_debug"]["sql"][0]["rawSql"]
261273
query = str(Reporter.objects.all()[:1].query)
262274
assert result.data["_debug"]["sql"][1]["rawSql"] == query

graphene_django/fields.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from functools import partial
22

33
from django.db.models.query import QuerySet
4-
from graphql_relay.connection.arrayconnection import connection_from_array_slice
4+
from graphql_relay.connection.arrayconnection import (
5+
connection_from_array_slice,
6+
get_offset_with_default,
7+
)
58
from promise import Promise
69

710
from graphene import NonNull
@@ -127,24 +130,37 @@ def resolve_queryset(cls, connection, queryset, info, args):
127130
return connection._meta.node.get_queryset(queryset, info)
128131

129132
@classmethod
130-
def resolve_connection(cls, connection, args, iterable):
133+
def resolve_connection(cls, connection, args, iterable, max_limit=None):
131134
iterable = maybe_queryset(iterable)
135+
132136
if isinstance(iterable, QuerySet):
133-
_len = iterable.count()
137+
list_length = iterable.count()
138+
list_slice_length = (
139+
min(max_limit, list_length) if max_limit is not None else list_length
140+
)
134141
else:
135-
_len = len(iterable)
142+
list_length = len(iterable)
143+
list_slice_length = (
144+
min(max_limit, list_length) if max_limit is not None else list_length
145+
)
146+
147+
after = get_offset_with_default(args.get("after"), -1) + 1
148+
149+
if max_limit is not None and args.get("first", None) == None:
150+
args["first"] = max_limit
151+
136152
connection = connection_from_array_slice(
137-
iterable,
153+
iterable[after:],
138154
args,
139-
slice_start=0,
140-
array_length=_len,
141-
array_slice_length=_len,
155+
slice_start=after,
156+
array_length=list_length,
157+
array_slice_length=list_slice_length,
142158
connection_type=partial(connection_adapter, connection),
143159
edge_type=connection.Edge,
144160
page_info_type=page_info_adapter,
145161
)
146162
connection.iterable = iterable
147-
connection.length = _len
163+
connection.length = list_length
148164
return connection
149165

150166
@classmethod
@@ -189,7 +205,9 @@ def connection_resolver(
189205
# thus the iterable gets refiltered by resolve_queryset
190206
# but iterable might be promise
191207
iterable = queryset_resolver(connection, iterable, info, args)
192-
on_resolve = partial(cls.resolve_connection, connection, args)
208+
on_resolve = partial(
209+
cls.resolve_connection, connection, args, max_limit=max_limit
210+
)
193211

194212
if Promise.is_thenable(iterable):
195213
return Promise.resolve(iterable).then(on_resolve)

graphene_django/filter/fields.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections import OrderedDict
22
from functools import partial
33

4+
from django.core.exceptions import ValidationError
45
from graphene.types.argument import to_arguments
56
from ..fields import DjangoConnectionField
67
from .utils import get_filtering_args_from_filterset, get_filterset_class
@@ -59,7 +60,12 @@ def resolve_queryset(
5960
connection, iterable, info, args
6061
)
6162
filter_kwargs = {k: v for k, v in args.items() if k in filtering_args}
62-
return filterset_class(data=filter_kwargs, queryset=qs, request=info.context).qs
63+
filterset = filterset_class(
64+
data=filter_kwargs, queryset=qs, request=info.context
65+
)
66+
if filterset.form.is_valid():
67+
return filterset.qs
68+
raise ValidationError(filterset.form.errors.as_json())
6369

6470
def get_queryset_resolver(self):
6571
return partial(

graphene_django/filter/tests/test_fields.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,114 @@ def test_global_id_field_relation():
412412
assert id_filter.field_class == GlobalIDFormField
413413

414414

415+
def test_global_id_field_relation_with_filter():
416+
class ReporterFilterNode(DjangoObjectType):
417+
class Meta:
418+
model = Reporter
419+
interfaces = (Node,)
420+
filter_fields = ["first_name", "articles"]
421+
422+
class ArticleFilterNode(DjangoObjectType):
423+
class Meta:
424+
model = Article
425+
interfaces = (Node,)
426+
filter_fields = ["headline", "reporter"]
427+
428+
class Query(ObjectType):
429+
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
430+
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
431+
reporter = Field(ReporterFilterNode)
432+
article = Field(ArticleFilterNode)
433+
434+
r1 = Reporter.objects.create(first_name="r1", last_name="r1", email="[email protected]")
435+
r2 = Reporter.objects.create(first_name="r2", last_name="r2", email="[email protected]")
436+
Article.objects.create(
437+
headline="a1",
438+
pub_date=datetime.now(),
439+
pub_date_time=datetime.now(),
440+
reporter=r1,
441+
editor=r1,
442+
)
443+
Article.objects.create(
444+
headline="a2",
445+
pub_date=datetime.now(),
446+
pub_date_time=datetime.now(),
447+
reporter=r2,
448+
editor=r2,
449+
)
450+
451+
# Query articles created by the reporter `r1`
452+
query = """
453+
query {
454+
allArticles (reporter: "UmVwb3J0ZXJGaWx0ZXJOb2RlOjE=") {
455+
edges {
456+
node {
457+
id
458+
}
459+
}
460+
}
461+
}
462+
"""
463+
schema = Schema(query=Query)
464+
result = schema.execute(query)
465+
assert not result.errors
466+
# We should only get back a single article
467+
assert len(result.data["allArticles"]["edges"]) == 1
468+
469+
470+
def test_global_id_field_relation_with_filter_not_valid_id():
471+
class ReporterFilterNode(DjangoObjectType):
472+
class Meta:
473+
model = Reporter
474+
interfaces = (Node,)
475+
filter_fields = ["first_name", "articles"]
476+
477+
class ArticleFilterNode(DjangoObjectType):
478+
class Meta:
479+
model = Article
480+
interfaces = (Node,)
481+
filter_fields = ["headline", "reporter"]
482+
483+
class Query(ObjectType):
484+
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
485+
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
486+
reporter = Field(ReporterFilterNode)
487+
article = Field(ArticleFilterNode)
488+
489+
r1 = Reporter.objects.create(first_name="r1", last_name="r1", email="[email protected]")
490+
r2 = Reporter.objects.create(first_name="r2", last_name="r2", email="[email protected]")
491+
Article.objects.create(
492+
headline="a1",
493+
pub_date=datetime.now(),
494+
pub_date_time=datetime.now(),
495+
reporter=r1,
496+
editor=r1,
497+
)
498+
Article.objects.create(
499+
headline="a2",
500+
pub_date=datetime.now(),
501+
pub_date_time=datetime.now(),
502+
reporter=r2,
503+
editor=r2,
504+
)
505+
506+
# Filter by the global ID that does not exist
507+
query = """
508+
query {
509+
allArticles (reporter: "fake_global_id") {
510+
edges {
511+
node {
512+
id
513+
}
514+
}
515+
}
516+
}
517+
"""
518+
schema = Schema(query=Query)
519+
result = schema.execute(query)
520+
assert "Invalid ID specified." in result.errors[0].message
521+
522+
415523
def test_global_id_multiple_field_implicit():
416524
field = DjangoFilterConnectionField(ReporterNode, fields=["pets"])
417525
filterset_class = field.filterset_class

graphene_django/tests/test_converter.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ def test_should_postgres_array_convert_list():
314314
)
315315
assert isinstance(field.type, graphene.NonNull)
316316
assert isinstance(field.type.of_type, graphene.List)
317+
assert isinstance(field.type.of_type.of_type, graphene.NonNull)
318+
assert field.type.of_type.of_type.of_type == graphene.String
319+
320+
field = assert_conversion(
321+
ArrayField, graphene.List, models.CharField(max_length=100, null=True)
322+
)
323+
assert isinstance(field.type, graphene.NonNull)
324+
assert isinstance(field.type.of_type, graphene.List)
317325
assert field.type.of_type.of_type == graphene.String
318326

319327

@@ -325,6 +333,17 @@ def test_should_postgres_array_multiple_convert_list():
325333
assert isinstance(field.type, graphene.NonNull)
326334
assert isinstance(field.type.of_type, graphene.List)
327335
assert isinstance(field.type.of_type.of_type, graphene.List)
336+
assert isinstance(field.type.of_type.of_type.of_type, graphene.NonNull)
337+
assert field.type.of_type.of_type.of_type.of_type == graphene.String
338+
339+
field = assert_conversion(
340+
ArrayField,
341+
graphene.List,
342+
ArrayField(models.CharField(max_length=100, null=True)),
343+
)
344+
assert isinstance(field.type, graphene.NonNull)
345+
assert isinstance(field.type.of_type, graphene.List)
346+
assert isinstance(field.type.of_type.of_type, graphene.List)
328347
assert field.type.of_type.of_type.of_type == graphene.String
329348

330349

@@ -345,7 +364,8 @@ def test_should_postgres_range_convert_list():
345364
field = assert_conversion(IntegerRangeField, graphene.List)
346365
assert isinstance(field.type, graphene.NonNull)
347366
assert isinstance(field.type.of_type, graphene.List)
348-
assert field.type.of_type.of_type == graphene.Int
367+
assert isinstance(field.type.of_type.of_type, graphene.NonNull)
368+
assert field.type.of_type.of_type.of_type == graphene.Int
349369

350370

351371
def test_generate_enum_name():

0 commit comments

Comments
 (0)