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

λŒ“κΈ€λ‚¨κΈ°κΈ°