11 KiB
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
# my_app/schema.py
import graphene
from graphene_django.types import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = '__all__'
fields
class Query:
= graphene.List(QuestionType)
questions = graphene.Field(QuestionType, question_id=graphene.String())
question
def resolve_questions(self, info, **kwargs):
# Querying a list
return Question.objects.all()
def resolve_question(self, info, question_id):
# Querying a single question
return Question.objects.get(pk=question_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:
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = ('id', 'question_text') fields
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:
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = '__all__' fields
exclude
Show all fields except those in exclude
:
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = ('question_text',) exclude
Customising fields
You can completely overwrite a field, or add new fields, to a DjangoObjectType
using a Resolver:
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = ('id', 'question_text')
fields
= graphene.String()
extra_field
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.
For example the following Model
and DjangoObjectType
:
class PetModel(models.Model):
= models.CharField(max_length=100, choices=(('cat', 'Cat'), ('dog', 'Dog')))
kind
class Pet(DjangoObjectType):
class Meta:
= PetModel
model = '__all__' fields
Results in the following GraphQL schema definition:
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.
class Pet(DjangoObjectType):
class Meta:
= PetModel
model = '__all__'
fields = False convert_choices_to_enum
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:
class Pet(DjangoObjectType):
class Meta:
= PetModel
model = '__all__'
fields = ['kind'] convert_choices_to_enum
Note: Setting convert_choices_to_enum = []
is the same as setting it to False
.
Related models
Say you have the following models:
class Category(models.Model):
= models.CharField(max_length=256)
foo
class Question(models.Model):
= models.ForeignKey(Category, on_delete=models.CASCADE) category
When Question
is published as a DjangoObjectType
and you want to add Category
as a query-able field like so:
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = ('category',) fields
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:
class CategoryType(DjangoObjectType):
class Meta:
= Category
model = '__all__' fields
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.
from graphene_django.types import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = '__all__'
fields
@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:
def resolve_foo(self, info, **kwargs):
Where "foo" is the name of the field declared in the Query
object.
class Query:
= graphene.List(QuestionType)
foo
def resolve_foo(self, info, **kwargs):
id = kwargs.get('id')
return QuestionModel.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.
class Query:
= graphene.Field(Question, foo=graphene.String(), bar=graphene.Int())
question
def resolve_question(self, 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:
def resolve_questions(self, info, **kwargs):
# 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.
import graphene
from .models import Question
class MyQuestion(graphene.ObjectType):
= graphene.String()
text
class Query:
= graphene.Field(MyQuestion, question_id=graphene.String())
question
def resolve_question(self, info, question_id):
= Question.objects.get(pk=question_id)
question return MyQuestion(
=question.question_text
text )
For more information and more examples, please see the core object type documentation.
Relay
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 on the core graphene pages for more information on customizing the Relay experience.
from graphene import relay
from graphene_django import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
= Question
model = '__all__'
fields = (relay.Node,)
interfaces
class QuestionConnection(relay.Connection):
class Meta:
= QuestionType
node
class Query:
= relay.ConnectionField(QuestionConnection)
questions
def resolve_questions(root, info, **kwargs):
return Question.objects.all()
You can now execute queries like:
{2, after: "YXJyYXljb25uZWN0aW9uOjEwNQ==") {
questions (first:
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {id
question_text
}
}
} }
Which returns:
{"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 pagination
capabilities automatically, adding a pageInfo
element, and including 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 on the GraphQL community site.