mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-25 02:54:06 +03:00
Improve DjangoListField (#929)
This commit is contained in:
parent
8990e173ac
commit
b4e34a5794
83
docs/fields.rst
Normal file
83
docs/fields.rst
Normal file
|
@ -0,0 +1,83 @@
|
|||
Fields
|
||||
======
|
||||
|
||||
Graphene-Django provides some useful fields to help integrate Django with your GraphQL
|
||||
Schema.
|
||||
|
||||
DjangoListField
|
||||
---------------
|
||||
|
||||
``DjangoListField`` allows you to define a list of :ref:`DjangoObjectType<queries-objecttypes>`'s. By default it will resolve the default queryset of the Django model.
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, Schema
|
||||
from graphene_django import DjangoListField
|
||||
|
||||
class RecipeType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = ("title", "instructions")
|
||||
|
||||
class Query(ObjectType):
|
||||
recipes = DjangoListField(RecipeType)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
The above code results in the following schema definition:
|
||||
|
||||
.. code::
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
|
||||
type Query {
|
||||
recipes: [RecipeType!]
|
||||
}
|
||||
|
||||
type RecipeType {
|
||||
title: String!
|
||||
instructions: String!
|
||||
}
|
||||
|
||||
Custom resolvers
|
||||
****************
|
||||
|
||||
If your ``DjangoObjectType`` has defined a custom
|
||||
:ref:`get_queryset<django-objecttype-get-queryset>` method, when resolving a
|
||||
``DjangoListField`` it will be called with either the return of the field
|
||||
resolver (if one is defined) or the default queryeset from the Django model.
|
||||
|
||||
For example the following schema will only resolve recipes which have been
|
||||
published and have a title:
|
||||
|
||||
.. code:: python
|
||||
|
||||
from graphene import ObjectType, Schema
|
||||
from graphene_django import DjangoListField
|
||||
|
||||
class RecipeType(DjangoObjectType):
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = ("title", "instructions")
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
# Filter out recipes that have no title
|
||||
return queryset.exclude(title__exact="")
|
||||
|
||||
class Query(ObjectType):
|
||||
recipes = DjangoListField(RecipeType)
|
||||
|
||||
def resolve_recipes(parent, info):
|
||||
# Only get recipes that have been published
|
||||
return Recipe.objects.filter(published=True)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
|
||||
DjangoConnectionField
|
||||
---------------------
|
||||
|
||||
*TODO*
|
|
@ -1,7 +1,7 @@
|
|||
Filtering
|
||||
=========
|
||||
|
||||
Graphene integrates with
|
||||
Graphene-Django integrates with
|
||||
`django-filter <https://django-filter.readthedocs.io/en/master/>`__ (2.x for
|
||||
Python 3 or 1.x for Python 2) to provide filtering of results. See the `usage
|
||||
documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
|
||||
|
|
|
@ -25,6 +25,7 @@ For more advanced use, check out the Relay tutorial.
|
|||
tutorial-relay
|
||||
schema
|
||||
queries
|
||||
fields
|
||||
extra-types
|
||||
mutations
|
||||
filtering
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _queries-objecttypes:
|
||||
|
||||
Queries & ObjectTypes
|
||||
=====================
|
||||
|
||||
|
@ -205,6 +207,8 @@ need to create the most basic class for this to work:
|
|||
class Meta:
|
||||
model = Category
|
||||
|
||||
.. _django-objecttype-get-queryset:
|
||||
|
||||
Default QuerySet
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
from .fields import DjangoConnectionField, DjangoListField
|
||||
from .types import DjangoObjectType
|
||||
from .fields import DjangoConnectionField
|
||||
|
||||
__version__ = "2.9.1"
|
||||
|
||||
__all__ = ["__version__", "DjangoObjectType", "DjangoConnectionField"]
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"DjangoObjectType",
|
||||
"DjangoListField",
|
||||
"DjangoConnectionField",
|
||||
]
|
||||
|
|
|
@ -38,16 +38,21 @@ class DjangoListField(Field):
|
|||
def model(self):
|
||||
return self._underlying_type._meta.model
|
||||
|
||||
def get_default_queryset(self):
|
||||
return self.model._default_manager.get_queryset()
|
||||
|
||||
@staticmethod
|
||||
def list_resolver(django_object_type, resolver, root, info, **args):
|
||||
def list_resolver(
|
||||
django_object_type, resolver, default_queryset, root, info, **args
|
||||
):
|
||||
queryset = maybe_queryset(resolver(root, info, **args))
|
||||
if queryset is None:
|
||||
# Default to Django Model queryset
|
||||
# N.B. This happens if DjangoListField is used in the top level Query object
|
||||
model_manager = django_object_type._meta.model.objects
|
||||
queryset = maybe_queryset(
|
||||
django_object_type.get_queryset(model_manager, info)
|
||||
)
|
||||
queryset = default_queryset
|
||||
|
||||
if isinstance(queryset, QuerySet):
|
||||
# Pass queryset to the DjangoObjectType get_queryset method
|
||||
queryset = maybe_queryset(django_object_type.get_queryset(queryset, info))
|
||||
|
||||
return queryset
|
||||
|
||||
def get_resolver(self, parent_resolver):
|
||||
|
@ -55,7 +60,12 @@ class DjangoListField(Field):
|
|||
if isinstance(_type, NonNull):
|
||||
_type = _type.of_type
|
||||
django_object_type = _type.of_type.of_type
|
||||
return partial(self.list_resolver, django_object_type, parent_resolver)
|
||||
return partial(
|
||||
self.list_resolver,
|
||||
django_object_type,
|
||||
parent_resolver,
|
||||
self.get_default_queryset(),
|
||||
)
|
||||
|
||||
|
||||
class DjangoConnectionField(ConnectionField):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
from django.db.models import Count
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -141,13 +142,26 @@ class TestDjangoListField:
|
|||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
ArticleModel.objects.create(
|
||||
headline="Not so good news",
|
||||
reporter=r1,
|
||||
pub_date=datetime.date.today(),
|
||||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
|
||||
result = schema.execute(query)
|
||||
|
||||
assert not result.errors
|
||||
assert result.data == {
|
||||
"reporters": [
|
||||
{"firstName": "Tara", "articles": [{"headline": "Amazing news"}]},
|
||||
{
|
||||
"firstName": "Tara",
|
||||
"articles": [
|
||||
{"headline": "Amazing news"},
|
||||
{"headline": "Not so good news"},
|
||||
],
|
||||
},
|
||||
{"firstName": "Debra", "articles": []},
|
||||
]
|
||||
}
|
||||
|
@ -163,8 +177,8 @@ class TestDjangoListField:
|
|||
model = ReporterModel
|
||||
fields = ("first_name", "articles")
|
||||
|
||||
def resolve_reporters(reporter, info):
|
||||
return reporter.articles.all()
|
||||
def resolve_articles(reporter, info):
|
||||
return reporter.articles.filter(headline__contains="Amazing")
|
||||
|
||||
class Query(ObjectType):
|
||||
reporters = DjangoListField(Reporter)
|
||||
|
@ -192,6 +206,13 @@ class TestDjangoListField:
|
|||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
ArticleModel.objects.create(
|
||||
headline="Not so good news",
|
||||
reporter=r1,
|
||||
pub_date=datetime.date.today(),
|
||||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
|
||||
result = schema.execute(query)
|
||||
|
||||
|
@ -202,3 +223,155 @@ class TestDjangoListField:
|
|||
{"firstName": "Debra", "articles": []},
|
||||
]
|
||||
}
|
||||
|
||||
def test_get_queryset_filter(self):
|
||||
class Reporter(DjangoObjectType):
|
||||
class Meta:
|
||||
model = ReporterModel
|
||||
fields = ("first_name", "articles")
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
# Only get reporters with at least 1 article
|
||||
return queryset.annotate(article_count=Count("articles")).filter(
|
||||
article_count__gt=0
|
||||
)
|
||||
|
||||
class Query(ObjectType):
|
||||
reporters = DjangoListField(Reporter)
|
||||
|
||||
def resolve_reporters(_, info):
|
||||
return ReporterModel.objects.all()
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
query = """
|
||||
query {
|
||||
reporters {
|
||||
firstName
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
|
||||
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
|
||||
|
||||
ArticleModel.objects.create(
|
||||
headline="Amazing news",
|
||||
reporter=r1,
|
||||
pub_date=datetime.date.today(),
|
||||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
|
||||
result = schema.execute(query)
|
||||
|
||||
assert not result.errors
|
||||
assert result.data == {"reporters": [{"firstName": "Tara"},]}
|
||||
|
||||
def test_resolve_list(self):
|
||||
"""Resolving a plain list should work (and not call get_queryset)"""
|
||||
|
||||
class Reporter(DjangoObjectType):
|
||||
class Meta:
|
||||
model = ReporterModel
|
||||
fields = ("first_name", "articles")
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
# Only get reporters with at least 1 article
|
||||
return queryset.annotate(article_count=Count("articles")).filter(
|
||||
article_count__gt=0
|
||||
)
|
||||
|
||||
class Query(ObjectType):
|
||||
reporters = DjangoListField(Reporter)
|
||||
|
||||
def resolve_reporters(_, info):
|
||||
return [ReporterModel.objects.get(first_name="Debra")]
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
query = """
|
||||
query {
|
||||
reporters {
|
||||
firstName
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
|
||||
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
|
||||
|
||||
ArticleModel.objects.create(
|
||||
headline="Amazing news",
|
||||
reporter=r1,
|
||||
pub_date=datetime.date.today(),
|
||||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
|
||||
result = schema.execute(query)
|
||||
|
||||
assert not result.errors
|
||||
assert result.data == {"reporters": [{"firstName": "Debra"},]}
|
||||
|
||||
def test_get_queryset_foreign_key(self):
|
||||
class Article(DjangoObjectType):
|
||||
class Meta:
|
||||
model = ArticleModel
|
||||
fields = ("headline",)
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
# Rose tinted glasses
|
||||
return queryset.exclude(headline__contains="Not so good")
|
||||
|
||||
class Reporter(DjangoObjectType):
|
||||
class Meta:
|
||||
model = ReporterModel
|
||||
fields = ("first_name", "articles")
|
||||
|
||||
class Query(ObjectType):
|
||||
reporters = DjangoListField(Reporter)
|
||||
|
||||
schema = Schema(query=Query)
|
||||
|
||||
query = """
|
||||
query {
|
||||
reporters {
|
||||
firstName
|
||||
articles {
|
||||
headline
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
r1 = ReporterModel.objects.create(first_name="Tara", last_name="West")
|
||||
ReporterModel.objects.create(first_name="Debra", last_name="Payne")
|
||||
|
||||
ArticleModel.objects.create(
|
||||
headline="Amazing news",
|
||||
reporter=r1,
|
||||
pub_date=datetime.date.today(),
|
||||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
ArticleModel.objects.create(
|
||||
headline="Not so good news",
|
||||
reporter=r1,
|
||||
pub_date=datetime.date.today(),
|
||||
pub_date_time=datetime.datetime.now(),
|
||||
editor=r1,
|
||||
)
|
||||
|
||||
result = schema.execute(query)
|
||||
|
||||
assert not result.errors
|
||||
assert result.data == {
|
||||
"reporters": [
|
||||
{"firstName": "Tara", "articles": [{"headline": "Amazing news"},],},
|
||||
{"firstName": "Debra", "articles": []},
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user