Django Rest Frameworkの便利さを理解する

#はじめに
この記事は[前回](https://qiita.com/Arata0608/items/f292dedffc8c0db1df28)の続きです。前回の記事はAPIの概念的な部分を説明しています。今回は実際にPython, Djangoを用いてAPIを構築していきたいと思います。今回作るAPIはデータを（ブラウザ上から）送るとデータベースに保存し、任意のエンドポイントに移動するとデータベース内のデータを表示するというシンプルなものとしました。また**Django Rest Framework**を使わずにAPIを構築します。理由としては、あえてDjangoの用意するフレームワークを使わずに**Pythonコード**で構築することで、Django Rest frameworkの利便性が理解できると思った為です。

#対象者
Pythonの基本的な文法は知っている方、Djangoの基本的な仕組みを知っている方。（Djangoのインストール、導入部分はかなり省いています。）

#初期設定
APIを実際に作っていく上でまず重要になるのは、**どのようなデータを扱うのか**の部分です。APIに向けてデータを送ればデータベースに勝手に保存されるという訳ではなく、APIは**models.py**で設計されたデータのモデルを元にデータを保存します。なのでまずは**どのようなデータを扱うのか**の部分をモデルに設定します。

```python:models.py
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    description = models.TextField()
    timestamp = models.DateField(auto_now_add=True)

    def __str__(self):
        return self.title
```
今回はブログのデータをAPIで扱うことを仮定したモデルを設計しました。タイトルがあり、記事の内容、投稿日時があるデータです。モデルが書けたら**migrate**までやっておきましょう。次に**admin.py**に**Post**を登録し、adminページからテストデータを幾つか入れます。下の図のように適当な値を項目ごとに入れて保存します。
![スクリーンショット 2020-02-06 13.08.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/1c796ad5-d735-9c8f-8e5e-95c4343e11c7.png)


#任意のエンドポイントでデータを表示させる
それではadminページから保存したデータを任意のエンドポイント上に表示させてみしょう。まず思い出す必要があるのが、最近のAPIはリクエストを送ると**JSONデータ**が返ってくることです。なので**views.py**のposts関数の返り値は**JsonResponse**になっています。それ以外はシンプルなコードで**QuerySet**をJSONデータに近付けています。QuerySetをfor文で回して辞書型にして、それをリストに入れ込んでいきます。ここからはDjangoが自動でやってくれる仕事で、Pythonのリストに入った辞書型のデータはJSONデータに自動で**シリアル化**してくれます。

```python:views.py
from .models import Post

def posts(request):

    posts = Post.objects.all()
    post_list = []
    for post in posts:
        post_dict = {
            'pk': post.pk,
            'title': post.title,
            'description': post.description,
            'timestamp': post.timestamp,
        }
        post_list.append(post_dict)

    return JsonResponse(post_list, safe=False)

    # non-dict型のデータをjsonデータに変換するには"safe=False"を記述する。
```
任意のエンドポイントを作成。

```python:urls.py
from application_name.views import posts #追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/post/', posts), #追加
]
```
ここまで来たら、一回ローカルサーバーを立てて作成したエンドポイントに移動してみましょう。今回はプライマルキーを入れる必要はありませんでしたが、このようにテスト時に確認する際は便利ですね。ちゃんとデータの確認ができたので成功ですね！次はPOSTリクエストについてです。
![スクリーンショット 2020-02-06 13.49.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/15de267d-4f60-e440-04a9-19821529c6d3.png)


#POSTリクエストに対する処理を書く
Djangoを用いたAPIの役割として、models.pyに基づいた新しいデータが**POSTリクエスト**を通して送られてきた場合、そのデータをデータベースに保存するというものがあります。少し書いていても分かりづらいので簡潔に説明しますと、API側の役割は送られてきたデータを保存しやすい形に**加工して**、**保存する**ことです。そこで次は加工して、保存するコードを書いていきます。

```python:views.py
#以下のコードを追加します。if文以下はさっきのGETリクエストの処理の上に書きます。
from django.http import HttpResponse
import json

def posts(request):

    if request.method == 'POST':
        data = json.loads(request.body)
        post = Post(
            title=data['title'],
            description=data['description'],
            )
        post.save()
        return HttpResponse(201)

    posts = Post.objects.all()
    # ~~ 以下略 ~~
```
**Django**は送られてきたJSONデータに対して自動的に**デシリアライズ**（JSONデータを扱いやすい形に戻すこと）する機能は標準で備わっていないので、デシリアライズするコードを書いておく必要があります。Pythonで扱えるデータ形式に戻さなければデータベースに保存できないので、この機能は必要です。JSONデータを適当な変数に入れ、この変数を用いてデシリアライズ（加工）します。また**Django**はviewsから自動的にデータを保存する機能がありませんので最後に**post.save**と明示的に書きます。

#実際にPOSTリクエストを送ってみる
今回は**axios**を用いてAPIに対して**POSTリクエスト**を送ってみたいと思います。**ajax**を自身のアプリケーションにおいて実現させたい場合はJavascriptの**axiosライブラリ**を使うのがオススメです。まずはPOSTリクエストを送るページをtemplatesに作成します。そのページに**cdn**を用いてaxiosを追加していきます。

```html:main.html

    <body>
        <p>Test Page</p>   
    </body>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script> #cdnで追加
</html>
```
viewに**function based**で作成。ルーティングも行っておく。

```python:views.py
def main_page(request):
    return render(request, 'main.html', context=None)
```

```python:urls.py
from application_name.views import main_page #追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', main_page),    #ルートを追加する
    path('api/post/', posts),
]
```
ローカルサーバーを立てて**main.html**が表示されるURLに移動します。そこでデベロッパーツールの**コンソール**からAPIがちゃんと動くかどうか確かめてみましょう。POSTリクエストで送信するデータは**JSONデータ**である必要があるため、辞書型にする。ただ引数として分かりやすくするために、変数にしておきます。

```javascript:console
/* 
    async関数を作る
    requestを送り、responseを作る
    requestを送るには時間がかかるためawaitさせたい
    サーバーが既に立っているのでAPIのエンドポイントは相対パスで良い
*/

async function addPost(title, description) {
    const response = await axios.post('api/post/', {'title': title, 'description': description})
    console.log(response)
}

// インスタンス化
addPost('sending from axios', 'some test messages')
```
ただこれを実行しても**403 Forbidden**のエラー文が帰ってくるはずです。このように403が帰ってくる時は、ブラウザのデベロッパーツールの**Network**で自分が送った全ての**リクエスト**を確認することができます。下の画像の通り、送ろうとしていたペイロードの部分（データ）は確認できます。
![スクリーンショット 2020-02-05 17.09.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/20efd622-0a48-77f1-2390-6e081a4986b1.png)
次に**Response**部分を見てみるとよく見る**html文**が並んでいます。分かりやすくブラウザでこのレスポンスを確認するために、**レスポンスのhtml文**をコピーして、**innerHTML**で書き換えてみましょう。少し言葉では説明しづらいので下のコードを見てください。

```javascript:console
document.querySelector('body').innerHTML = `レスポンスのhtml文`
```
これで実行するとDjango側からエラーが来ていると分かるはずです。一応エラー画面を下に貼っておきます。エラー文を見てみると**CSRF verification**が失敗していると分かります。Djangoでformを使っているなら**csrf_token**を使えと書いてありますが、今回はajaxを用いてデータを送信しているので、この方法では解決できません。Djangoにおいてajaxを行うには普段使わない方法を用います。
![スクリーンショット 2020-02-05 17.56.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/121da25c-1bfc-9217-0f30-63df36da5f64.png)
そもそも**CSRF verification**の流れを説明しますと、フロント側が他の場所からデータにアクセスしているのではなく、同じサイトからアクセスしていることをDjango側に示し、それを受け取ったDjangoはフロント側にデータのアクセス許可を出します。これを踏まえてもう一度**403 Forbidden**について考えると、『あなたはデータを扱う権限（許可）がありません』ということを表していると分かります。
ただ先ほど述べたようにformは使わないので**csrf_token**は使えません。なので今回はcsrf_tokenを**Cookie**に持たせてjavascriptのfileに貼り付けます。

```javascript:sample.js
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
```
本来は上のコードを、ajaxリクエストを送る**jsファイル**の一番上に貼り付けることで、axiosを用いたリクエストにデフォルトでcsrf_tokenを持った**Cookie**を持たせることができます。これにより先ほどのエラーを解決できるはずです。
今回Javascriptを用いてajax通信以外の機能は持たせませんので、任意の名前のjsファイルを作成し、上のコードのみ書いておきます。このファイルを下にあるように**main.html**で読み込み（繋ぎこみ）、デベロッパーツールのコンソールでも動くようにします。

```html:main.html

    <body>
        <p>Test Page</p>   
    </body>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script type="text/javascript" src="{% static 'sample.js' %}"></script> #追加
</html>
```

先ほどのようにコンソールで引数に値を入れてインスタンス化すると、今度はエラーは出ずにステータスコード201が帰ってきています。つまり成功です！
![スクリーンショット 2020-02-05 21.49.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/e20721d6-0092-fa51-f18f-5596b0c93598.png)
一応**'api/post/'**のエンドポイントに移動して、ちゃんとPOSTしたデータが表示されているか確認してみます。
![スクリーンショット 2020-02-05 23.22.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513383/5cc072d4-07d3-6ebf-66db-a3ed9af3992b.png)プライマリーキーが3のデータが先ほど追加したデータなので、ちゃんとGET, POSTリクエストが動いていることが分かります。

#Django Rest Frameworkを使う理由
まず**Django Rest Framework**（DRF）を使うとデータの加工や今回は扱っていない認証に関するコードを大幅に少なくしてくれるという理由が一番かと思います。ただ次に挙げられる理由として私は、PUT、PATCH、DELETEなどのHTTP Verbが使えるようになることだと思います。通常、**Django**はこれらを無視するので使いたいと思っても使えません。**DRF**は他のリクエストも簡単に扱ってくれるので、APIとしての機能を最大限活かせるようになります。他にも沢山理由があると思いますが、とりあえずこの二つが大きな理由と考えます。

#最後に
記事の内容はいかがでしたでしょうか。自分の備忘録としての役割もあり、かなり長いものになってしまったので最後まで読んでくれた方はありがとうございました。間違い、誤字などございましたら、お気軽に指摘していただけると助かります。次回は外部のAPIを用いて簡易的なアプリケーションを作成していきたいと思ってます。