Skip to content

Commit 3c229b6

Browse files
pcraciunoiujkimbo
andauthored
Fix hasNextPage - revert to count. Fix after (#986)
Co-authored-by: Jonathan Kim <jkimbo@gmail.com>
1 parent 3c6733e commit 3c229b6

File tree

3 files changed

+96
-52
lines changed

3 files changed

+96
-52
lines changed

graphene_django/debug/tests/test_query.py

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def resolve_reporter(self, info, **args):
5656
assert result.data == expected
5757

5858

59-
@pytest.mark.parametrize("max_limit,does_count", [(None, True), (100, False)])
60-
def test_should_query_nested_field(graphene_settings, max_limit, does_count):
59+
@pytest.mark.parametrize("max_limit", [None, 100])
60+
def test_should_query_nested_field(graphene_settings, max_limit):
6161
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
6262

6363
r1 = Reporter(last_name="ABA")
@@ -117,18 +117,11 @@ def resolve_reporter(self, info, **args):
117117
assert not result.errors
118118
query = str(Reporter.objects.order_by("pk")[:1].query)
119119
assert result.data["__debug"]["sql"][0]["rawSql"] == query
120-
if does_count:
121-
assert "COUNT" in result.data["__debug"]["sql"][1]["rawSql"]
122-
assert "tests_reporter_pets" in result.data["__debug"]["sql"][2]["rawSql"]
123-
assert "COUNT" in result.data["__debug"]["sql"][3]["rawSql"]
124-
assert "tests_reporter_pets" in result.data["__debug"]["sql"][4]["rawSql"]
125-
assert len(result.data["__debug"]["sql"]) == 5
126-
else:
127-
assert len(result.data["__debug"]["sql"]) == 3
128-
for i in range(len(result.data["__debug"]["sql"])):
129-
assert "COUNT" not in result.data["__debug"]["sql"][i]["rawSql"]
130-
assert "tests_reporter_pets" in result.data["__debug"]["sql"][1]["rawSql"]
131-
assert "tests_reporter_pets" in result.data["__debug"]["sql"][2]["rawSql"]
120+
assert "COUNT" in result.data["__debug"]["sql"][1]["rawSql"]
121+
assert "tests_reporter_pets" in result.data["__debug"]["sql"][2]["rawSql"]
122+
assert "COUNT" in result.data["__debug"]["sql"][3]["rawSql"]
123+
assert "tests_reporter_pets" in result.data["__debug"]["sql"][4]["rawSql"]
124+
assert len(result.data["__debug"]["sql"]) == 5
132125

133126
assert result.data["reporter"] == expected["reporter"]
134127

@@ -175,8 +168,8 @@ def resolve_all_reporters(self, info, **args):
175168
assert result.data == expected
176169

177170

178-
@pytest.mark.parametrize("max_limit,does_count", [(None, True), (100, False)])
179-
def test_should_query_connection(graphene_settings, max_limit, does_count):
171+
@pytest.mark.parametrize("max_limit", [None, 100])
172+
def test_should_query_connection(graphene_settings, max_limit):
180173
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
181174

182175
r1 = Reporter(last_name="ABA")
@@ -219,20 +212,14 @@ def resolve_all_reporters(self, info, **args):
219212
)
220213
assert not result.errors
221214
assert result.data["allReporters"] == expected["allReporters"]
222-
if does_count:
223-
assert len(result.data["__debug"]["sql"]) == 2
224-
assert "COUNT" in result.data["__debug"]["sql"][0]["rawSql"]
225-
query = str(Reporter.objects.all()[:1].query)
226-
assert result.data["__debug"]["sql"][1]["rawSql"] == query
227-
else:
228-
assert len(result.data["__debug"]["sql"]) == 1
229-
assert "COUNT" not in result.data["__debug"]["sql"][0]["rawSql"]
230-
query = str(Reporter.objects.all()[:1].query)
231-
assert result.data["__debug"]["sql"][0]["rawSql"] == query
232-
233-
234-
@pytest.mark.parametrize("max_limit,does_count", [(None, True), (100, False)])
235-
def test_should_query_connectionfilter(graphene_settings, max_limit, does_count):
215+
assert len(result.data["__debug"]["sql"]) == 2
216+
assert "COUNT" in result.data["__debug"]["sql"][0]["rawSql"]
217+
query = str(Reporter.objects.all()[:1].query)
218+
assert result.data["__debug"]["sql"][1]["rawSql"] == query
219+
220+
221+
@pytest.mark.parametrize("max_limit", [None, 100])
222+
def test_should_query_connectionfilter(graphene_settings, max_limit):
236223
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
237224

