【Django REST Framework チュートリアル】#4 ReactとAPIサーバー繋ぐ【入門】

# DjangoでAPIサーバー作成⇨React(axios)で取得して表示
`Django REST Framework`でAPIサーバーを作る初心者の入門記事として書きます。

[前回記事](https://qiita.com/Bashi50/items/ce8aaeaa52f73ba971b9)までで、ReactでJSON PlaceholderでAPIをデータを取得しました。
JSON Placeholderで取得できるのと同じデータを`Django REST Framework`を使ってAPIサーバーを作り、Reactで表示させようと思います。
![dajngo rest framework and react.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/fe72cf8e-debc-791b-3e1f-4fac06a7ea32.jpeg)


# :pushpin: React & Material UI & Django RestFramework記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[React Material UIを使ってみる](https://qiita.com/Bashi50/items/8964cc55c596e51fcbbe)|
|**part2**|[【初心者】#2 React axiosでAPI データ取得](https://qiita.com/Bashi50/items/b4e0626b2ede73568eab)|
|**part3**|[【React初心者】 #3 ルーティング・ページ遷移を作る！ react-router-dom](https://qiita.com/Bashi50/items/ce8aaeaa52f73ba971b9)|
|**part4**|**<font color="red">【Python】 Django REST Framework ReactとAPIサーバー繋ぐ</font>　← ココ**|

# JSON Placeholderデータ確認
一応どんな感じのデータか確認する場合は以下で確認してください。
`/posts`: 記事全部取得
> https://jsonplaceholder.typicode.com/posts

`/posts/:id`: 個別記事取得
> https://jsonplaceholder.typicode.com/posts/1

これと同じデータ取得できるようなAPIサーバーをDjango REST Frameworkで作ります。


# Django導入
私は今回、pipenvで作ろうと思います。
環境の準備が難しい場合は、anaconda入れるといいと思います。

[> Pipenv 環境構築方法](https://qiita.com/Bashi50/items/7eff0a2ba66cd0604447)

好きなところにディレクトリ作って開始します。

```
$ pip install pipenv
$ pipenv --python 3.8.7
```

Pythonのバージョンを指定してます。
2020年の年末Djnagoを少しいじってたら、3.9だと動かないライブラリあったので3.8系を使ってます。

<font color="Red">※ 以下`pipenv`を使用しますが、anacondaやDockerなど他の環境でやるときは`pip install`してください。</font>

```
$ pipenv install django
$ pipenv install djangorestframework
```

# 仮想環境pipenvで作業
`pipfile`が置いてあるところで、仮想環境の中に入ります。
普段、作業を開始するときはここから開始。

```
$ pipenv shell
```

一応確認。

```
$ python -V
Python 3.8.7
```

# djangoのプロジェクトとアプリを作成

backendというプロジェクトを作成。
最後の`.`はカレントディレクトリにプロジェクト作成すると言う意味。
アプリの名前は`api`という名前にします。

プロジェクト名も、アプリ名も好きに決めてください。

```
$ django-admin startproject backend .
$ django-admin startapp api
```

## サーバー起動
manage.pyがあるところで、以下のコマンド実行

```
$ python manage.py runserver
```

デフォルトでは以下でサーバーが起動します。

> http://127.0.0.1:8000/

ポート変更したかったら、
`python manage.py runserver 7000`

ブラウザでURLにアクセスして、ロケットが飛んだらOK

![スクリーンショット 2021-01-16 20.48.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/ce40ad2d-a52a-8bef-449f-93475c5ac339.png)


# 設定変更
日本語表示と、タイムゾーンを変更します。

```settings.py
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'
```

```settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'api.apps.ApiConfig',
]
```

あとは、シークレットキーなど隠す必要ある方は、一番最後の
`補足： python-dotenvで環境変数を設定`を見て設定してみてください。

# ルーティング urls.py
アプリごとにurls.pyを作って管理した方が良いのでurls.pyを作ります。

```
$ touch api/urls.py
```

↓まだ記述が足りないです。後で付け足します。


```api/urls.py
from django.urls import path, include
from rest_framework import routers

router = routers.DefaultRouter()

urlpatterns = [
    path('', include(router.urls)),
]
```


includeを足してアプリ(api)側に作ったurls.pyのルーティングを反映させる。

```backend/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
]
```


# model作成
今回とりあえず必要なのは、
`id, title, body`です。
idはデフォルトで作られるので、`title`, `body`を作ります。

```api/models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=50, blank=False)
    body = models.CharField(max_length=4000, blank=False)

    def __str__(self):
        return self.title
```
max_lengthは最大何文字にするかです。テキトウに決めました。

`def __str__(self)`に`self.title`と書くことで、
あとで、どんなデータか、タイトルのデータ表示で見やすくなるので設定しておいた方がいいと思います。
必須ではないです。


モデルにDBの構造を書いたので、`makemigrations`でマイグレーションファイルを作成して、
マイグレーションファイルの記述通りにDBを作るため、`migrate`します。

```
$ python manage.py makemigrations
$ python manage.py migrate
```

これで、DBにテーブルが作成されました。

# シリアライザーを作る
データの加工とか、形式正しいかとかの処理をするのがシリアライザーです。
とりあえず、やってみないとピンとこないと思うので作ってみましょう。

```
$ touch api/serializers.py
```

```django-react-material/api/serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'body']
```

使用するモデルがPostなので`model = Post`
`id, title, body`をReactに渡すので、
`fields = ['id', 'title', 'body']`

例えば、Reactで`id, title`だけでいいなら
`fields = ['id', 'title']`になります。
必要なデータだけ選択するようにします。


# views.py設定
DjangoはMVC(Model View Controller)モデルでなくMVT（Model View Template）です。

DB関係を扱う「Model」、
HTMLっぽい記述をする「Template」
「Views」は、その二つの間でデータを処理・加工など両者の受け渡しというか、、、
そんな感じです。

ViewsがMVCのControllerに当たるもので、なんか紛らわしいですね。
とりあえず、ModelViewsetというのを使うと、CRUD(Create Read Update Delete)を少ない記述で簡単に作れますので使ってみます。
（記述量増えるけど、細かく処理させたいこと決めたいならAPIviewとか使う）

とりあえず、Postテーブルに登録されているデータを全部JSONとして返すコードを書きます。
↓

```django-react-material/api/views.py
from django.shortcuts import render
from rest_framework import viewsets
from django.http import HttpResponse
from .serializers import PostSerializer
from .models import Post


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
```


# admin管理画面に入る

情報を取得しようとしても、今は、DBに何もデータがありません。
これでは、データが取得できたかわからないですね。

ということで、管理画面を設定します。

## Djangoの管理画面を作る

Djangoの管理画面は最初から付属しています。
簡単にデータを追加・修正したり、ユーザーを追加したりすることができます。

```django-react-material/api/admin.py
from django.contrib import admin

from .models import Post

admin.site.register(Post)
```

作成したmodelをインポートして登録しましょう。
これで管理画面で確認、追加、編集、。削除などできます。

## 管理画面に入る

![スクリーンショット 2021-01-17 1.04.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/cfe91a88-3680-df65-50ed-79e9d15c1597.png)

ユーザーを作っていないと入れないですね。
コマンドでスーパーユーザーを作成します。

```
$ python manage.py createsuperuser  
```

ユーザー名、メール、パスワードを設定してユーザーを作成します。
テストですので、簡単なパスワードでもいいと思います。

`Bypass password validation and create user anyway? [y/N]:`
みたいにパスワード単純すぎるよ！みたいなこと言われるけど、無視して`y`入れていいです。
<font color="red">※ デプロイするときは、単純なパスワードは避ける</font>

作成したユーザーでログインしてください。


モデルの名前である、
**「POST」**をクリック ⇨ 右上の**「Postを追加」**

押して、データを追加します。
そうしたら、以下のような登録フォーム出るのでデータを追加します。

![スクリーンショット 2021-01-17 1.14.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/45cb1443-9a75-7791-1fb2-c907052c8b47.png)

データを何個か追加してみてください。
DBにデータが追加されます。


# ルーターに追加
最後に、ルートを追加します。

```djnago-react-material/django-react-material/api/urls.py
from django.urls import path, include
from rest_framework import routers
from api.views import PostViewSet

router = routers.DefaultRouter()
router.register('posts', PostViewSet)

urlpatterns = [
    path('', include(router.urls)),
]
```
`router.register('posts', PostViewSet)` postsというURLに割り当てるのは、
views.pyに書いた、`PostViewSet`の処理という意味です。


紛らわしいですが、元からあったurls.pyには
`path('api/', include('api.urls')),`と書いたので、
`api/`というURL以降にapiフォルダの方にある、urls.pyの記述を足します。

ということで、`api/` + `posts`なので

> http://127.0.0.1:8000/api/posts/

にアクセスするとAPIサーバーからデータを取れることになります。

## 処理の流れ

- APIを叩かれる。URLが`http://127.0.0.1:8000/api/posts/`
- views.pyに書いてある`PostViewSet`の処理が実行
- `queryset = Post.objects.all()`でPostテーブルからデータを全て取得
  - SQLなら `SELECT * FROM POST`みたいな感じ。正確にはDBのテーブル名違うけど
- シリアライザーでデータを整える。
  - 今回は、単純な処理なので、処理はほぼしていない。
  - いらないカラムデータを削除(fieldsで指定したりする)、データを加工してJSONにするとかできる
  - 例えば、2021-01-17 12:00:00が見にくいから、2021年1月１７日にしてJSONデータ作るとか
- JSON形式でDjangoのAPIサーバーがデータを送信


少し長くなりましたが、これでできました。
URLにアクセスすると、

> http://127.0.0.1:8000/api/posts

以下のように先ほど管理画面で登録したデータを取得できているのがわかります。

![スクリーンショット 2021-01-17 1.48.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f46e611b-928b-1110-2c34-32c597f3ed1f.png)


# Postmanで取得してみる
インストールしてない人はここから

> https://www.postman.com/downloads/

APIを叩く（APIサーバーからデータもらう）ときによく使われるツールです。
先ほど確認できたので、大丈夫ですが、一応体験しましょう。

今回はGetで取得しますね。

手順
-

<font color="red">**create request ⇨ URL入れる ⇨ Send**</font>

![スクリーンショット 2021-01-17 1.59.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/0619b5e2-1b01-3d39-0be3-e34d36e2c544.png)

認証のトークンをつけてAPIを叩いたり、
POSTデータを送信することもできるなどなど便利ツールです。
（今回はあまり使う意味なかったけど、WEBアプリ開発するなら入れておきましょう。使います。）


# これだけだと、Reactで読み込めない
一見良さそうなんですけど、セキュリティとかの関係で今のままだと、
ReactではAPIを叩いてもデータを取得できません。
必要なものをインストールして、設定をします。


```
$ pipenv install django-cors-headers
```

設定が必要なので、以下を追加します。

```settings.py
INSTALLED_APPS = [
    'corsheaders', # 追加
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # 追加
]

CORS_ALLOWED_ORIGINS = [
    'http://127.0.0.1:3000',
    'http://localhost:3000',
]
```

`CORS_ALLOWED_ORIGINS`は通信を許可するIPとポートを指定する設定です。
Reactのデフォルトで起動するのは`http://localhost:3000`ですのでこのように書いてます。

localhostのIPの設定は意図的に変更しない限り、`127.0.0.1`なので一応書いてます。

※ `CORS_ORIGIN_WHITELIST`で指定するという情報がありました。
最近のバージョンでは`CORS_ALLOWED_ORIGINS`ですので注意。

一応ちゃんと確認したい方は、リポジトリを見てください
> https://github.com/adamchainz/django-cors-headers

Djangoはこれで完成です！🎉

# ReactからDjangoのAPIサーバーにアクセス
※ こちらは[前回作ったReact](https://qiita.com/Bashi50/items/ce8aaeaa52f73ba971b9)の続きです。

こちらはそれほどやることはなくて、URLを変更すれば良いだけです。

```material-react/src/components/Content.js
function Content() {
    const [post, setPosts] = useState([])

    useEffect(() => {
        // axios.get('https://jsonplaceholder.typicode.com/posts')
        axios.get('http://127.0.0.1:8000/api/posts/')
        .then(res => {
            setPosts(res.data)
        })
    }, [])
    const getCardContent = getObj => {
        const bodyCardContent = {...getObj, ...cardContent};
        return (
            <Grid item xs={12} sm={4} key={getObj.id}>
                <BodyCard {...bodyCardContent} />
            </Grid>
        );
    };
    return (
        <Grid container spacing={2}>
            {post.map(contentObj => getCardContent(contentObj))}
        </Grid>
    )
}
```


`BodyCard.js`で`const { userId, id, title, body, avatarUrl, imageUrl } = props;`と書いてあって、ここにデータが渡ります。

userIdは今回作っていませんが、もともと使っていなかったので表示には問題ないです。
（※ 消してもよかったですね…）


## Reactサーバー起動
Reactを開発したディレクトリにて、サーバーを起動して、

```
$ npm start
```
URLにアクセスすると、

> http://127.0.0.1:3000

うまくいけば、表示されます。

![スクリーンショット 2021-01-17 2.32.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/d6cc4243-98cf-9d5a-ce5d-e78f3311ae80.png)

Djangoの管理画面でデータを登録したら増えますので試してください。


# APIで個別記事のデータ取得
次は、「詳細をみる」ボタンで表示される個別の詳細記事です。
実は、ModelViewSetを使うと一通りの機能を作ってくれているので、すでに完成しています。

> http://localhost:8000/api/posts/1

とブラウザのURLのところに入れれば、データが表示されます。
![スクリーンショット 2021-01-18 0.17.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f3794353-5890-1425-68cb-c04cf1606e49.png)


ついでに、コード書いてないですが、削除、変更もすでに実装されています。
DELETE、PUTのボタンありますよね。この画面のボタンを押しても、編集できますし、

Postmanで、HTTPの通信を使って、
消したり、変更したりを、所定の形式に従って送ってやれば実行されるAPIが完成してるんですね。


まあ、今のうちは、`ModelViewSet`使うと簡単にCRUD作れるんだねー
くらいで良いと思います。

## Reactからこの個別記事詳細データを取得

前回の
[【初心者】React #3 ルーティング・ページ遷移 react-router-dom]
ですでにボタンから個別の記事に飛べるようにもなっていますので
API叩くためのURLを変えるだけでOK

```material-react/src/components/PostContent.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom'

import { Grid } from '@material-ui/core'
import BodyCard from './BodyCard'


const cardContent = 
    {
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    }

function PostContent() {
    const { id } = useParams();
    const [post, setPosts] = useState([])

    useEffect(() => {
        // axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
        axios.get(`http://127.0.0.1:8000/api/posts/${id}`)
        .then(res => {
            setPosts(res.data)
        })
    }, [])

    return (
        <Grid container spacing={2}>
            <Grid item xs={12} key={post.id}>
                <BodyCard {...{...post, ...cardContent}} />
            </Grid>
        </Grid>
    )
}

export default PostContent
```

URLを変えるだけで詳細ページはOKです。

![スクリーンショット 2021-01-18 2.29.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/b1a31b48-af29-d925-e70d-3d172266076c.png)

一覧表示の`BodyCardコンポーネント`をそのまま使ったので
「詳細をみる」ボタンはここには必要ないし、画面の引き伸ばし感がひどいですが、
個別記事のルーティングもできていますね

# まとめ
- Django Rest Frameworkを使ってAPIサーバーを作成
- Reactから作成したAPIサーバーにアクセスできるようにDjangoで設定
- Reactで作成したAPIサーバーからデータが取得できることを確認


# 補足： python-dotenvで環境変数を設定
必須ではないです。必要な人はやってみましょう。

githubなどにpublicで公開するとき、APIキーなど公開すると危険な情報はを.envに書き、`.gitignore`で除外しましょう


やり方よく忘れるので、メモしときます。

デプロイするものなどは、githubのpublicにしないか、
公開したらやばいものは公開しないようにしてください。

環境変数を`.env`から読めるようにします。

```
$ pipenv install python-dotenv
```

プロジェクト直下、manage.pyとかと同じところに.envファイル作って、ここにみられたえらやばい情報を追加します。

```
$ vim .env
```

SECRET_KEYと、DEBUGも一応追加しておきます。

`setting.py`に書いてある、SECRET_KEYと、
ついでにDEBUGの値を以下のように貼り付け。

```:.env
SECRET_KEY=1234567890asdfghjklwertyzx
DEBUG=True
```

.envから読み込む記述と、
`SECRET_KEY`, `DEBUG`を`os.getenv('ここに.envに書いた名前書く')`にする。

```backend/settings.py
import os
from dotenv import load_dotenv

load_dotenv(verbose=True)
dotenv_path = os.path.join(BASE_DIR, ".env")
load_dotenv(dotenv_path)

SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG')
```

.gitignore作ってなかったら、.envと同じプロジェクト直下に作成して以下の記述を足してください。

ついでに.githubのリポジトリ作るときに、.gitignoreを追加⇨python選べば、
こんな感じの記述はすでに書かれていると思うのでその場合は不要です。

```:.gitignore
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
```

.gitignoreに追加すれば、変更を追跡しなくなるので、
うっかり後悔すると問題になる情報をgithubに公開してしまった！
ということはなくなります。


今回はDjangoのSECRET_KEYで、テストアプリなので公開しても特に問題はないと思います。
ただAPIキー、特にAWS関係の情報を晒したら、悪用されて多額の請求来ます！
隠したいファイルはあるけど、プロジェクトはgithubに公開しないといけないときには使いましょう。

# :pushpin: React & Material UI & Django RestFramework記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[React Material UIを使ってみる](https://qiita.com/Bashi50/items/8964cc55c596e51fcbbe)|
|**part2**|[【初心者】#2 React axiosでAPI データ取得](https://qiita.com/Bashi50/items/b4e0626b2ede73568eab)|
|**part3**|[【React初心者】 #3 ルーティング・ページ遷移を作る！ react-router-dom](https://qiita.com/Bashi50/items/ce8aaeaa52f73ba971b9)|
|**part4**|**<font color="red">【Python】 Django REST Framework ReactとAPIサーバー繋ぐ</font>　← ココ**|