mirror of
https://github.com/graphql-python/graphene-django.git
synced 2024-11-10 19:57:15 +03:00
c049ab7470
* merge master into v3 * fix order_by snake casing by checking if value is None, switch executor to execution_context_class since schema.execute no longer supports executor * fix linting by removing duplicate defintion and test of convert_form_field_to_string_list
477 lines
12 KiB
ReStructuredText
477 lines
12 KiB
ReStructuredText
.. _queries-objecttypes:
|
|
|
|
Queries & ObjectTypes
|
|
=====================
|
|
|
|
Introduction
|
|
------------
|
|
|
|
Graphene-Django offers a host of features for performing GraphQL queries.
|
|
|
|
Graphene-Django ships with a special ``DjangoObjectType`` that automatically transforms a Django Model
|
|
into a ``ObjectType`` for you.
|
|
|
|
|
|
Full example
|
|
~~~~~~~~~~~~
|
|
|
|
.. code:: python
|
|
|
|
# my_app/schema.py
|
|
|
|
import graphene
|
|
from graphene_django import DjangoObjectType
|
|
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
fields = ("id", "question_text")
|
|
|
|
class Query(graphene.ObjectType):
|
|
questions = graphene.List(QuestionType)
|
|
question_by_id = graphene.Field(QuestionType, id=graphene.String())
|
|
|
|
def resolve_questions(root, info, **kwargs):
|
|
# Querying a list
|
|
return Question.objects.all()
|
|
|
|
def resolve_question_by_id(root, info, id):
|
|
# Querying a single question
|
|
return Question.objects.get(pk=id)
|
|
|
|
|
|
Specifying which fields to include
|
|
----------------------------------
|
|
|
|
By default, ``DjangoObjectType`` will present all fields on a Model through GraphQL.
|
|
If you only want a subset of fields to be present, you can do so using
|
|
``fields`` or ``exclude``. It is strongly recommended that you explicitly set
|
|
all fields that should be exposed using the fields attribute.
|
|
This will make it less likely to result in unintentionally exposing data when
|
|
your models change.
|
|
|
|
Setting neither ``fields`` nor ``exclude`` is deprecated and will raise a warning, you should at least explicitly make
|
|
``DjangoObjectType`` include all fields in the model as described below.
|
|
|
|
``fields``
|
|
~~~~~~~~~~
|
|
|
|
Show **only** these fields on the model:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
fields = ("id", "question_text")
|
|
|
|
You can also set the ``fields`` attribute to the special value ``"__all__"`` to indicate that all fields in the model should be used.
|
|
|
|
For example:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
fields = "__all__"
|
|
|
|
|
|
``exclude``
|
|
~~~~~~~~~~~
|
|
|
|
Show all fields **except** those in ``exclude``:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
exclude = ("question_text",)
|
|
|
|
|
|
Customising fields
|
|
------------------
|
|
|
|
You can completely overwrite a field, or add new fields, to a ``DjangoObjectType`` using a Resolver:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
|
|
class Meta:
|
|
model = Question
|
|
fields = ("id", "question_text")
|
|
|
|
extra_field = graphene.String()
|
|
|
|
def resolve_extra_field(self, info):
|
|
return "hello!"
|
|
|
|
|
|
Choices to Enum conversion
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
By default Graphene-Django will convert any Django fields that have `choices`_
|
|
defined into a GraphQL enum type.
|
|
|
|
.. _choices: https://docs.djangoproject.com/en/2.2/ref/models/fields/#choices
|
|
|
|
For example the following ``Model`` and ``DjangoObjectType``:
|
|
|
|
.. code:: python
|
|
|
|
from django.db import models
|
|
from graphene_django import DjangoObjectType
|
|
|
|
class PetModel(models.Model):
|
|
kind = models.CharField(
|
|
max_length=100,
|
|
choices=(("cat", "Cat"), ("dog", "Dog"))
|
|
)
|
|
|
|
class Pet(DjangoObjectType):
|
|
class Meta:
|
|
model = PetModel
|
|
fields = ("id", "kind",)
|
|
|
|
Results in the following GraphQL schema definition:
|
|
|
|
.. code::
|
|
|
|
type Pet {
|
|
id: ID!
|
|
kind: PetModelKind!
|
|
}
|
|
|
|
enum PetModelKind {
|
|
CAT
|
|
DOG
|
|
}
|
|
|
|
You can disable this automatic conversion by setting
|
|
``convert_choices_to_enum`` attribute to ``False`` on the ``DjangoObjectType``
|
|
``Meta`` class.
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import PetModel
|
|
|
|
class Pet(DjangoObjectType):
|
|
class Meta:
|
|
model = PetModel
|
|
fields = ("id", "kind",)
|
|
convert_choices_to_enum = False
|
|
|
|
.. code::
|
|
|
|
type Pet {
|
|
id: ID!
|
|
kind: String!
|
|
}
|
|
|
|
You can also set ``convert_choices_to_enum`` to a list of fields that should be
|
|
automatically converted into enums:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import PetModel
|
|
|
|
class Pet(DjangoObjectType):
|
|
class Meta:
|
|
model = PetModel
|
|
fields = ("id", "kind",)
|
|
convert_choices_to_enum = ["kind"]
|
|
|
|
**Note:** Setting ``convert_choices_to_enum = []`` is the same as setting it to
|
|
``False``.
|
|
|
|
|
|
Related models
|
|
--------------
|
|
|
|
Say you have the following models:
|
|
|
|
.. code:: python
|
|
|
|
from django.db import models
|
|
|
|
class Category(models.Model):
|
|
foo = models.CharField(max_length=256)
|
|
|
|
class Question(models.Model):
|
|
category = models.ForeignKey(Category, on_delete=models.CASCADE)
|
|
|
|
|
|
When ``Question`` is published as a ``DjangoObjectType`` and you want to add ``Category`` as a query-able field like so:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
fields = ("category",)
|
|
|
|
Then all query-able related models must be defined as DjangoObjectType subclass,
|
|
or they will fail to show if you are trying to query those relation fields. You only
|
|
need to create the most basic class for this to work:
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Category
|
|
|
|
class CategoryType(DjangoObjectType):
|
|
class Meta:
|
|
model = Category
|
|
fields = ("foo",)
|
|
|
|
.. _django-objecttype-get-queryset:
|
|
|
|
Default QuerySet
|
|
-----------------
|
|
|
|
If you are using ``DjangoObjectType`` you can define a custom `get_queryset` method.
|
|
Use this to control filtering on the ObjectType level instead of the Query object level.
|
|
|
|
.. code:: python
|
|
|
|
from graphene_django.types import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
fields = "__all__"
|
|
|
|
@classmethod
|
|
def get_queryset(cls, queryset, info):
|
|
if info.context.user.is_anonymous:
|
|
return queryset.filter(published=True)
|
|
return queryset
|
|
|
|
Resolvers
|
|
---------
|
|
|
|
When a GraphQL query is received by the ``Schema`` object, it will map it to a "Resolver" related to it.
|
|
|
|
This resolve method should follow this format:
|
|
|
|
.. code:: python
|
|
|
|
def resolve_foo(parent, info, **kwargs):
|
|
|
|
Where "foo" is the name of the field declared in the ``Query`` object.
|
|
|
|
.. code:: python
|
|
|
|
import graphene
|
|
from .models import Question
|
|
from .types import QuestionType
|
|
|
|
class Query(graphene.ObjectType):
|
|
foo = graphene.List(QuestionType)
|
|
|
|
def resolve_foo(root, info, **kwargs):
|
|
id = kwargs.get("id")
|
|
return Question.objects.get(id)
|
|
|
|
Arguments
|
|
~~~~~~~~~
|
|
|
|
Additionally, Resolvers will receive **any arguments declared in the field definition**. This allows you to provide input arguments in your GraphQL server and can be useful for custom queries.
|
|
|
|
.. code:: python
|
|
|
|
import graphene
|
|
from .models import Question
|
|
from .types import QuestionType
|
|
|
|
class Query(graphene.ObjectType):
|
|
question = graphene.Field(
|
|
QuestionType,
|
|
foo=graphene.String(),
|
|
bar=graphene.Int()
|
|
)
|
|
|
|
def resolve_question(root, info, foo, bar):
|
|
# If `foo` or `bar` are declared in the GraphQL query they will be here, else None.
|
|
return Question.objects.filter(foo=foo, bar=bar).first()
|
|
|
|
|
|
Info
|
|
~~~~
|
|
|
|
The ``info`` argument passed to all resolve methods holds some useful information.
|
|
For Graphene-Django, the ``info.context`` attribute is the ``HTTPRequest`` object
|
|
that would be familiar to any Django developer. This gives you the full functionality
|
|
of Django's ``HTTPRequest`` in your resolve methods, such as checking for authenticated users:
|
|
|
|
.. code:: python
|
|
|
|
import graphene
|
|
|
|
from .models import Question
|
|
from .types import QuestionType
|
|
|
|
class Query(graphene.ObjectType):
|
|
questions = graphene.List(QuestionType)
|
|
|
|
def resolve_questions(root, info):
|
|
# See if a user is authenticated
|
|
if info.context.user.is_authenticated():
|
|
return Question.objects.all()
|
|
else:
|
|
return Question.objects.none()
|
|
|
|
|
|
DjangoObjectTypes
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
A Resolver that maps to a defined `DjangoObjectType` should only use methods that return a queryset.
|
|
Queryset methods like `values` will return dictionaries, use `defer` instead.
|
|
|
|
|
|
Plain ObjectTypes
|
|
-----------------
|
|
|
|
With Graphene-Django you are not limited to just Django Models - you can use the standard
|
|
``ObjectType`` to create custom fields or to provide an abstraction between your internal
|
|
Django models and your external API.
|
|
|
|
.. code:: python
|
|
|
|
import graphene
|
|
from .models import Question
|
|
|
|
class MyQuestion(graphene.ObjectType):
|
|
text = graphene.String()
|
|
|
|
class Query(graphene.ObjectType):
|
|
question = graphene.Field(MyQuestion, question_id=graphene.String())
|
|
|
|
def resolve_question(root, info, question_id):
|
|
question = Question.objects.get(pk=question_id)
|
|
return MyQuestion(
|
|
text=question.question_text
|
|
)
|
|
|
|
For more information and more examples, please see the `core object type documentation <https://docs.graphene-python.org/en/latest/types/objecttypes/>`__.
|
|
|
|
|
|
Relay
|
|
-----
|
|
|
|
`Relay <http://docs.graphene-python.org/en/latest/relay/>`__ with Graphene-Django gives us some additional features:
|
|
|
|
- Pagination and slicing.
|
|
- An abstract ``id`` value which contains enough info for the server to know its type and its id.
|
|
|
|
There is one additional import and a single line of code needed to adopt this:
|
|
|
|
Full example
|
|
~~~~~~~~~~~~
|
|
See the `Relay documentation <https://docs.graphene-python.org/en/latest/relay/nodes/>`__ on
|
|
the core graphene pages for more information on customizing the Relay experience.
|
|
|
|
.. code:: python
|
|
|
|
from graphene import relay
|
|
from graphene_django import DjangoObjectType
|
|
from .models import Question
|
|
|
|
class QuestionType(DjangoObjectType):
|
|
class Meta:
|
|
model = Question
|
|
interfaces = (relay.Node,) # make sure you add this
|
|
fields = "__all__"
|
|
|
|
class QuestionConnection(relay.Connection):
|
|
class Meta:
|
|
node = QuestionType
|
|
|
|
class Query:
|
|
questions = relay.ConnectionField(QuestionConnection)
|
|
|
|
def resolve_questions(root, info, **kwargs):
|
|
return Question.objects.all()
|
|
|
|
You can now execute queries like:
|
|
|
|
|
|
.. code:: python
|
|
|
|
{
|
|
questions (first: 2, after: "YXJyYXljb25uZWN0aW9uOjEwNQ==") {
|
|
pageInfo {
|
|
startCursor
|
|
endCursor
|
|
hasNextPage
|
|
hasPreviousPage
|
|
}
|
|
edges {
|
|
cursor
|
|
node {
|
|
id
|
|
question_text
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Which returns:
|
|
|
|
.. code:: python
|
|
|
|
{
|
|
"data": {
|
|
"questions": {
|
|
"pageInfo": {
|
|
"startCursor": "YXJyYXljb25uZWN0aW9uOjEwNg==",
|
|
"endCursor": "YXJyYXljb25uZWN0aW9uOjEwNw==",
|
|
"hasNextPage": true,
|
|
"hasPreviousPage": false
|
|
},
|
|
"edges": [
|
|
{
|
|
"cursor": "YXJyYXljb25uZWN0aW9uOjEwNg==",
|
|
"node": {
|
|
"id": "UGxhY2VUeXBlOjEwNw==",
|
|
"question_text": "How did we get here?"
|
|
}
|
|
},
|
|
{
|
|
"cursor": "YXJyYXljb25uZWN0aW9uOjEwNw==",
|
|
"node": {
|
|
"id": "UGxhY2VUeXBlOjEwOA==",
|
|
"name": "Where are we?"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
Note that relay implements :code:`pagination` capabilities automatically, adding a :code:`pageInfo` element, and including :code:`cursor` on nodes. These elements are included in the above example for illustration.
|
|
|
|
To learn more about Pagination in general, take a look at `Pagination <https://graphql.org/learn/pagination/>`__ on the GraphQL community site.
|