Skip to content

Support use first and last at same time as offset #1022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
51 changes: 34 additions & 17 deletions graphene_django/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,26 +179,43 @@ def connection_resolver(
info,
**args
):
# By current `connection_from_list_slice` implementation,
# `last` means last N items in the selection,
# and when use `last` with `first`, `last` means last N items in first N items.

first = args.get("first")
last = args.get("last")

if enforce_first_or_last:
assert first or last, (
"You must provide a `first` or `last` value to properly paginate the `{}` connection."
).format(info.field_name)

if max_limit:
if first:
assert first <= max_limit, (
"Requesting {} records on the `{}` connection exceeds the `first` limit of {} records."
).format(first, info.field_name, max_limit)
args["first"] = min(first, max_limit)

if last:
assert last <= max_limit, (
"Requesting {} records on the `{}` connection exceeds the `last` limit of {} records."
).format(last, info.field_name, max_limit)
args["last"] = min(last, max_limit)
if first is not None and first <= 0:
raise ValueError(
"`first` argument must be positive, got `{first}`".format(first=first)
)
if last is not None and last <= 0:
raise ValueError(
"`last` argument must be positive, got `{last}`".format(last=last)
)
if enforce_first_or_last and not (first or last):
raise ValueError(
"You must provide a `first` or `last` value "
"to properly paginate the `{info.field_name}` connection.".format(
info=info
)
)

if not max_limit:
pass
elif first is None and last is None:
args["first"] = max_limit
else:
count = min(i for i in (first, last) if i)
if count > max_limit:
raise ValueError(
(
"Requesting {count} records "
"on the `{info.field_name}` connection "
"exceeds the limit of {max_limit} records."
).format(count=count, info=info, max_limit=max_limit)
)

# eventually leads to DjangoObjectType's get_queryset (accepts queryset)
# or a resolve_foo (does not accept queryset)
Expand Down
178 changes: 175 additions & 3 deletions graphene_django/tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from ..compat import JSONField, MissingType
from ..fields import DjangoConnectionField
from ..settings import graphene_settings
from ..types import DjangoObjectType
from ..utils import DJANGO_FILTER_INSTALLED
from .models import Article, CNNReporter, Film, FilmDetails, Reporter
Expand Down Expand Up @@ -681,7 +682,7 @@ class Query(graphene.ObjectType):
assert len(result.errors) == 1
assert str(result.errors[0]) == (
"Requesting 101 records on the `allReporters` connection "
"exceeds the `first` limit of 100 records."
"exceeds the limit of 100 records."
)
assert result.data == expected

Expand Down Expand Up @@ -722,11 +723,182 @@ class Query(graphene.ObjectType):
assert len(result.errors) == 1
assert str(result.errors[0]) == (
"Requesting 101 records on the `allReporters` connection "
"exceeds the `last` limit of 100 records."
"exceeds the limit of 100 records."
)
assert result.data == expected


def test_should_not_error_if_last_and_first_not_greater_than_max():
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 1

class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)

class Query(graphene.ObjectType):
all_reporters = DjangoConnectionField(ReporterType)

r = Reporter.objects.create(
first_name="John", last_name="Doe", email="[email protected]", a_choice=1
)

schema = graphene.Schema(query=Query)
query = """
query NodeFilteringQuery {
allReporters(first: 999999, last: 1) {
edges {
node {
id
}
}
}
}
"""

expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}

result = schema.execute(query)
assert not result.errors
assert result.data == expected

graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100


def test_should_error_if_negative_first():
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)

class Query(graphene.ObjectType):
all_reporters = DjangoConnectionField(ReporterType)

schema = graphene.Schema(query=Query)
query = """
query NodeFilteringQuery {
allReporters(first: -100, last: 200) {
edges {
node {
id
}
}
}
}
"""

expected = {"allReporters": None}

result = schema.execute(query)
assert len(result.errors) == 1
assert str(result.errors[0]) == "`first` argument must be positive, got `-100`"
assert result.data == expected


def test_should_error_if_negative_last():
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)

class Query(graphene.ObjectType):
all_reporters = DjangoConnectionField(ReporterType)

schema = graphene.Schema(query=Query)
query = """
query NodeFilteringQuery {
allReporters(first: 200, last: -100) {
edges {
node {
id
}
}
}
}
"""

expected = {"allReporters": None}

result = schema.execute(query)
assert len(result.errors) == 1
assert str(result.errors[0]) == "`last` argument must be positive, got `-100`"
assert result.data == expected


def test_max_limit_is_zero():
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 0

class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)

class Query(graphene.ObjectType):
all_reporters = DjangoConnectionField(ReporterType)

r = Reporter.objects.create(
first_name="John", last_name="Doe", email="[email protected]", a_choice=1
)

schema = graphene.Schema(query=Query)
query = """
query NodeFilteringQuery {
allReporters(first: 99999999) {
edges {
node {
id
}
}
}
}
"""

expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}

result = schema.execute(query)
assert not result.errors
assert result.data == expected

graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100


def test_max_limit_is_none():
graphene_settings.RELAY_CONNECTION_MAX_LIMIT = None

class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)

class Query(graphene.ObjectType):
all_reporters = DjangoConnectionField(ReporterType)

r = Reporter.objects.create(
first_name="John", last_name="Doe", email="[email protected]", a_choice=1
)

schema = graphene.Schema(query=Query)
query = """
query NodeFilteringQuery {
allReporters(first: 99999999) {
edges {
node {
id
}
}
}
}
"""

expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjE="}}]}}

result = schema.execute(query)
assert not result.errors
assert result.data == expected

graphene_settings.RELAY_CONNECTION_MAX_LIMIT = 100


def test_should_query_promise_connectionfields():
from promise import Promise

Expand Down Expand Up @@ -1229,7 +1401,7 @@ class Meta:
class Query(graphene.ObjectType):
films = DjangoConnectionField(FilmType)

def resolve_films(root, info):
def resolve_films(root, info, **kwargs):
qs = Film.objects.prefetch_related("reporters")
return qs

Expand Down