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.
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 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
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.
We will add settings for graphene_django.
graphene_django
to INSTALL_APPSmyproj/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
]
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'
}
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
.
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.
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
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)
auth_user.is_superuser
→ isSuperuser
auth_user.is_staff
→ iStaffr
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
}
}
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
}
]
}
}
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
}
}
]
}
}
}
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