From eb938da35374dd00e2ae3c9f443d9d45f965ec6f Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Thu, 4 Oct 2018 14:19:08 -0700 Subject: [PATCH 1/6] first attempt at adding get_queryset --- graphene_django/fields.py | 3 ++- graphene_django/types.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 1ecce454c..237cd07d0 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -135,7 +135,8 @@ def connection_resolver( args["last"] = min(last, max_limit) iterable = resolver(root, info, **args) - on_resolve = partial(cls.resolve_connection, connection, default_manager, args) + queryset = connection._meta.node.get_queryset(info) + on_resolve = partial(cls.resolve_connection, connection, queryset, args) if Promise.is_thenable(iterable): return Promise.resolve(iterable).then(on_resolve) diff --git a/graphene_django/types.py b/graphene_django/types.py index aa8b5a30c..a11111409 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -126,10 +126,14 @@ def is_type_of(cls, root, info): model = root._meta.model._meta.concrete_model return model == cls._meta.model + + @classmethod + def get_queryset(cls, info): + return cls._meta.model.objects @classmethod def get_node(cls, info, id): try: - return cls._meta.model.objects.get(pk=id) + return cls.get_queryset(info).get(pk=id) except cls._meta.model.DoesNotExist: return None From 857b20a2f2ac266102cd5eede97d762a9717ad90 Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Fri, 5 Oct 2018 09:35:34 -0700 Subject: [PATCH 2/6] add queryset_resolver to DjangoConnectionField and fix test failures --- graphene_django/fields.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 237cd07d0..7ea30ee3e 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -66,6 +66,20 @@ def get_manager(self): return getattr(self.model, self.on) else: return self.model._default_manager + + @classmethod + def resolve_queryset(cls, connection, queryset, info): + default_queryset = maybe_queryset(connection._meta.node.get_queryset(info)) + if queryset: + return cls.merge_querysets(default_queryset, queryset) + return default_queryset + + def get_queryset_resolver(self): + return partial( + self.resolve_queryset, + self.type, + maybe_queryset(getattr(self.model, self.on)) if self.on else None, + ) @classmethod def merge_querysets(cls, default_queryset, queryset): @@ -106,7 +120,7 @@ def connection_resolver( cls, resolver, connection, - default_manager, + queryset_resolver, max_limit, enforce_first_or_last, root, @@ -135,7 +149,13 @@ def connection_resolver( args["last"] = min(last, max_limit) iterable = resolver(root, info, **args) - queryset = connection._meta.node.get_queryset(info) + if callable(queryset_resolver): + queryset = queryset_resolver(info) + else: + assert isinstance(queryset_resolver, QuerySet), "The type {} is not a QuerySet".format( + type(queryset_resolver) + ) + queryset = queryset_resolver on_resolve = partial(cls.resolve_connection, connection, queryset, args) if Promise.is_thenable(iterable): @@ -148,7 +168,7 @@ def get_resolver(self, parent_resolver): self.connection_resolver, parent_resolver, self.type, - self.get_manager(), + self.get_queryset_resolver(), self.max_limit, self.enforce_first_or_last, ) From 2c6faa63c22dde71a7e54c9beb9728c9342a80a6 Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Fri, 5 Oct 2018 09:47:23 -0700 Subject: [PATCH 3/6] cleanup get_queryset API to match proposal as close as possible --- graphene_django/fields.py | 26 +++++--------------------- graphene_django/types.py | 7 ++++--- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index 7ea30ee3e..ea07298b0 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -68,18 +68,8 @@ def get_manager(self): return self.model._default_manager @classmethod - def resolve_queryset(cls, connection, queryset, info): - default_queryset = maybe_queryset(connection._meta.node.get_queryset(info)) - if queryset: - return cls.merge_querysets(default_queryset, queryset) - return default_queryset - - def get_queryset_resolver(self): - return partial( - self.resolve_queryset, - self.type, - maybe_queryset(getattr(self.model, self.on)) if self.on else None, - ) + def resolve_queryset(cls, connection, queryset, info, args): + return connection._meta.node.get_queryset(queryset, info) @classmethod def merge_querysets(cls, default_queryset, queryset): @@ -120,7 +110,7 @@ def connection_resolver( cls, resolver, connection, - queryset_resolver, + default_manager, max_limit, enforce_first_or_last, root, @@ -149,13 +139,7 @@ def connection_resolver( args["last"] = min(last, max_limit) iterable = resolver(root, info, **args) - if callable(queryset_resolver): - queryset = queryset_resolver(info) - else: - assert isinstance(queryset_resolver, QuerySet), "The type {} is not a QuerySet".format( - type(queryset_resolver) - ) - queryset = queryset_resolver + queryset = cls.resolve_queryset(connection, default_manager, info, args) on_resolve = partial(cls.resolve_connection, connection, queryset, args) if Promise.is_thenable(iterable): @@ -168,7 +152,7 @@ def get_resolver(self, parent_resolver): self.connection_resolver, parent_resolver, self.type, - self.get_queryset_resolver(), + self.get_manager(), self.max_limit, self.enforce_first_or_last, ) diff --git a/graphene_django/types.py b/graphene_django/types.py index a11111409..8e35608d0 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -128,12 +128,13 @@ def is_type_of(cls, root, info): return model == cls._meta.model @classmethod - def get_queryset(cls, info): - return cls._meta.model.objects + def get_queryset(cls, queryset, info): + return queryset @classmethod def get_node(cls, info, id): + queryset = cls.get_queryset(cls._meta.model.objects, info) try: - return cls.get_queryset(info).get(pk=id) + return queryset.get(pk=id) except cls._meta.model.DoesNotExist: return None From b4be0d1837fbc2bf10f3dfa0f5cacbe361fde34b Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Fri, 5 Oct 2018 09:56:41 -0700 Subject: [PATCH 4/6] pep8 fix: W293 --- graphene_django/fields.py | 2 +- graphene_django/types.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphene_django/fields.py b/graphene_django/fields.py index ea07298b0..9b27f70fe 100644 --- a/graphene_django/fields.py +++ b/graphene_django/fields.py @@ -66,7 +66,7 @@ def get_manager(self): return getattr(self.model, self.on) else: return self.model._default_manager - + @classmethod def resolve_queryset(cls, connection, queryset, info, args): return connection._meta.node.get_queryset(queryset, info) diff --git a/graphene_django/types.py b/graphene_django/types.py index 8e35608d0..1bfa2b25a 100644 --- a/graphene_django/types.py +++ b/graphene_django/types.py @@ -126,7 +126,7 @@ def is_type_of(cls, root, info): model = root._meta.model._meta.concrete_model return model == cls._meta.model - + @classmethod def get_queryset(cls, queryset, info): return queryset From dd46f5ff20265f9b2fd5a0e9372beb1596c97648 Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Fri, 5 Oct 2018 10:35:34 -0700 Subject: [PATCH 5/6] document get_queryset usage --- docs/authorization.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/authorization.rst b/docs/authorization.rst index 7a08481ba..08e7bf2a0 100644 --- a/docs/authorization.rst +++ b/docs/authorization.rst @@ -96,6 +96,29 @@ schema is simple. result = schema.execute(query, context_value=request) + +Global Filtering +---------------- + +If you are using ``DjangoObjectType`` you can define a custom `get_queryset`. + +.. code:: python + + from graphene import relay + from graphene_django.types import DjangoObjectType + from .models import Post + + class PostNode(DjangoObjectType): + class Meta: + model = Post + + @classmethod + def get_queryset(cls, queryset, info): + if info.context.user.is_anonymous: + return queryset.filter(published=True) + return queryset + + Filtering ID-based Node Access ------------------------------ From ad2688fea3bd836509ed3496e6bd3e029a170e86 Mon Sep 17 00:00:00 2001 From: Jason Kraus Date: Tue, 9 Oct 2018 09:00:34 -0700 Subject: [PATCH 6/6] add test for when get_queryset is defined on DjangoObjectType --- graphene_django/tests/test_query.py | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/graphene_django/tests/test_query.py b/graphene_django/tests/test_query.py index 1716034c3..58f46c726 100644 --- a/graphene_django/tests/test_query.py +++ b/graphene_django/tests/test_query.py @@ -1007,3 +1007,47 @@ class Query(graphene.ObjectType): result = schema.execute(query) assert result.errors + + +def test_should_resolve_get_queryset_connectionfields(): + reporter_1 = Reporter.objects.create( + first_name="John", last_name="Doe", email="johndoe@example.com", a_choice=1 + ) + reporter_2 = CNNReporter.objects.create( + first_name="Some", + last_name="Guy", + email="someguy@cnn.com", + a_choice=1, + reporter_type=2, # set this guy to be CNN + ) + + class ReporterType(DjangoObjectType): + class Meta: + model = Reporter + interfaces = (Node,) + + @classmethod + def get_queryset(cls, queryset, info): + return queryset.filter(reporter_type=2) + + class Query(graphene.ObjectType): + all_reporters = DjangoConnectionField(ReporterType) + + schema = graphene.Schema(query=Query) + query = """ + query ReporterPromiseConnectionQuery { + allReporters(first: 1) { + edges { + node { + id + } + } + } + } + """ + + expected = {"allReporters": {"edges": [{"node": {"id": "UmVwb3J0ZXJUeXBlOjI="}}]}} + + result = schema.execute(query) + assert not result.errors + assert result.data == expected