238225
from ...filter import DjangoFilterConnectionField
@@ -278,13 +265,7 @@ def resolve_all_reporters(self, info, **args):
278265
)
279266
assert not result.errors
280267
assert result.data["allReporters"] == expected["allReporters"]
281-
if does_count:
282-
assert len(result.data["__debug"]["sql"]) == 2
283-
assert "COUNT" in result.data["__debug"]["sql"][0]["rawSql"]
284-
query = str(Reporter.objects.all()[:1].query)
285-
assert result.data["__debug"]["sql"][1]["rawSql"] == query
286-
else:
287-
assert len(result.data["__debug"]["sql"]) == 1
288-
assert "COUNT" not in result.data["__debug"]["sql"][0]["rawSql"]
289-
query = str(Reporter.objects.all()[:1].query)
290-
assert result.data["__debug"]["sql"][0]["rawSql"] == query
268+
assert len(result.data["__debug"]["sql"]) == 2
269+
assert "COUNT" in result.data["__debug"]["sql"][0]["rawSql"]
270+
query = str(Reporter.objects.all()[:1].query)
271+
assert result.data["__debug"]["sql"][1]["rawSql"] == query

graphene_django/fields.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import six
44
from django.db.models.query import QuerySet
5-
from graphql_relay.connection.arrayconnection import connection_from_list_slice
5+
from graphql_relay.connection.arrayconnection import (
6+
connection_from_list_slice,
7+
get_offset_with_default,
8+
)
69
from promise import Promise
710

811
from graphene import NonNull
@@ -129,25 +132,32 @@ def resolve_queryset(cls, connection, queryset, info, args):
129132
@classmethod
130133
def resolve_connection(cls, connection, args, iterable, max_limit=None):
131134
iterable = maybe_queryset(iterable)
132-
# When slicing from the end, need to retrieve the iterable length.
133-
if args.get("last"):
134-
max_limit = None
135+
135136
if isinstance(iterable, QuerySet):
136-
_len = max_limit or 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+
)
137141
else:
138-
_len = max_limit or 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+
139149
connection = connection_from_list_slice(
140-
iterable,
150+
iterable[after:],
141151
args,
142-
slice_start=0,
143-
list_length=_len,
144-
list_slice_length=_len,
152+
slice_start=after,
153+
list_length=list_length,
154+
list_slice_length=list_slice_length,
145155
connection_type=connection,
146156
edge_type=connection.Edge,
147157
pageinfo_type=PageInfo,
148158
)
149159
connection.iterable = iterable
150-
connection.length = _len
160+
connection.length = list_length
151161
return connection
152162

153163
@classmethod

graphene_django/tests/test_query.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,59 @@ class Query(graphene.ObjectType):
11261126
assert len(result.data["allReporters"]["edges"]) == 4
11271127

11281128

1129+
def test_should_have_next_page(graphene_settings):
1130+
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 6
1131+
reporters = [Reporter(**kwargs) for kwargs in REPORTERS]
1132+
Reporter.objects.bulk_create(reporters)
1133+
db_reporters = Reporter.objects.all()
1134+
1135+
class ReporterType(DjangoObjectType):
1136+
class Meta:
1137+
model = Reporter
1138+
interfaces = (Node,)
1139+
1140+
class Query(graphene.ObjectType):
1141+
all_reporters = DjangoConnectionField(ReporterType)
1142+
1143+
schema = graphene.Schema(query=Query)
1144+
# Need first: 4 here to trigger the `has_next_page` logic in graphql-relay
1145+
# See `arrayconnection.py::connection_from_list_slice`:
1146+
# has_next_page=isinstance(first, int) and end_offset < upper_bound
1147+
query = """
1148+
query AllReporters($first: Int, $after: String) {
1149+
allReporters(first: $first, after: $after) {
1150+
pageInfo {
1151+
hasNextPage
1152+
endCursor
1153+
}
1154+
edges {
1155+
node {
1156+
id
1157+
}
1158+
}
1159+
}
1160+
}
1161+
"""
1162+
1163+
result = schema.execute(query, variable_values=dict(first=4))
1164+
assert not result.errors
1165+
assert len(result.data["allReporters"]["edges"]) == 4
1166+
assert result.data["allReporters"]["pageInfo"]["hasNextPage"]
1167+
1168+
last_result = result.data["allReporters"]["pageInfo"]["endCursor"]
1169+
result2 = schema.execute(query, variable_values=dict(first=4, after=last_result))
1170+
assert not result2.errors
1171+
assert len(result2.data["allReporters"]["edges"]) == 2
1172+
assert not result2.data["allReporters"]["pageInfo"]["hasNextPage"]
1173+
gql_reporters = (
1174+
result.data["allReporters"]["edges"] + result2.data["allReporters"]["edges"]
1175+
)
1176+
1177+
assert {to_global_id("ReporterType", reporter.id) for reporter in db_reporters} == {
1178+
gql_reporter["node"]["id"] for gql_reporter in gql_reporters
1179+
}
1180+
1181+
11291182
def test_should_preserve_prefetch_related(django_assert_num_queries):
11301183
class ReporterType(DjangoObjectType):
11311184
class Meta:
@@ -1172,7 +1225,7 @@ def resolve_films(root, info):
11721225
}
11731226
"""
11741227
schema = graphene.Schema(query=Query)
1175-
with django_assert_num_queries(2) as captured:
1228+
with django_assert_num_queries(3) as captured:
11761229
result = schema.execute(query)
11771230
assert not result.errors
11781231

0 commit comments

Comments
 (0)