使用Django和graphene_django来创建GraphQL API

使用Django和graphene_django构建GraphQL API。

这篇文章是 Django Advent Calendar 2016 的第19天的文章。

在用于GraphQL的Python框架中,有一个名为graphene的库。graphene包含了几个可供使用的易用性对象关系映射器库。今天我们将尝试使用其中的一个,叫做graphene-django。

安装

让我们创建一个venv环境并进行激活。

$ ~/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 startproject 命令来创建项目。
暂时将其命名为 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。

将 graphene_django 添加到 INSTALL_APPS 中

我的项目/settings.py::

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

将GRAPHENE的dotted name设置为模式中的dotted name。

在settings.py中指定dotted name(类似于foo.bar.baz)到先前创建的schema.py中的schema对象。

我的项目/设置.py::

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

添加一个用于接收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。
有一个名为graphiql的界面可用于构建graphql请求。
设置graphiql=True即可启用。

启动

搬迁后,让我们试着启动一下。

(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: test@example.com
Password:
Password (again):
Superuser created successfully.

让我们尝试在GraphiQL界面上执行查询。
在左侧面板中输入以下查询。

query {
  users {
    id
    username
    email
    isSuperuser
        isStaff
  }
}

输入后,点击左上角的播放按钮。
然后在右侧面板上会显示如下结果。

{
  "data": {
    "users": [
      {
        "id": "1",
        "username": "foo",
        "email": "test@example.com",
        "isSuperuser": true,
        "isStaff": true
      }
    ]
  }
}

成功获取到用户的信息。如果存在多个用户,则如下所示。

{
  "data": {
    "users": [
      {
        "id": "1",
        "username": "foo",
        "email": "test@example.com",
        "isSuperuser": true,
        "isStaff": true
      },
      {
        "id": "2",
        "username": "bar",
        "email": "test2@example.com",
        "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类如下。

我的项目/schema.py文件

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

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

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


重新启动开发服务器,执行以下查询。

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

运行后将获得以下结果。

{
  "data": {
    "user": {
      "id": "1",
      "username": "foo",
      "email": "test@example.com",
      "isSuperuser": true,
      "isStaff": true
    }
  }
}

这次,可以通过ID指定的用户来获取了。
如果不需要email,请将其从查询中删除,API服务器将不会返回email。
由于可以在客户端指定想要返回的信息(例如,要email),因此分析变得更加容易,并且在客户端的规范更改中,
似乎无需对API进行修改即可获取新的字段。此外,还可以避免不必要的数据交互。

需要注意的是,带有下划线的字段名变成了省略下划线并且使用小驼峰式命名法。

单身意味着你独自一人,没有伴侣。

auth_user.is_superuser → isSuperuser

auth_user.is_staff → iStaffr

如果id不存在,则使用 .first() 将返回None,因此会变成null。

查询::

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

结果

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

可以同时请求两个选项。

查询:

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

结果:

{
  "data": {
    "user": {
      "id": "1",
      "username": "foo",
      "email": "test@example.com",
      "isSuperuser": true,
      "isStaff": true
    },
    "users": [
      {
        "id": "1",
        "username": "foo",
        "lastLogin": null
      },
      {
        "id": "2",
        "username": "bar",
        "lastLogin": null
      }
    ]
  }
}

过滤

实际上,在实际应用中,与其获取所有记录,更常见的是应用条件进行筛选。

我們將對先前的用戶進行篩選以進行修改。

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": "test@example.com",
            "isStaff": true
          }
        },
        {
          "node": {
            "username": "bar",
            "email": "test2@example.com",
            "isStaff": true
          }
        }
      ]
    }
  }
}

将foo用户的员工属性移除会产生以下结果。

{
  "data": {
    "users": {
      "edges": [
        {
          "node": {
            "username": "bar",
            "email": "test2@example.com",
            "isStaff": true
          }
        }
      ]
    }
  }
}

管理

我輕輕觸摸了一下,感覺有點怪異。如果要在生產中使用,當然需要了解GraphQL,同時也需要掌握graphene和graphene_django的使用方法,否則可能會陷入困境難以擺脫。

只要根据使用情况,这东西非常方便呢,我也有这样的想法。
可以通过一个请求完成多次调用API并显示出来,非常好。
GraphQL 是 Facebook 公开的规范,在前端可以通过 Relay 使用。
考虑到这一点,我也想再玩一下。

广告
将在 10 秒后关闭
bannerAds