LoginSignup
0
2

Django で GraphQL 実装してみた Query編

Last updated at Posted at 2024-06-21

Django で GraphQL 実装してみた

実装

基本的に、graphene のチュートリアルをなぞっていく.

モデルの作成

app/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

マイグレート

docker compose exec app-back python manage.py makemigrations
docker compose exec app-back python manage.py migrate

テストデータ生成

app/fixtures/ingredients.json
ここからダウンロード

docker compose exec app-back python manage.py loaddata ingredients

スキーマ作成

config/scheme.py

import graphene
import app.schema


class Query(app.schema.Query, graphene.ObjectType):
    pass


schema = graphene.Schema(query=Query)

app/schema.py

from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField

from .models import Category, Ingredient


class CategoryNode(DjangoObjectType):
    class Meta:
        model = Category
        fields = ("name", "ingredients")
        filter_fields = ["name", "ingredients"]
        interfaces = (relay.Node,)


class IngredientNode(DjangoObjectType):
    class Meta:
        model = Ingredient
        interfaces = (relay.Node,)
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "notes": ["exact", "icontains"],
            "category": ["exact"],
            "category__name": ["exact"],
        }


class Query:
    category = relay.Node.Field(CategoryNode)
    categories = DjangoFilterConnectionField(CategoryNode)

    ingredient = relay.Node.Field(IngredientNode)
    ingredients = DjangoFilterConnectionField(IngredientNode)

以下のようにすることで、クエリの詳細設定ができる

...
            "category__name": ["exact"],
            "category__is_active": ["exact"],
        }


+ class IngredientConnection(relay.Connection):
+     class Meta:
+         node = IngredientNode

class Query:
    category = relay.Node.Field(CategoryNode)
    categories = DjangoFilterConnectionField(CategoryNode)

    ingredient = relay.Node.Field(IngredientNode)
-   ingredients = DjangoFilterConnectionField(IngredientNode)
+   ingredients = relay.ConnectionField(IngredientNode)
+
+   def resolve_ingredients(root, info, **kwargs):
+       return Question.objects.all()

設定を追加する

config/setting.py


INSTALLED_APPS = [
    ...
    "graphene_django", # 追加
    "app",   # 追加
]

GRAPHENE = {"SCHEMA": "config.schema.schema"} # 追加


MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware", # 追加
    ...
]

config/urls.py


from django.contrib import admin
from django.urls import path

from graphene_django.views import GraphQLView # 追加
from config.schema import schema # 追加

urlpatterns = [
    path("admin/", admin.site.urls),
    # 以下追加
    path(
        "graphql/",
        GraphQLView.as_view(
            graphiql=True,
            schema=schema,
        ),
    ),
]

実行してみる

http://localhost/graphql にアクセス

左上のペインにクエリを入力する

query {
  ingredients {
    edges {
      node {
        id
        name
      }
    }
  }
}

再生ボタンを押下することで、右側ペインに結果が表示される

{
  "data": {
    "ingredients": {
      "edges": [
        {
          "node": {
            "id": "SW5ncmVkaWVudE5vZGU6MQ==",
            "name": "Eggs"
          }
        },
        {
          "node": {
            "id": "SW5ncmVkaWVudE5vZGU6Mg==",
            "name": "Milk"
          }
        },
        {
          "node": {
            "id": "SW5ncmVkaWVudE5vZGU6Mw==",
            "name": "Beef"
          }
        },
        {
          "node": {
            "id": "SW5ncmVkaWVudE5vZGU6NA==",
            "name": "Chicken"
          }
        }
      ]
    }
  }
}

絞り込み

app/schema.py の filter_fileds で設定できる

class IngredientNode(DjangoObjectType):
    class Meta:
        model = Ingredient
        interfaces = (relay.Node,)
        filter_fields = {
            "name": ["exact", "icontains", "istartswith"],
            "notes": ["exact", "icontains"],
            "category": ["exact"],
            "category__name": ["exact"],
        }
設定値 動作
exact 完全一致
icontains 指定値を含む
istartswith 指定値から始まる

category_name 完全一致の場合
リレーション先のカラム名は、パスカルケースで指定する

query {
  ingredients(category_Name: "Dairy") {
    edges {
      node {
        id
        name
      }
    }
  }
}
{
  "data": {
    "ingredients": {
      "edges": [
        {
          "node": {
            "id": "SW5ncmVkaWVudE5vZGU6MQ==",
            "name": "Eggs"
          }
        },
        {
          "node": {
            "id": "SW5ncmVkaWVudE5vZGU6Mg==",
            "name": "Milk"
          }
        }
      ]
    }
  }
}

ページネーション

条件 動作
first: n n 行取得する
offset: n n 行移行を取得する
after: id or cursor 指定の id や cursor 以降を取得する

query {
  ingredients(first:2) {
    pageInfo {
      startCursor
      endCursor
    }
    edges {
      cursor
      node {
        id
        name
      }
    }
  }
}

発見.GraphQL のいいところ

同時にリクエストすることが可能
:::note info
category(id: "\d")は、query categories を実行して表示された id を指定すること
:::

query {
  categories{
    edges {
      node {
        id
        name
      }
    }
  }
  category(id:"\d") {
    name
  }
  ingredients(category_IsActive: false) {
    pageInfo {
      startCursor
      endCursor
    }
    edges {
      cursor
      node {
        id
        name
      }
    }
  }
}

所感

セキュリティなどは何もしていないが「だいぶいいぞ」って感じがする.
なんといっても、必要なデータのみが取れる, 複数のqueryを同時に実行できるのが良い.
grapheneはquerysetをよしなにやってくれるため、rest frameworkより簡単な印象を受けた.

次は、Mutation編

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2