3 ๋ถ„ ์†Œ์š”

[๊ณต์‹ ๋ฌธ์„œ] Basic Tutorial ์‹œ์ž‘!

graphene-django์˜ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉฐ ์ง„ํ–‰ํ–ˆ๋‹ค.

graphene-django๋Š” Django๋กœ ์‰ฝ๊ฒŒ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค.

์ด ํŠœํ† ๋ฆฌ์–ผ์˜ ์ฃผ์š” ์ดˆ์ ์€ Django ORM์—์„œ Graphene ๊ฐ์ฒด ์œ ํ˜•์œผ๋กœ ๋ชจ๋ธ์„ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž˜ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

Django ํ”„๋กœ์ ํŠธ ์„ธํŒ…ํ•˜๊ธฐ

pip install django graphene_django

django-admin startproject cookbook .
python manage.py startapp ingredients
python manage.py migrate
cookbook
|
| - cookbook
| | - asgi.py
| | - settings.py
| | - urls.py
| | - wsgi.py
|
| - ingredients
| | - migrations
| | - admin.py
| | - models.py
| | - tests.py
| | - views.py
|
| - db.sqlite3
| - manage.py

ingredients/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", on_delete=models.CASCADE
    )

    def __str__(self):
        return self.name

cookbook/settings.py

INSTALLED_APPS = [
    ...
    
    "ingredients",
]
python manage.py makemigrations
python manage.py migrate

Sample Data ๋กœ๋“œํ•˜๊ธฐ

Sample Data ๋ฅผ ๋‹ค์šด๋กœ๋“œ(๋ณต์‚ฌ)ํ•ด์„œ, ingredients/ingredients.json ํŒŒ์ผ๋กœ ๋ถ™์—ฌ๋„ฃ์ž.

ingredients/ingredients.json

[
    {
      "fields": {
        "name": "Dairy"
      },
      "model": "ingredients.category",
      "pk": 1
    },
    {
      "fields": {
        "name": "Meat"
      },
      "model": "ingredients.category",
      "pk": 2
    },
    {
      "fields": {
        "category": 1,
        "name": "Eggs",
        "notes": "Good old eggs"
      },
      "model": "ingredients.ingredient",
      "pk": 1
    },
    {
      "fields": {
        "category": 1,
        "name": "Milk",
        "notes": "Comes from a cow"
      },
      "model": "ingredients.ingredient",
      "pk": 2
    },
    {
      "fields": {
        "category": 2,
        "name": "Beef",
        "notes": "Much like milk, this comes from a cow"
      },
      "model": "ingredients.ingredient",
      "pk": 3
    },
    {
      "fields": {
        "category": 2,
        "name": "Chicken",
        "notes": "Definitely doesn't come from a cow"
      },
      "model": "ingredients.ingredient",
      "pk": 4
    }
  ]

์ด ์ž‘์—…์„ ์™„๋ฃŒํ–ˆ์œผ๋ฉด, ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด๋ณด์ž!

python manage.py loaddata ingredients/ingredients.json

Installed 6 object(s) from 1 fixture(s)

ingredients/admin.py

