Django REST Framework「実践入門」Part２

# はじめに
今回は、Next.jsとDRFを用いて簡単なAPIを作成します。

DRFの基本的な使い方と、CORSについて説明します。

Next.jsについての説明は省きます。

# 制作物概要
イベントを追加できるカレンダーアプリを作ります。
カレンダーには、FullCalendarを使用します。

https://fullcalendar.io/

# 環境構築
## DRFの準備
仮想環境に入る
```
$ python3 -m venv venv
$ source venv/bin/activate
```
必要なものをinstall
```
$ pip install django
$ pip install djangorestframework
```
プロジェクトとアプリケーションを作成
```
$ django-admin startproject config .
$ python3 manage.py startapp model # モデル用
$ python3 manage.py startapp api # Api用
```
初期設定
```config/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # 追加
    'api.apps.ApiConfig', # 追加
    'model.apps.ModelConfig', # 追加
]

LANGUAGE_CODE = 'ja' # 修正

TIME_ZONE = 'Asia/Tokyo' # 修正

```
## Next.jsの準備
Next.jsの構築
```
$ npx create-next-app calendar
$ npm run dev　# サーバが立ち上がる
```
TypeScriptなどは追加していません
fullcalendar install
```
$ npm install --save \
  @fullcalendar/core \
  @fullcalendar/react \
  @fullcalendar/daygrid \
  @fullcalendar/interaction
```
fullcalendarの基本的なコード

https://fullcalendar.io/docs/react

これを参考にして下さい。

```page.js
"use client"
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin from "@fullcalendar/interaction"

export default function Home() {
  const handleDateClick = (arg) => {
    alert(arg.dateStr)
  }
  return (
    <FullCalendar
      plugins={[dayGridPlugin, interactionPlugin]}
      initialView="dayGridMonth"
      locale={"ja"}
      weekends={false}
      events={[
        { title: 'event 1', date: '2019-04-01' },
        { title: 'event 2', date: '2019-04-02' }
      ]}
      dateClick={handleDateClick}
    />
  );
}

```
# DRF
今回は使用するDRFの機能は、大きく４つに分けられます。
- models.py （モデル）
- serializer.py （シリアライザ）
- views.py （ビュー）
- urls.py （URL）
## models.py　（モデル）
DRFには「ORM」という機能が備わっています。
「ORM」とはオブジェクトとリレーショナルデータベースをうまく対応付けてくれる機能です。
Djangoではmodel.pyにclassを作り、マイグレーションすることでデータベースが自動的に作成されます。
また、このおかげでSQL文を書く必要なく直感的な操作が可能となります。
```model/models.py
import uuid
from django.db import models

class Event(models.Model):
    class Meta:
        db_table = 'event'
        
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(verbose_name='タイトル', max_length=20)
    date = models.DateField(verbose_name='開催日')
    created_at = models.DateField(verbose_name='投稿日', auto_now_add=True)
    
    def __str__(self):
        return self.title
```
マイグレーション
```
$ python3 manage.py makemigrations
$ python3 manage.py migrate
```
makemigrationで設計図を作成して
migrateで実際にテーブルを作成するイメージです
管理画面からモデルを登録しましょう
```model/admin.py
from django.contrib import admin
from .models import Event

admin.site.register(Event)
```
これにより、管理者画面でモデルの管理ができます。
```
$ python3 manage.py createsuperuser
```
で管理者を作成してサーバを起動して(http://127.0.0.1:8000/admin/)にアクセスしましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887061/252790bf-51eb-6936-820b-5d8a4df8ef84.png)
モデル作成がうまくいったと分かりました。
実際に何個か適当なイベントを追加しておいてください。
## serializer.py （シリアライザ）
次にシリアライザについて説明します。これはDRF固有のもので、JSON文字列と、オブジェクトの相互変換をしてくれる機能になります。
今回は、自動で変換やバリデーションを行ってくれるModelserializerについてのみ説明します。
詳しく知りたい場合は以下を参照。

https://www.django-rest-framework.org/tutorial/1-serialization/

```api/serializer.py
from rest_framework import serializers
from model.models import Event

class EventSerializer(serializers.ModelSerializer):
    class Meta:
        model = Event
        fields = ['id', 'title', 'date']
```
このように、ModelSerializerではほとんどを自動で行ってくれます。

## views.py （ビュー）
これも、Djangoとあまり機能は変わりません。
```api/views.py
from rest_framework import viewsets

from model.models import Event
from .serializers import EventSerializer

class EventViewSet(viewsets.ModelViewSet):
    queryset = Event.objects.all()
    serializer_class = EventSerializer
```
ModelViewSetを使うと、Modelに関連したCRUDを自動で実装してくれます。
## urls.py （URL）
これもDjangoとほとんど変わりません。
```config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
]
```
```api/urls.py
from django.urls import path, include
from rest_framework import routers

from . import views

router = routers.DefaultRouter()
router.register('event', views.EventViewSet)

app_name = 'api'
urlpatterns = [
    path('', include(router.urls)),
]
```
## apiの確認
http://127.0.0.1:8000/api/event/
にアクセスするとapiを作成できたことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887061/c57e2875-06c4-23e9-81b6-040dd032fd9b.png)
これで、DRFの作成は終了です。

