diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 47b44f64a..146237cdd 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -143,17 +143,30 @@ def connection_resolver( enforce_first_or_last, root, info, - **args + **kwargs ): - 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) + first = kwargs.get("first") + last = kwargs.get("last") + 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 max_limit: + if first is None and last is None: + kwargs['first'] = first = max_limit + if first: assert first <= max_limit, ( "Requesting {} records on the `{}` connection exceeds the `first` limit of {} records." @@ -165,16 +178,20 @@ def connection_resolver( "Requesting {} records on the `{}` connection exceeds the `last` limit of {} records." ).format(last, info.field_name, max_limit) args["last"] = min(last, max_limit) - - # eventually leads to DjangoObjectType's get_queryset (accepts queryset) - # or a resolve_foo (does not accept queryset) - iterable = resolver(root, info, **args) + + 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)) + + iterable = resolver(root, info, **kwargs) if iterable is None: iterable = default_manager - # thus the iterable gets refiltered by resolve_queryset - # but iterable might be promise - iterable = queryset_resolver(connection, iterable, info, args) - on_resolve = partial(cls.resolve_connection, connection, args) + queryset = cls.resolve_queryset(connection, iterable, info, kwargs) + on_resolve = partial(cls.resolve_connection, connection, queryset, kwargs) if Promise.is_thenable(iterable): return Promise.resolve(iterable).then(on_resolve) diff --git a/graphene_django/filter/tests/test_fields.py b/graphene_django/filter/tests/test_fields.py index de366bada..6514e96b2 100644 --- a/graphene_django/filter/tests/test_fields.py +++ b/graphene_django/filter/tests/test_fields.py @@ -978,7 +978,7 @@ class Query(ObjectType): } } } - """ + """ % reporter_1.email ) @@ -999,3 +999,40 @@ class Query(ObjectType): assert not result.errors assert result.data == expected + + +def test_filter_with_union(): + class ReporterType(DjangoObjectType): + class Meta: + model = Reporter + interfaces = (Node,) + filter_fields = ("first_name",) + + class Query(ObjectType): + all_reporters = DjangoFilterConnectionField(ReporterType) + + @classmethod + def resolve_all_reporters(cls, root, info, **kwargs): + ret = Reporter.objects.none() | Reporter.objects.filter(first_name="John") + + Reporter.objects.create(first_name="John", last_name="Doe") + + schema = Schema(query=Query) + + query = """ + query NodeFilteringQuery { + allReporters(firstName: "abc") { + edges { + node { + firstName + } + } + } + } + """ + expected = {"allReporters": {"edges": []}} + + result = schema.execute(query) + + assert not result.errors + assert result.data == expected diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 95db2d192..bfab08d6e 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -1,23 +1,23 @@ import base64 import datetime +import graphene import pytest from django.db import models -from django.utils.functional import SimpleLazyObject -from py.test import raises - from django.db.models import Q from graphql_relay import to_global_id import graphene +from django.utils.functional import SimpleLazyObject from graphene.relay import Node +from py.test import raises -from ..utils import DJANGO_FILTER_INSTALLED -from ..compat import MissingType, JSONField +from ..compat import JSONField, MissingType from ..fields import DjangoConnectionField -from ..types import DjangoObjectType from ..settings import graphene_settings -from .models import Article, CNNReporter, Reporter, Film, FilmDetails +from ..types import DjangoObjectType +from ..utils import DJANGO_FILTER_INSTALLED +from .models import Article, CNNReporter, Film, FilmDetails, Reporter pytestmark = pytest.mark.django_db @@ -663,7 +663,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 @@ -706,13 +706,184 @@ 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 graphene_settings.RELAY_CONNECTION_ENFORCE_FIRST_OR_LAST = False +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="johndoe@example.com", 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="johndoe@example.com", 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="johndoe@example.com", 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