๊ทธ ๋‹ค์Œ, python [manage.py](http://manage.py) createsuperuser ๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ์ž๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋˜, superuser์˜ ์ž‘์—…์„ ์œ„ํ•ด admin์— ๋“ฑ๋กํ•ด์ค€๋‹ค.

from django.contrib import admin
from cookbook.ingredients.models import Category, Ingredient

admin.site.register(Category)
admin.site.register(Ingredient)

Hello GraphQL - Schema & Object Types

Django ํ”„๋กœ์ ํŠธ์— query๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด ๋‹ค์Œ ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค.

  • ๊ฐ์ฒด ์œ ํ˜•์ด ์ •์˜๋œ schema
  • ์ฟผ๋ฆฌ๋ฅผ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” views

GraphQL์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ต์ˆ™ํ•œ ๊ณ„์ธต ๊ตฌ์กฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ทธ๋ž˜ํ”„ ๊ตฌ์กฐ๋กœ ๊ฐ์ฒด๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค.

์ด ํ‘œํ˜„์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ทธ๋ž˜ํ”„์— ํ‘œ์‹œ๋  ๊ฐ ๊ฐ์ฒด์˜ ์œ ํ˜•์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•œ๋‹ค.

์ด ๊ทธ๋ž˜ํ”„์—๋Š” ๋ชจ๋“  ์ ‘๊ทผ์ด ์‹œ์ž‘๋˜๋Š” root ์œ ํ˜•์ด ์กด์žฌํ•œ๋‹ค.

์ด๊ฒƒ์ด ์•„๋ž˜์˜ Query ํด๋ž˜์Šค์ด๋‹ค.

๊ฐ Django ๋ชจ๋ธ์— ๋Œ€ํ•œ GraphQL ํƒ€์ž…์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด, Django ๋ชจ๋ธ์˜ ํ•„๋“œ์— ํ•ด๋‹นํ•˜๋Š” GraphQL ํ•„๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ •์˜ํ•˜๋Š” DjangoObjectType ํด๋ž˜์Šค๋ฅผ ์„œ๋ธŒํด๋ž˜์Šค๋กœ ๋งŒ๋“ ๋‹ค.

ํ›„์—, ํ•ด๋‹น ํƒ€์ž…์„ Query ํด๋ž˜์Šค์— ํ•„๋“œ๋กœ ๋‚˜์—ดํ•œ๋‹ค.

cookbook/schema.py

cookbook ์— schema.py ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

import graphene
from graphene_django import DjangoObjectType

from cookbook.ingredients.models import Category, Ingredient

class CategoryType(DjangoObjectType):
    class Meta:
        model = Category
        fields = ("id", "name", "ingredients")

class IngredientType(DjangoObjectType):
    class Meta:
        model = Ingredient
        fields = ("id", "name", "notes", "category")

class Query(graphene.ObjectType):
    all_ingredients = graphene.List(IngredientType)
    category_by_name = graphene.Field(CategoryType, name=graphene.String(required=True))

    def resolve_all_ingredients(root, info):
        # We can easily optimize query count in the resolve method
        return Ingredient.objects.select_related("category").all()

    def resolve_category_by_name(root, info, name):
        try:
            return Category.objects.get(name=name)
        except Category.DoesNotExist:
            return None

schema = graphene.Schema(query=Query)

๊ฑฐ์˜ ๋‹ค ํ–ˆ๋‹ค!

graphene_dango๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด INSTALLED_APPS์— ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ , GRAPHENE ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

cookbook/settings.py

INSTALLED_APPS = [
    ...
    "graphene_django",
]

... 

GRAPHENE = {
		"SCHEMA": "cookbook.schema.schema"
}

cookbook/urls.py

ํ›„์— ์•„๋ž˜์™€ ๊ฐ™์ด urls.py์— ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

urlpatterns = [
    path("admin/", admin.site.urls),
    path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

RESTful API์™€ ๋‹ฌ๋ฆฌ GraphQL์— ์—‘์„ธ์Šคํ•˜๋Š” url์€ ๋‹จ ํ•˜๋‚˜๋ฟ์ด๋‹ค.

์ด url์— ๋Œ€ํ•œ ์š”์ฒญ์€ Grpahene์˜ GraphQLView ์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

GraphQLView๋Š” GraphQL ์—”๋“œํฌ์ธํŠธ ์—ญํ• ์„ํ•œ๋‹ค.

์ถ”๊ฐ€๋กœ, GraphiQL์„ ์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— graphiql=True ์˜ต์…˜์„ ์ง€์ •ํ•œ๋‹ค.

๋งŒ์•ฝ settings.py์—์„œ SCHEMA๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์—ฌ๊ธฐ์—์„œ ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ๋‹ค!

from django.contrib import admin
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

from cookbook.schema import schema

urlpatterns = [
    path("admin/", admin.site.urls),
    path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True, schema=schema))),
] 

์ด์ œ ์ง„์งœ ํ…Œ์ŠคํŠธ!

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์„œ๋ฒ„๋ฅผ ์‹คํ–‰์‹œ์ผœ๋ณด์ž!

python manage.py runserver

...
... http://127.0.0.1:8000/
...

http://127.0.0.1:8000/graphql/๋กœ ๋“ค์–ด๊ฐ€๋ณด์ž!

๊ทธ๋Ÿฌ๋ฉด GraphQL query๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฐ„์ด ๋‚˜์˜จ๋‹ค.

์—ฌ๊ธฐ์—์„œ query๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ ์›ํ•˜๋Š” ๊ฐ’๋“ค์„ ๋ฝ‘์•„์˜ค๋ฉด ๋œ๋‹ค!!

query {
  allIngredients {
    id
    name
  }
}
{
  "data": {
    "allIngredients": [
      {
        "id": "1",
        "name": "Eggs"
      },
      {
        "id": "2",
        "name": "Milk"
      },
      {
        "id": "3",
        "name": "Beef"
      },
      {
        "id": "4",
        "name": "Chicken"
      }
    ]
  }
}
query {
  categoryByName(name: "Dairy") {
    id
    name
    ingredients {
      id
      name
    }
  }
}
{
  "data": {
    "categoryByName": {
      "id": "1",
      "name": "Dairy",
      "ingredients": [
        {
          "id": "1",
          "name": "Eggs"
        },
        {
          "id": "2",
          "name": "Milk"
        }
      ]
    }
  }
}
query {
  allIngredients {
    id
    name
    category {
      id
      name
    }
  }
}

๊ฒฐ๋ก ?

์ด์ฒ˜๋Ÿผ GraphQL์€ ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๋ฉฐ, Django ๋ชจ๋ธ์„ ํ†ตํ•ฉํ•˜๋ฉด ์„œ๋ฒ„๋ฅผ ๋น ๋ฅด๊ฒŒ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

django-filter ๋ฐ automatic pagination๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋ ค๋ฉด Relay Tutorial์„ ๊ณ„์† ์ง„ํ–‰ํ•ด๋ด์•ผ ํ•œ๋‹ค!

์ฐธ๊ณ  ์ž๋ฃŒ

Graphene-Python

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