[PYTHON] GraphQL API with graphene_django in Django

GraphQL API with graphene_django in Django

This article is the 19th day of Django Advent Calendar 2016.

A Python framework for GraphQL is graphene. graphene has several libraries that are easier to use with O / R Mapper. This time, I will use one of them graphene-django.

Installation

Let's create a venv environment and activate it.

$ ~/ng/home/src/develop/pyvm/pythons/Python-3.5.2/bin/python3 -m venv env
$ source env/bin/activate
(env) $

Install with pip.

(env) $ pip install graphene_django
Collecting graphene-django
  Using cached graphene-django-1.2.1.tar.gz
Collecting six>=1.10.0 (from graphene-django)
Collecting graphene>=1.1.3 (from graphene-django)
  Using cached graphene-1.1.3.tar.gz
Collecting Django>=1.6.0 (from graphene-django)
  Using cached Django-1.10.4-py2.py3-none-any.whl
Collecting iso8601 (from graphene-django)
  Using cached iso8601-0.1.11-py2.py3-none-any.whl
Collecting singledispatch>=3.4.0.3 (from graphene-django)
  Using cached singledispatch-3.4.0.3-py2.py3-none-any.whl
Collecting graphql-core>=1.0.1 (from graphene>=1.1.3->graphene-django)
  Using cached graphql-core-1.0.1.tar.gz
Collecting graphql-relay>=0.4.5 (from graphene>=1.1.3->graphene-django)
  Using cached graphql-relay-0.4.5.tar.gz
Collecting promise>=1.0.1 (from graphene>=1.1.3->graphene-django)
  Using cached promise-1.0.1.tar.gz
Collecting typing (from promise>=1.0.1->graphene>=1.1.3->graphene-django)
  Using cached typing-3.5.2.2.tar.gz
Installing collected packages: six, typing, promise, graphql-core, graphql-relay, graphene, Django, iso8601, singledispatch, graphene-django
  Running setup.py install for typing ... done
  Running setup.py install for promise ... done
  Running setup.py install for graphql-core ... done
  Running setup.py install for graphql-relay ... done
  Running setup.py install for graphene ... done
  Running setup.py install for graphene-django ... done
Successfully installed Django-1.10.4 graphene-1.1.3 graphene-django-1.2.1 graphql-core-1.0.1 graphql-relay-0.4.5 iso8601-0.1.11 promise-1.0.1 singledispatch-3.4.0.3 six-1.10.0 typing-3.5.2.2

Create a project

Create a project with django-admin startprojet. For the time being, leave it as myproj.

(env) $ django-admin startproject myproj .

The directory structure looks like this.

(env) $ tree myproj
myproj
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py

Define schema

Let's define the API schema in myproj / schema.py.

import graphene
from graphene_django import DjangoObjectType
from django.contrib.auth import models as auth_models


class User(DjangoObjectType):
    class Meta:
        model = auth_models.User


class Query(graphene.ObjectType):
    users = graphene.List(User)

    @graphene.resolve_only_args
    def resolve_users(self):
        return auth_models.User.objects.all()


schema = graphene.Schema(query=Query)

I used django.contrib.auth.models.User () because it was a hassle to create a model.

Add settings

We will add settings for graphene_django.

Add graphene_django to INSTALL_APPS

myproj/settings.py::

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'graphene_django',  # <-add to
]

Set GRAPHENE to dotted name to schema

In settings.py, specify the dotted name (like foo.bar.baz) up to the schema object in schema.py created earlier.

myproj/settings.py::

GRAPHENE = {
    'SCHEMA': 'myproj.schema.schema'
}

Add URL to accept graphql request

from django.conf.urls import url
from django.contrib import admin

from graphene_django.views import GraphQLView  # <-add to

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^graphql/', GraphQLView.as_view(graphiql=True)),  # <-add to
]

http: // localhost: 8000 / graphql / is the URL that accepts API requests. There is a screen called graphiql for composing graphql requests. Enabled by specifying graphiql = True.

to start

After migrate, let's start it.

