Update tutorial docs (#994)

This commit is contained in:
Jonathan Kim 2020-08-05 20:17:53 +01:00 committed by GitHub
parent b552dcac24
commit 97de26bf2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 177 additions and 214 deletions

View File

@ -25,8 +25,8 @@ Add ``graphene_django`` to the ``INSTALLED_APPS`` in the ``settings.py`` file of
INSTALLED_APPS = [
...
'django.contrib.staticfiles', # Required for GraphiQL
'graphene_django'
"django.contrib.staticfiles", # Required for GraphiQL
"graphene_django"
]
@ -63,7 +63,7 @@ Finally, define the schema location for Graphene in the ``settings.py`` file of
.. code:: python
GRAPHENE = {
'SCHEMA': 'django_root.schema.schema'
"SCHEMA": "django_root.schema.schema"
}
Where ``path.schema.schema`` is the location of the ``Schema`` object in your Django project.
@ -75,7 +75,7 @@ The most basic ``schema.py`` looks like this:
import graphene
class Query(graphene.ObjectType):
pass
hello = graphene.String(default_value="Hi!")
schema = graphene.Schema(query=Query)

View File

@ -20,27 +20,26 @@ Full example
# my_app/schema.py
import graphene
from graphene_django import DjangoObjectType
from graphene_django.types import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
model = Question
fields = ("id", "question_text")
class Query:
class Query(graphene.ObjectType):
questions = graphene.List(QuestionType)
question = graphene.Field(QuestionType, question_id=graphene.String())
question_by_id = graphene.Field(QuestionType, id=graphene.String())
def resolve_questions(self, info, **kwargs):
def resolve_questions(root, info, **kwargs):
# Querying a list
return Question.objects.all()
def resolve_question(self, info, question_id):
def resolve_question_by_id(root, info, id):
# Querying a single question
return Question.objects.get(pk=question_id)
return Question.objects.get(pk=id)
Specifying which fields to include
@ -60,21 +59,27 @@ 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')
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.
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__'
fields = "__all__"
``exclude``
@ -84,10 +89,13 @@ 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',)
exclude = ("question_text",)
Customising fields
@ -97,16 +105,19 @@ You can completely overwrite a field, or add new fields, to a ``DjangoObjectType
.. code:: python
from graphene_django import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
model = Question
fields = ('id', 'question_text')
fields = ("id", "question_text")
extra_field = graphene.String()
def resolve_extra_field(self, info):
return 'hello!'
return "hello!"
Choices to Enum conversion
@ -121,12 +132,19 @@ For example the following ``Model`` and ``DjangoObjectType``:
.. code:: python
class PetModel(models.Model):
kind = models.CharField(max_length=100, choices=(('cat', 'Cat'), ('dog', 'Dog')))
from django.db import models
from graphene_django import DjangoObjectType
class Pet(DjangoObjectType):
class Meta:
model = PetModel
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:
@ -148,27 +166,35 @@ You can disable this automatic conversion by setting
.. code:: python
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = False
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!
}
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
class Pet(DjangoObjectType):
class Meta:
model = PetModel
convert_choices_to_enum = ['kind']
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``.
@ -181,6 +207,8 @@ Say you have the following models:
.. code:: python
from django.db import models
class Category(models.Model):
foo = models.CharField(max_length=256)
@ -192,10 +220,13 @@ When ``Question`` is published as a ``DjangoObjectType`` and you want to add ``C
.. code:: python
from graphene_django import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
model = Question
fields = ('category',)
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
@ -203,9 +234,13 @@ 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:
@ -220,7 +255,6 @@ Use this to control filtering on the ObjectType level instead of the Query objec
from graphene_django.types import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
model = Question
@ -240,18 +274,22 @@ This resolve method should follow this format:
.. code:: python
def resolve_foo(self, info, **kwargs):
def resolve_foo(parent, info, **kwargs):
Where "foo" is the name of the field declared in the ``Query`` object.
.. code:: python
class Query:
import graphene
from .models import Question
from .types import QuestionType
class Query(graphene.ObjectType):
foo = graphene.List(QuestionType)
def resolve_foo(self, info, **kwargs):
id = kwargs.get('id')
return QuestionModel.objects.get(id)
def resolve_foo(root, info):
id = kwargs.get("id")
return Question.objects.get(id)
Arguments
~~~~~~~~~
@ -260,10 +298,18 @@ Additionally, Resolvers will receive **any arguments declared in the field defin
.. code:: python
class Query:
question = graphene.Field(Question, foo=graphene.String(), bar=graphene.Int())
import graphene
from .models import Question
from .types import QuestionType
def resolve_question(self, info, foo, bar):
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()
@ -278,7 +324,15 @@ of Django's ``HTTPRequest`` in your resolve methods, such as checking for authen
.. code:: python
def resolve_questions(self, info, **kwargs):
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()
@ -305,15 +359,13 @@ Django models and your external API.
import graphene
from .models import Question
class MyQuestion(graphene.ObjectType):
text = graphene.String()
class Query:
class Query(graphene.ObjectType):
question = graphene.Field(MyQuestion, question_id=graphene.String())
def resolve_question(self, info, question_id):
def resolve_question(root, info, question_id):
question = Question.objects.get(pk=question_id)
return MyQuestion(
text=question.question_text
@ -343,25 +395,22 @@ the core graphene pages for more information on customizing the Relay experience
from graphene_django import DjangoObjectType
from .models import Question
class QuestionType(DjangoObjectType):
class Meta:
model = Question
interfaces = (relay.Node,)
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:

View File

@ -3,15 +3,11 @@ Basic Tutorial
Graphene Django has a number of additional features that are designed to make
working with Django easy. Our primary focus in this tutorial is to give a good
understanding of how to connect models from Django ORM to graphene object types.
understanding of how to connect models from Django ORM to Graphene object types.
Set up the Django project
-------------------------
You can find the entire project in ``examples/cookbook-plain``.
----
We will set up the project, create the following:
- A Django project called ``cookbook``
@ -28,13 +24,12 @@ We will set up the project, create the following:
source env/bin/activate # On Windows use `env\Scripts\activate`
# Install Django and Graphene with Django support
pip install django
pip install graphene_django
pip install django graphene_django
# Set up a new project with a single application
django-admin.py startproject cookbook . # Note the trailing '.' character
django-admin startproject cookbook . # Note the trailing '.' character
cd cookbook
django-admin.py startapp ingredients
django-admin startapp ingredients
Now sync your database for the first time:
@ -54,19 +49,18 @@ Let's get started with these models:
# cookbook/ingredients/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.CharField(max_length=100)
notes = models.TextField()
category = models.ForeignKey(
Category, related_name='ingredients', on_delete=models.CASCADE)
Category, related_name="ingredients", on_delete=models.CASCADE
)
def __str__(self):
return self.name
@ -75,10 +69,12 @@ Add ingredients as INSTALLED_APPS:
.. code:: python
# cookbook/settings.py
INSTALLED_APPS = [
...
# Install the ingredients app
'cookbook.ingredients',
"cookbook.ingredients",
]
@ -102,13 +98,13 @@ following:
.. code:: bash
$ python ./manage.py loaddata ingredients
python manage.py loaddata ingredients
Installed 6 object(s) from 1 fixture(s)
Alternatively you can use the Django admin interface to create some data
yourself. You'll need to run the development server (see below), and
create a login for yourself too (``./manage.py createsuperuser``).
create a login for yourself too (``python manage.py createsuperuser``).
Register models with admin panel:
@ -138,66 +134,48 @@ order to create this representation, Graphene needs to know about each
This graph also has a *root type* through which all access begins. This
is the ``Query`` class below.
This means, for each of our models, we are going to create a type, subclassing ``DjangoObjectType``
To create GraphQL types for each of our Django models, we are going to subclass the ``DjangoObjectType`` class which will automatically define GraphQL fields that correspond to the fields on the Django models.
After we've done that, we will list those types as fields in the ``Query`` class.
Create ``cookbook/ingredients/schema.py`` and type the following:
Create ``cookbook/schema.py`` and type the following:
.. code:: python
# cookbook/ingredients/schema.py
# cookbook/schema.py
import graphene
from graphene_django.types import DjangoObjectType
from graphene_django import DjangoObjectType
from cookbook.ingredients.models import Category, Ingredient
class CategoryType(DjangoObjectType):
class Meta:
model = Category
fields = ("id", "name", "ingredients")
class IngredientType(DjangoObjectType):
class Meta:
model = Ingredient
fields = ("id", "name", "notes", "category")
class Query(object):
all_categories = graphene.List(CategoryType)
class Query(graphene.ObjectType):
all_ingredients = graphene.List(IngredientType)
category_by_name = graphene.Field(CategoryType, name=graphene.String(required=True))
def resolve_all_categories(self, info, **kwargs):
return Category.objects.all()
def resolve_all_ingredients(self, info, **kwargs):
def resolve_all_ingredients(root, info):
# We can easily optimize query count in the resolve method
return Ingredient.objects.select_related('category').all()
return Ingredient.objects.select_related("category").all()
Note that the above ``Query`` class is a mixin, inheriting from
``object``. This is because we will now create a project-level query
class which will combine all our app-level mixins.
Create the parent project-level ``cookbook/schema.py``:
.. code:: python
import graphene
import cookbook.ingredients.schema
class Query(cookbook.ingredients.schema.Query, graphene.ObjectType):
# This class will inherit from multiple Queries
# as we begin to add more apps to our project
pass
def resolve_category_by_name(root, info, name):
try:
return Category.objects.get(name=name)
except Category.DoesNotExist:
return None
schema = graphene.Schema(query=Query)
You can think of this as being something like your top-level ``urls.py``
file (although it currently lacks any namespacing).
file.
Testing everything so far
-------------------------
@ -216,18 +194,21 @@ Add ``graphene_django`` to ``INSTALLED_APPS`` in ``cookbook/settings.py``:
.. code:: python
# cookbook/settings.py
INSTALLED_APPS = [
...
# This will also make the `graphql_schema` management command available
'graphene_django',
"graphene_django",
]
And then add the ``SCHEMA`` to the ``GRAPHENE`` config in ``cookbook/settings.py``:
.. code:: python
# cookbook/settings.py
GRAPHENE = {
'SCHEMA': 'cookbook.schema.schema'
"SCHEMA": "cookbook.schema.schema"
}
Alternatively, we can specify the schema to be used in the urls definition,
@ -245,14 +226,17 @@ aforementioned GraphiQL we specify that on the parameters with ``graphiql=True``
.. code:: python
from django.conf.urls import url, include
# cookbook/urls.py
from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^graphql$', GraphQLView.as_view(graphiql=True)),
path("admin/", admin.site.urls),
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
@ -261,16 +245,19 @@ as explained above, we can do so here using:
.. code:: python
from django.conf.urls import url, include
# cookbook/urls.py
from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
from cookbook.schema import schema
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^graphql$', GraphQLView.as_view(graphiql=True, schema=schema)),
path("admin/", admin.site.urls),
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
]
@ -283,10 +270,10 @@ from the command line.
.. code:: bash
$ python ./manage.py runserver
python manage.py runserver
Performing system checks...
Django version 1.11, using settings 'cookbook.settings'
Django version 3.0.7, using settings 'cookbook.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
@ -329,24 +316,25 @@ If you are using the provided fixtures, you will see the following response:
}
}
You can experiment with ``allCategories`` too.
Something to have in mind is the `auto camelcasing <http://docs.graphene-python.org/en/latest/types/schema/#auto-camelcase-field-names>`__ that is happening.
Congratulations, you have created a working GraphQL server 🥳!
Note: Graphene `automatically camelcases <http://docs.graphene-python.org/en/latest/types/schema/#auto-camelcase-field-names>`__ all field names for better compatibility with JavaScript clients.
Getting relations
-----------------
Right now, with this simple setup in place, we can query for relations too. This is where graphql becomes really powerful!
Using the current schema we can query for relations too. This is where GraphQL becomes really powerful!
For example, we may want to list all categories and in each category, all ingredients that are in that category.
For example, we may want to get a specific categories and list all ingredients that are in that category.
We can do that with the following query:
.. code::
query {
allCategories {
categoryByName(name: "Dairy") {
id
name
ingredients {
@ -356,43 +344,26 @@ We can do that with the following query:
}
}
This will give you (in case you are using the fixtures) the following result:
.. code::
{
"data": {
"allCategories": [
{
"id": "1",
"name": "Dairy",
"ingredients": [
{
"id": "1",
"name": "Eggs"
},
{
"id": "2",
"name": "Milk"
}
]
},
{
"id": "2",
"name": "Meat",
"ingredients": [
{
"id": "3",
"name": "Beef"
},
{
"id": "4",
"name": "Chicken"
}
]
}
]
"categoryByName": {
"id": "1",
"name": "Dairy",
"ingredients": [
{
"id": "1",
"name": "Eggs"
},
{
"id": "2",
"name": "Milk"
}
]
}
}
}
@ -411,71 +382,12 @@ We can also list all ingredients and get information for the category they are i
}
}
Getting single objects
----------------------
So far, we have been able to fetch list of objects and follow relation. But what about single objects?
We can update our schema to support that, by adding new query for ``ingredient`` and ``category`` and adding arguments, so we can query for specific objects.
Add the **Highlighted** lines to ``cookbook/ingredients/schema.py``
.. literalinclude:: schema.py
:emphasize-lines: 19-21,25-27,36-58
Now, with the code in place, we can query for single objects.
For example, lets query ``category``:
.. code::
query {
category(id: 1) {
name
}
anotherCategory: category(name: "Dairy") {
ingredients {
id
name
}
}
}
This will give us the following results:
.. code::
{
"data": {
"category": {
"name": "Dairy"
},
"anotherCategory": {
"ingredients": [
{
"id": "1",
"name": "Eggs"
},
{
"id": "2",
"name": "Milk"
}
]
}
}
}
As an exercise, you can try making some queries to ``ingredient``.
Something to keep in mind - since we are using one field several times in our query, we need `aliases <http://graphql.org/learn/queries/#aliases>`__
Summary
-------
As you can see, GraphQL is very powerful but there are a lot of repetitions in our example. We can do a lot of improvements by adding layers of abstraction on top of ``graphene-django``.
As you can see, GraphQL is very powerful and integrating Django models allows you to get started with a working server quickly.
If you want to put things like ``django-filter`` and automatic pagination in action, you should continue with the **relay tutorial.**
If you want to put things like ``django-filter`` and automatic pagination in action, you should continue with the :ref:`Relay tutorial`.
A good idea is to check the `graphene <http://docs.graphene-python.org/en/latest/>`__
documentation but it is not essential to understand and use Graphene-Django in your project.
A good idea is to check the `Graphene <http://docs.graphene-python.org/en/latest/>`__
documentation so that you are familiar with it as well.

View File

@ -1,3 +1,5 @@
.. _Relay tutorial:
Relay tutorial
========================================