この記事は、元々 https://qiita.com/TakesxiSximada/items/d64e34f77ff9139f7e1f に投稿していた記事だが、このサイトを使用しなくなったため、記事を移植しておく。
—-
GraphQLのためのPython製フレームワークに [graphene](http://graphene-python.org/) がある。そしてgrapheneにはO/R Mapperで使いやすくしたライブラリがいくつかある。 今回はその中の1つ [graphene-django](https://github.com/graphql-python/graphene-django/) を使ってみる。
インストール
venv環境を作ってactivateする。
$ ~/ng/home/src/develop/pyvm/pythons/Python-3.5.2/bin/python3 -m venv env
$ source env/bin/activate
(env) $
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
プロジェクトを作成する
django-admin startprojet
でプロジェクトを作成する。今回は myproj
にした。
(env) $ django-admin startproject myproj .
以下のようなディレクトリ構成となる。
(env) $ tree myproj
myproj
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
schemaを定義する
myproj/schema.pyにAPIのスキーマを定義する。
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)
モデルを作るのが面倒だったため django.contrib.auth.models.User()
を使った。
設定を追加する
graphene_djangoのための設定を追加する。
INSTALL_APPS に graphene_django を追加する
GRAPHENE にスキーマへのdotted nameを設定する
settings.pyに先ほど作成したschema.pyの中のschemaオブジェクトまでのdotted name (foo.bar.bazみたいなやつ) を指定する。
graphqlのリクエストを受け付けるためのURLを追加する
from django.conf.urls import url
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)), # <- 追加
]
http://localhost:8000/graphql/
がAPIのリクエストを受け付けるURLになる。graphqlのリクエストを組みためのgraphiqlという画面が用意されている。=graphiql=True= を指定すると有効になる。
起動する
migrate
した後、起動する。
(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) $
起動する。
(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.
ブラウザで http://localhost:8000/graphql/ にアクセスすると、画面が表示される。
取得する
データベースが空の状態のためユーザを作っておく。
(env) $ python manage.py createsuperuser
Username (leave blank to use 'sximada'): foo
Email address: [email protected]
Password:
Password (again):
Superuser created successfully.
では、graphiql画面でqueryを発行する。左側のペインに以下のクエリを入力する。
query {
users {
id
username
email
isSuperuser
isStaff
}
}
入力したら左上部にある再生マークをクリックする。すると右側のペインに以下のような結果が表示される。
{
"data": {
"users": [
{
"id": "1",
"username": "foo",
"email": "[email protected]",
"isSuperuser": true,
"isStaff": true
}
]
}
}
ユーザの情報が取得できた。ユーザが複数いる場合は以下のようになる。
{
"data": {
"users": [
{
"id": "1",
"username": "foo",
"email": "[email protected]",
"isSuperuser": true,
"isStaff": true
},
{
"id": "2",
"username": "bar",
"email": "[email protected]",
"isSuperuser": true,
"isStaff": true
}
]
}
}
proj.schema.Query.resolve_users()で全てのユーザを返しているため、全ユーザが一覧になって出力される。
@graphene.resolve_only_args
def resolve_users(self):
return auth_models.User.objects.all() # <- ココ
id指定でユーザ指定して取得する
idを指定してユーザを指定したいので、myproj/schema.py のQueryクラスを以下のように変更する。
開発サーバを再起動し、以下のqueryを実行する。
query {
user(id: "1") {
id
username
email
isSuperuser
isStaff
}
}
実行すると次の結果が得られる。
{
"data": {
"user": {
"id": "1",
"username": "foo",
"email": "[email protected]",
"isSuperuser": true,
"isStaff": true
}
}
}
今度はidで指定したユーザが取得できた。もしemailが必要なければqueryからemailを削除してしまえばAPIサーバはemailを返さない。どの情報を返して欲しいか (例えばemailが欲しいなど) をクライアント側で指定できるので、解析も楽になるし、クライアント側の仕様変更で新しいfieldを取得したい場合にも、API側の修正をしなくてすむ。また必要のないデータのやり取りのしなくてすむ。
気をつけるところとしては アンダースコアのあるフィールド名が、アンダースコアが省略されlowerキャメルケースになる点がある。
例)
auth_user.is_superuser
→isSuperuser
auth_user.is_staff
→iStaffr
存在しないidの場合は .first()
でNoneになるのでnullになる。
両方を同時にリクエストすることもできる
フィルターする
実際のアプリケーションなどではレコードを全て取得するよりも、条件をつけてフィルターすることの方が多いだろう。先ほどのusersをfilterできるように書き換える。
import graphene
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField # <- 追加
from django.contrib.auth import models as auth_models
class User(DjangoObjectType):
class Meta:
model = auth_models.User
filter_fields = ('username', 'email', 'is_staff') # <- 追加
interfaces = (graphene.relay.Node,) # <- 追加
class Query(graphene.ObjectType):
user = graphene.Field(User, id=graphene.String())
users = DjangoFilterConnectionField(User) # <- 変更
@graphene.resolve_only_args
def resolve_user(self, id):
return auth_models.User.objects.filter(pk=id).first()
# resolve_users()メソッドは削除
schema = graphene.Schema(query=Query)
filter_fields
にモデルの属性名を指定する。ここで指定した属性でフィルターできる。開発サーバを再起動して次のクエリを実行する。
query {
users(isStaff: true) {
edges {
node {
username
email
isStaff
}
}
}
}
isStaff: true
と指定している。スタッフ属性がついているユーザだけが返却される。
{
"data": {
"users": {
"edges": [
{
"node": {
"username": "foo",
"email": "[email protected]",
"isStaff": true
}
},
{
"node": {
"username": "bar",
"email": "[email protected]",
"isStaff": true
}
}
]
}
}
}
foo
ユーザのスタッフ属性を外すと以下の結果になる。
{
"data": {
"users": {
"edges": [
{
"node": {
"username": "bar",
"email": "[email protected]",
"isStaff": true
}
}
]
}
}
}
所感
軽く触ってみた感想ですが結構癖があるなあと感じた。プロダクションで使うには、GraphQLについて知る必要があるのはもちろんだが、grapheneの使い方、graphene_djangoの使い方も一通り押さえておかないと、ハマって抜け出せないとう状況に陥りそうだった。ただ使いどころによってはすごく便利だなあとも思う。APIを何発も撃って表示していたところを1リクエストで済むので良い。GraphQLはfacebookが公開した仕様でフロントエンドではRelayで使うことができる。そっちも合わせてもうちょっと遊んでみたい気にはなった。