(env) $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
(env) $

start up.

(env) $ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
December 20, 2016 - 13:28:32
Django version 1.10.4, using settings 'myproj.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Try accessing http: // localhost: 8000 / graphql / with your browser.

Screen is displayed.

get

Create a user because the database is empty.

(env) $ python manage.py createsuperuser
Username (leave blank to use 'sximada'): foo
Email address: [email protected]
Password:
Password (again):
Superuser created successfully.

Let's issue a query on the graphiql screen. Enter the following query in the left pane.

query {
  users {
    id
    username
    email
    isSuperuser
        isStaff
  }
}

After entering, click the play mark in the upper left. Then, the following result will be displayed in the right pane.

{
  "data": {
    "users": [
      {
        "id": "1",
        "username": "foo",
        "email": "[email protected]",
        "isSuperuser": true,
        "isStaff": true
      }
    ]
  }
}

I was able to get the user information. If there are multiple users, it will be as follows.

{
  "data": {
    "users": [
      {
        "id": "1",
        "username": "foo",
        "email": "[email protected]",
        "isSuperuser": true,
        "isStaff": true
      },
      {
        "id": "2",
        "username": "bar",
        "email": "[email protected]",
        "isSuperuser": true,
        "isStaff": true
      }
    ]
  }
}

Since proj.schema.Query.resolve_users () returns all users All users are output as a list.

    @graphene.resolve_only_args
    def resolve_users(self):
        return auth_models.User.objects.all()  # <-Here

Get by specifying the user by specifying the id

I want to specify the user by specifying the id, so Modify the Query class in myproj / schema.py as follows:

myproj/schema.py::

class Query(graphene.ObjectType):
    user = graphene.Field(User, id=graphene.String())  # <-add to
    users = graphene.List(User)

    @graphene.resolve_only_args                                # <-add to
    def resolve_user(self, id):                                # <-add to
        return auth_models.User.objects.filter(pk=id).first()  # <-add to

    @graphene.resolve_only_args
    def resolve_users(self):
        return auth_models.User.objects.all()


Restart the development server and execute the following query.

query {
  user(id: "1") {
    id
    username
    email
    isSuperuser
        isStaff
  }
}

When run, you will get the following results:

{
  "data": {
    "user": {
      "id": "1",
      "username": "foo",
      "email": "[email protected]",
      "isSuperuser": true,
      "isStaff": true
    }
  }
}

This time, the user specified by id could be obtained. If you don't need the email, you can remove the email from the query and the API server will not return the email. Since you can specify what information you want to return (for example, you want email) on the client side. Analysis will be easier, and if you want to get a new field by changing the specifications on the client side, It seems that there is no need to modify the API side. Also, you don't have to exchange unnecessary data.

One thing to keep in mind is that field names with underscores The underscore is omitted and it becomes a lower camel case.

Example)

If the id does not exist, .first () will be None, so it will be null.

Query ::

query {
  user(id: "6589645936543") {
    id
    username
    email
    isSuperuser
    isStaff
  }
}

result::

{
  "data": {
    "user": null
  }
}

You can request both at the same time

Query ::

query {
  user(id: "1") {
    id
    username
    email
    isSuperuser
    isStaff
  }
  users {
    id
    username
    lastLogin
  }
}

result::

{
  "data": {
    "user": {
      "id": "1",
      "username": "foo",
      "email": "[email protected]",
      "isSuperuser": true,
      "isStaff": true
    },
    "users": [
      {
        "id": "1",
        "username": "foo",
        "lastLogin": null
      },
      {
        "id": "2",
        "username": "bar",
        "lastLogin": null
      }
    ]
  }
}

Filter

In an actual application, rather than getting all the records It's more likely that you will filter with conditions.

Rewrite the previous users so that they can be filtered.

import graphene
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField  # <-add to

from django.contrib.auth import models as auth_models


class User(DjangoObjectType):
    class Meta:
        model = auth_models.User
        filter_fields = ('username', 'email', 'is_staff')  # <-add to
        interfaces = (graphene.relay.Node,)                # <-add to


