mirror of
https://github.com/graphql-python/graphene-django.git
synced 2025-02-06 06:30:36 +03:00
Add basic structure for plain tutorial
- `tutorial.rst` is now `tutorial-relay.rst` - Added `tutorial-plain.rst`
This commit is contained in:
parent
f3d9cbcd14
commit
61dd8c4277
|
@ -6,7 +6,8 @@ Contents:
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 0
|
:maxdepth: 0
|
||||||
|
|
||||||
tutorial
|
tutorial-plain
|
||||||
|
tutorial-relay
|
||||||
filtering
|
filtering
|
||||||
authorization
|
authorization
|
||||||
debug
|
debug
|
||||||
|
|
504
docs/tutorial-plain.rst
Normal file
504
docs/tutorial-plain.rst
Normal file
|
@ -0,0 +1,504 @@
|
||||||
|
Introduction tutorial - Graphene and Django
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
Graphene has a number of additional features that are designed to make
|
||||||
|
working with Django *really simple*.
|
||||||
|
|
||||||
|
Our primary focus here is to give a good understanding of how to connect models from Django ORM to graphene object types.
|
||||||
|
|
||||||
|
A good idea is to check the `graphene <http://docs.graphene-python.org/en/latest/>`__ documentation first.
|
||||||
|
|
||||||
|
Setup the Django project
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
We will setup the project, create the following:
|
||||||
|
|
||||||
|
- A Django project called ``cookbook``
|
||||||
|
- An app within ``cookbook`` called ``ingredients``
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
# Create the project directory
|
||||||
|
mkdir cookbook
|
||||||
|
cd cookbook
|
||||||
|
|
||||||
|
# Create a virtualenv to isolate our package dependencies locally
|
||||||
|
virtualenv env
|
||||||
|
source env/bin/activate # On Windows use `env\Scripts\activate`
|
||||||
|
|
||||||
|
# Install Django and Graphene with Django support
|
||||||
|
pip install django
|
||||||
|
pip install graphene_django
|
||||||
|
|
||||||
|
# Set up a new project with a single application
|
||||||
|
django-admin.py startproject cookbook . # Note the trailing '.' character
|
||||||
|
cd cookbook
|
||||||
|
django-admin.py startapp ingredients
|
||||||
|
|
||||||
|
Now sync your database for the first time:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
python manage.py migrate
|
||||||
|
|
||||||
|
Let's create a few simple models...
|
||||||
|
|
||||||
|
Defining our models
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Let's get started with these models:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
Don't forget to create & run migrations:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
|
||||||
|
Load some test data
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Now is a good time to load up some test data. The easiest option will be
|
||||||
|
to `download the
|
||||||
|
ingredients.json <https://raw.githubusercontent.com/graphql-python/graphene-django/master/examples/cookbook/cookbook/ingredients/fixtures/ingredients.json>`__
|
||||||
|
fixture and place it in
|
||||||
|
``cookbook/ingredients/fixtures/ingredients.json``. You can then run the
|
||||||
|
following:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ 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``).
|
||||||
|
|
||||||
|
Hello GraphQL - Schema and Object Types
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
In order to make queries to our Django project, we are going to need few things:
|
||||||
|
|
||||||
|
* Schema with defined object types
|
||||||
|
* A view, taking queries as input and returning the result
|
||||||
|
|
||||||
|
GraphQL presents your objects to the world as a graph structure rather
|
||||||
|
than a more hierarchical structure to which you may be accustomed. In
|
||||||
|
order to create this representation, Graphene needs to know about each
|
||||||
|
*type* of object which will appear in the graph.
|
||||||
|
|
||||||
|
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``
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# cookbook/ingredients/schema.py
|
||||||
|
import graphene
|
||||||
|
|
||||||
|
from graphene_django.types import DjangoObjectType
|
||||||
|
|
||||||
|
from cookbook.ingredients.models import Category, Ingredient
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Ingredient
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.AbstractType):
|
||||||
|
all_categories = graphene.List(CategoryType)
|
||||||
|
all_ingredients = graphene.List(IngredientType)
|
||||||
|
|
||||||
|
def resolve_all_categories(self, args, context, info):
|
||||||
|
return Category.objects.all()
|
||||||
|
|
||||||
|
def resolve_all_ingredients(self, args, context, info):
|
||||||
|
# We can easily optimize query count in the resolve method
|
||||||
|
return Ingredient.objects.select_related('category').all()
|
||||||
|
|
||||||
|
|
||||||
|
Note that the above ``Query`` class is marked as 'abstract'. This is
|
||||||
|
because we will now create a project-level query which will combine all
|
||||||
|
our app-level queries.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
Testing everything so far
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
We are going to do some configuration work, in order to have a working Django where we can test queries, before we move on, updating our schema.
|
||||||
|
|
||||||
|
Update settings
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Next, install your app and GraphiQL in your Django project. GraphiQL is
|
||||||
|
a web-based integrated development environment to assist in the writing
|
||||||
|
and executing of GraphQL queries. It will provide us with a simple and
|
||||||
|
easy way of testing our cookbook project.
|
||||||
|
|
||||||
|
Add ``ingredients`` and ``graphene_django`` to ``INSTALLED_APPS`` in ``cookbook/settings.py``:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
...
|
||||||
|
# This will also make the `graphql_schema` management command available
|
||||||
|
'graphene_django',
|
||||||
|
|
||||||
|
# Install the ingredients app
|
||||||
|
'ingredients',
|
||||||
|
]
|
||||||
|
|
||||||
|
And then add the ``SCHEMA`` to the ``GRAPHENE`` config in ``cookbook/settings.py``:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
GRAPHENE = {
|
||||||
|
'SCHEMA': 'cookbook.schema.schema'
|
||||||
|
}
|
||||||
|
|
||||||
|
Alternatively, we can specify the schema to be used in the urls definition,
|
||||||
|
as explained below.
|
||||||
|
|
||||||
|
Creating GraphQL and GraphiQL views
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Unlike a RESTful API, there is only a single URL from which GraphQL is
|
||||||
|
accessed. Requests to this URL are handled by Graphene's ``GraphQLView``
|
||||||
|
view.
|
||||||
|
|
||||||
|
This view will serve as GraphQL endpoint. As we want to have the
|
||||||
|
aforementioned GraphiQL we specify that on the params with ``graphiql=True``.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from django.conf.urls import url, include
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^admin/', admin.site.urls),
|
||||||
|
url(r'^graphql', GraphQLView.as_view(graphiql=True)),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
If we didn't specify the target schema in the Django settings file
|
||||||
|
as explained above, we can do so here using:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from django.conf.urls import url, include
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
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)),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Testing our GraphQL schema
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
We're now ready to test the API we've built. Let's fire up the server
|
||||||
|
from the command line.
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
$ python ./manage.py runserver
|
||||||
|
|
||||||
|
Performing system checks...
|
||||||
|
Django version 1.9, using settings 'cookbook.settings'
|
||||||
|
Starting development server at http://127.0.0.1:8000/
|
||||||
|
Quit the server with CONTROL-C.
|
||||||
|
|
||||||
|
Go to `localhost:8000/graphiql <http://localhost:8000/graphiql>`__ and
|
||||||
|
type your first query!
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
query {
|
||||||
|
allIngredients {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
If you are using the provided fixtures, you will see the following response:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"allIngredients": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"name": "Eggs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"name": "Milk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"name": "Beef"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"name": "Chicken"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 happeing.
|
||||||
|
|
||||||
|
|
||||||
|
Getting relations
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Right now, with this simple setup in place, 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.
|
||||||
|
|
||||||
|
We can do that with the following query:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
query {
|
||||||
|
allCategories {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
ingredients {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
We can also list all ingredients and get information for the category they are in:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
query {
|
||||||
|
allIngredients {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
category {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
import graphene
|
||||||
|
|
||||||
|
from graphene_django.types import DjangoObjectType
|
||||||
|
|
||||||
|
from cookbook.ingredients.models import Category, Ingredient
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientType(DjangoObjectType):
|
||||||
|
class Meta:
|
||||||
|
model = Ingredient
|
||||||
|
|
||||||
|
|
||||||
|
class Query(graphene.AbstractType):
|
||||||
|
category = graphene.Field(CategoryType,
|
||||||
|
id=graphene.Int(),
|
||||||
|
name=graphene.String())
|
||||||
|
all_categories = graphene.List(CategoryType)
|
||||||
|
|
||||||
|
|
||||||
|
ingredient = graphene.Field(IngredientType,
|
||||||
|
id=graphene.Int(),
|
||||||
|
name=graphene.String())
|
||||||
|
all_ingredients = graphene.List(IngredientType)
|
||||||
|
|
||||||
|
def resolve_all_categories(self, args, context, info):
|
||||||
|
return Category.objects.all()
|
||||||
|
|
||||||
|
def resolve_all_ingredients(self, args, context, info):
|
||||||
|
return Ingredient.objects.all()
|
||||||
|
|
||||||
|
def resolve_category(self, args, context, info):
|
||||||
|
id = args.get('id')
|
||||||
|
name = args.get('name')
|
||||||
|
|
||||||
|
if id is not None:
|
||||||
|
return Category.objects.get(pk=id)
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
return Category.objects.get(name=name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def resolve_ingredient(self, args, context, info):
|
||||||
|
id = args.get('id')
|
||||||
|
name = args.get('name')
|
||||||
|
|
||||||
|
if id is not None:
|
||||||
|
return Ingredient.objects.get(pk=id)
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
return Ingredient.objects.get(name=name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
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 excercise, 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>`__
|
|
@ -1,5 +1,5 @@
|
||||||
Graphene-Django Tutorial
|
Graphene-Django Tutorial using Relay
|
||||||
========================
|
====================================
|
||||||
|
|
||||||
Graphene has a number of additional features that are designed to make
|
Graphene has a number of additional features that are designed to make
|
||||||
working with Django *really simple*.
|
working with Django *really simple*.
|
Loading…
Reference in New Issue
Block a user