# Next.js
コードのみで説明は省略します。
```page.js
"use client"
import React, { useState, useEffect } from 'react'
import AddEvent from './components/addEvent'
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin from "@fullcalendar/interaction"

export default function Home() {
  const [events, setEvents] = useState([])
  const [showForm, setShowForm] = useState(false)
  const [selectedDate, setSelectedDate] = useState(null)

  useEffect(() => {
    const fetchEvents = async () => {
      try {
        const response = await fetch("http://127.0.0.1:8000/api/event/")
        const json = await response.json()
        setEvents(json.map((item) => ({
          title: item.title,
          date: item.date,
        })))
      } catch (error) {
        alert('データが取得できませんでした')
      }
    }
    fetchEvents()
  }, [])

  const handleDateClick = (arg) => {
    setSelectedDate(arg.dateStr)
    setShowForm(true)
  }

  return (
    <div>
      <FullCalendar
        plugins={[dayGridPlugin, interactionPlugin]}
        dateClick={handleDateClick}
        initialView="dayGridMonth"
        weekends={false}
        locale="ja"
        events={events}
      />
      {showForm && <AddEvent setShowForm={setShowForm} selectedDate={selectedDate} />}
    </div>
  )
}

```
```components/addEvent.js
"use client"
import React, { useState, useEffect } from 'react'

export default function AddEvent({setShowForm, selectedDate}) {
    const [title, setTitle] = useState('')
    // const [user, setUser] = useState()
    const handleSubmit = async() => {
        try{
            const response = await fetch("http://127.0.0.1:8000/api/event/",{
                method: "POST",
                headers: {
                    "Accept": "application/json",
                    "Content-Type": "application/json"
                },
                body: JSON.stringify({
                    title: title,
                    date: selectedDate,
                })
            })
            setShowForm(false)
        }catch{
            alert("登録し直してください")
        }
    }

    const handleCancelClick = () => {
        setShowForm(false)
    }

    return (
        <div>
            <form onSubmit={handleSubmit}>
                <input value={title} onChange={(e) => {
                    setTitle(e.target.value)
                }} type="text" name="title" placeholder="イベント名" required />
                <button>イベント追加</button>
            </form>
            <button onClick={handleCancelClick}>キャンセル</button>
        </div>
    )
}
```
ここまでで、プログラムは完成です。実際にサーバーを起動して操作を実行してみてください。
コンソールにcorsエラーが出ると思います。
# CORS(Cross-Origin Resource Sharing)
ブラウザにはサンドボックスという考え方があり、同一オリジンポリシーという制約があります。
この制約により、異なるオリジン（ホスト・スキーム・ポート番号）間からのアクセスが規制されます。
しかし、この制限を超えたデータのやり取りのニーズに対応するために「CORS」が策定されました。
「CORS」ではheaderに情報を追加したりなどの作業が必要となりますが、Djangoでは、**django-cors-headers**を使用すると簡単に実装できます。
```
$ pip install django-cors-headers
```
を実行してから
setting.pyに以下を追加してください
```config/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'api.apps.ApiConfig',
    'model.apps.ModelConfig',
    'corsheaders', # 追加
]
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware', # 追加
]
CORS_ALLOW_ALL_ORIGINS = False # 追加
CORS_ALLOWED_ORIGINS = [
    'http://localhost:3000',
] # 追加

```
これですべての作業が終了しました。
実際にサーバを起動してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3887061/041b47ef-94d1-338c-8250-1bc90271b601.png)

このように、セルを選択すると入力欄が出てきてイベントを追加できることが分かります。

# さいごに
DRFを使うと簡単にAPIを作成できることが分かりました。
認証などについても便利な機能がたくさんあるのでぜひDRFについて勉強してみてください。