class Query(graphene.ObjectType):
    user = graphene.Field(User, id=graphene.String())
    users = DjangoFilterConnectionField(User)  # <-Change

    @graphene.resolve_only_args
    def resolve_user(self, id):
        return auth_models.User.objects.filter(pk=id).first()

    # resolve_users()Method removed

schema = graphene.Schema(query=Query)

Specify the model attribute name in filter_fields. You can filter by the attributes specified here.

Restart the development server and run the following query.

query {
  users(isStaff: true) {
    edges {
      node {
        username
        email
        isStaff
      }
    }
  }
}

ʻIs Staff: true` is specified. Only users with the staff attribute will be returned.

{
  "data": {
    "users": {
      "edges": [
        {
          "node": {
            "username": "foo",
            "email": "[email protected]",
            "isStaff": true
          }
        },
        {
          "node": {
            "username": "bar",
            "email": "[email protected]",
            "isStaff": true
          }
        }
      ]
    }
  }
}

If you remove the staff attribute of the foo user, you will get the following result.

{
  "data": {
    "users": {
      "edges": [
        {
          "node": {
            "username": "bar",
            "email": "[email protected]",
            "isStaff": true
          }
        }
      ]
    }
  }
}

Jurisdiction

It was an impression that I touched it lightly, but I felt that I had a habit. Of course you need to know GraphQL to use it in production, I have to understand how to use graphene and how to use graphene_django. I'm going to get into a situation where I'm addicted to it.

However, I also thought that it would be very convenient depending on the purpose of use. It's good because you only need one request to shoot and display the API many times. GraphQL is a specification published by facebook and can be used in Relay on the front end. It made me want to play with that as well.

Recommended Posts

GraphQL API with graphene_django in Django
Create an API with Django
Qiita API Oauth with Django
reload in django shell with ipython
Try hitting the Spotify API in Django.
Models in Django
Internationalization with django
CRUD with Django
Forms in Django
Save multiple models in one form with Django
Build a Django environment with Vagrant in 5 minutes
How to create a Rest Api in Django
Implementing authentication in Django REST Framework with djoser
Django 1.11 started with Python3.6
Develop a web API that returns data stored in DB with Django and SQLite
Development digest with Django
Output PDF with Django
Markdown output with Django
Hit the Twitter API after Oauth authentication with Django
Use Gentelella with django
Playing with a user-local artificial intelligence API in Python
Twitter OAuth with Django
Evernote API in Python
Getting Started with Django 1
Send email with Django
Model changes in Django
Extrude with Fusion360 API
Dynamically create tables in schema with Django, dynamically generate models
Pooling mechanize with Django
C API in Python 3
Use MySQL with Django
Start today with Django
Getting Started with Django 2
Implement hierarchical URLs with drf-nested-routers in Django REST framework
Flow of extracting text in PDF with Cloud Vision API
Try running python in a Django environment created with pipenv
[Django] Manage settings like writing in settings.py with a model
[Django] How to give input values in advance with ModelForm
Until I return something with a line bot in Django!
Create an authentication feature with django-allauth and CustomUser in Django
I can't log in to the admin page with Django3
[Translated article] Fast API is actually very compatible with Django
Get YouTube Live chat field in real time with API
Create a web API that can deliver images with Django
(For beginners) Try creating a simple web API with Django
Image analysis with Object Detection API to try in 1 hour
Issue reverse geocoding in Japanese with Python Google Maps API
Create a social integration API for smartphone apps with Django
Hit Mastodon's API in Python
Image download with Flickr API
Performance optimization in Django 3.xx
Do Django with CodeStar (Python3.6.8, Django2.2.9)
PHP var_dump in Django templates
Handle constants in Django templates
Use "$ in" operator with mongo-go-driver
Implement follow functionality in Django
Get started with Django! ~ Tutorial ⑤ ~
Minimal website environment with django
Working with LibreOffice in Python
Scraping with chromedriver in python
Use Twitter API with Python