Skip to content

Commit 75e1b74

Browse files
Thomas Leonardsuperlevure
andcommitted
fix: backward pagination
Co-authored-by: Laurent <[email protected]>
1 parent 42a40b4 commit 75e1b74

File tree

2 files changed

+49
-28
lines changed

2 files changed

+49
-28
lines changed

graphene_django/fields.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,22 +152,24 @@ def resolve_connection(cls, connection, args, iterable, max_limit=None):
152152
array_length = iterable.count()
153153
else:
154154
array_length = len(iterable)
155-
array_slice_length = (
156-
min(max_limit, array_length) if max_limit is not None else array_length
157-
)
158155

159-
# If after is higher than list_length, connection_from_list_slice
156+
# If after is higher than array_length, connection_from_array_slice
160157
# would try to do a negative slicing which makes django throw an
161158
# AssertionError
162159
slice_start = min(
163-
get_offset_with_default(args.get("after"), -1) + 1, array_length
160+
get_offset_with_default(args.get("after"), -1) + 1,
161+
array_length,
164162
)
165-
166-
if max_limit is not None and args.get("first", None) is None:
167-
if args.get("last", None) is not None:
168-
slice_start = max(array_length - args["last"], 0)
169-
else:
170-
args["first"] = max_limit
163+
array_slice_length = array_length - slice_start
164+
165+
# Impose the maximum limit via the `first` field if neither first or last are already provided
166+
# (note that if any of them is provided they must be under max_limit otherwise an error is raised).
167+
if (
168+
max_limit is not None
169+
and args.get("first", None) is None
170+
and args.get("last", None) is None
171+
):
172+
args["first"] = max_limit
171173

172174
connection = connection_from_array_slice(
173175
iterable[slice_start:],

graphene_django/tests/test_query.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,7 @@ class Query(graphene.ObjectType):
12431243
}
12441244

12451245

1246+
@pytest.mark.parametrize("max_limit", [100, 4])
12461247
class TestBackwardPagination:
12471248
def setup_schema(self, graphene_settings, max_limit):
12481249
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = max_limit
@@ -1261,8 +1262,8 @@ class Query(graphene.ObjectType):
12611262
schema = graphene.Schema(query=Query)
12621263
return schema
12631264

1264-
def do_queries(self, schema):
1265-
# Simply last 3
1265+
def test_query_last(self, graphene_settings, max_limit):
1266+
schema = self.setup_schema(graphene_settings, max_limit=max_limit)
12661267
query_last = """
12671268
query {
12681269
allReporters(last: 3) {
@@ -1282,7 +1283,8 @@ def do_queries(self, schema):
12821283
e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
12831284
] == ["First 3", "First 4", "First 5"]
12841285

1285-
# Use a combination of first and last
1286+
def test_query_first_and_last(self, graphene_settings, max_limit):
1287+
schema = self.setup_schema(graphene_settings, max_limit=max_limit)
12861288
query_first_and_last = """
12871289
query {
12881290
allReporters(first: 4, last: 3) {
@@ -1302,7 +1304,8 @@ def do_queries(self, schema):
13021304
e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
13031305
] == ["First 1", "First 2", "First 3"]
13041306

1305-
# Use a combination of first and last and after
1307+
def test_query_first_last_and_after(self, graphene_settings, max_limit):
1308+
schema = self.setup_schema(graphene_settings, max_limit=max_limit)
13061309
query_first_last_and_after = """
13071310
query queryAfter($after: String) {
13081311
allReporters(first: 4, last: 3, after: $after) {
@@ -1317,28 +1320,44 @@ def do_queries(self, schema):
13171320

13181321
after = base64.b64encode(b"arrayconnection:0").decode()
13191322
result = schema.execute(
1320-
query_first_last_and_after, variable_values=dict(after=after)
1323+
query_first_last_and_after,
1324+
variable_values=dict(after=after),
13211325
)
13221326
assert not result.errors
13231327
assert len(result.data["allReporters"]["edges"]) == 3
13241328
assert [
13251329
e["node"]["firstName"] for e in result.data["allReporters"]["edges"]
13261330
] == ["First 2", "First 3", "First 4"]
13271331

1328-
def test_should_query(self, graphene_settings):
1329-
"""
1330-
Backward pagination should work as expected
1332+
def test_query_last_and_before(self, graphene_settings, max_limit):
1333+
schema = self.setup_schema(graphene_settings, max_limit=max_limit)
1334+
query_first_last_and_after = """
1335+
query queryAfter($before: String) {
1336+
allReporters(last: 1, before: $before) {
1337+
edges {
1338+
node {
1339+
firstName
1340+
}
1341+
}
1342+
}
1343+
}
13311344
"""
1332-
schema = self.setup_schema(graphene_settings, max_limit=100)
1333-
self.do_queries(schema)
13341345

1335-
def test_should_query_with_low_max_limit(self, graphene_settings):
1336-
"""
1337-
When doing backward pagination (using last) in combination with a max limit higher than the number of objects
1338-
we should really retrieve the last ones.
1339-
"""
1340-
schema = self.setup_schema(graphene_settings, max_limit=4)
1341-
self.do_queries(schema)
1346+
result = schema.execute(
1347+
query_first_last_and_after,
1348+
)
1349+
assert not result.errors
1350+
assert len(result.data["allReporters"]["edges"]) == 1
1351+
assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 5"
1352+
1353+
before = base64.b64encode(b"arrayconnection:5").decode()
1354+
result = schema.execute(
1355+
query_first_last_and_after,
1356+
variable_values=dict(before=before),
1357+
)
1358+
assert not result.errors
1359+
assert len(result.data["allReporters"]["edges"]) == 1
1360+
assert result.data["allReporters"]["edges"][0]["node"]["firstName"] == "First 4"
13421361

13431362

13441363
def test_should_preserve_prefetch_related(django_assert_num_queries):

0 commit comments

Comments
 (0)