Django Rest Framework で RESTful な APIを作成する

# 概要

　この記事は初心者の自分がRESTful なAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっています。

　前回の[SwiftのTableViewCellを使ってTableViewを自由にカスタマイズ](https://qiita.com/Ajyarimochi/items/0154030bde239d703806)で、データベースで管理するクーポン情報をAPIを通してアプリが取得し、リスト形式で表示するところまで作成しました。

　次はこのAPIをRESTfulなAPIに改造します。改造にあたりコードを大幅に変更するため、一旦GETで全てのクーポン情報をレスポンスするだけのAPIを実装し、その後にリクエストに応じたクーポン情報をレスポンスするよう修正を加えていきます。

### 参考
* [Django REST Framework の使い方メモ](https://qiita.com/__init__/items/f5a5a64a05541fcda713)
* [Django REST Frameworkを使って爆速でAPIを実装する](https://qiita.com/kimihiro_n/items/86e0a9e619720e57ecd8)
* [【Django入門】REST FrameworkでAPIを作ってみよう](https://www.sejuku.net/blog/29189)
* [適当に作る python Djnago REST frameworkで作る初めてのAPI](https://qiita.com/MuuKojima/items/3ea452fd69756db9e815)
* [よく使うcurlコマンドのオプション](https://qiita.com/ryuichi1208/items/e4e1b27ff7d54a66dcd9)

### 環境
Mac OS 10.15
VSCode 1.39.2
pipenv 2018.11.26
Python 3.7.4
Django 2.2.6


# 手順

* Django Rest Framework をインストールする
* Django Rest Framework を取り込む
* Serializerを定義する
* View.pyを改造する
* URL_patternを定義する
* curl コマンドを使って動作確認をする

### Django Rest Framework をインストールする

自分はpipenvでpythonのプロジェクト（仮想環境）を作っているので、そこに django rest framework をインストールします。pipenvのシェルに入り、インストールのコマンドを実行します。

```
$ pipenv shell  #シェルに入る
$ pipenv install djangorestframework # インストール実行
```

インストール中のターミナル...
<img width="441" alt="install-django-rest-framework.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/63a9ea01-563d-d5cd-4472-fb98bb5e3459.png">


インストール完了後にPipfileの中を見ると、`[packages]`に`djangorestframework`が追加されています。

```

[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
django = "*"
djangorestframework = "*"

[requires]
python_version = "3.7"
```

### Django Rest Framework を取り込む

プロジェクト名のディレクトリ配下の`setting.py` の `INSTALLED_APPS = {` に インストールした `rest_framework` を追加するだけです。

```python

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'coupon',
    'rest_framework', # 追加
]
```

これで Django Rest Framework を使う準備が完了しました。


### Serializerを定義する

　Rest Framework を使うには Serializer というモジュールが必要です。アプリのディレクトリ配下に自分で Serializer のファイルを作り実装します。

下記のように実装しました。

```coupon/serializer.py

from rest_framework import serializers # Django Rest Frameworkをインポート
from .models import Coupon # models.py のcouponクラスをインポート

class CouponSerializer(serializers.ModelSerializer):
    class Meta:
        model = Coupon # 扱う対象のモデル名を設定する
        fields = '__all__'
```

上のコードで、`fields`はレスポンスしたいモデルフィールド（ここではクーポン情報）を指定します。特に指定せず全ての項目をレスポンスする場合は`'__all__'`とします。


### View.pyを改造する

`views.py`を Django Rest Framework を使った場合に合わせて改造します。

* rest_framework の viewsets と　filters をインポート
* 上で定義した serializer の　CouponSerializer をインポート
* 全てのクーポン情報をレスポンスする queryset に変更
* rest_framework を使うと データをjson 形式にしたり dump したり、HTTPでレスポンスする処理は自動でやってくれるようなので、それらの不要になった処理を削除
* 上で定義した serializer.pyのCouponSerializer を呼ぶ。

コードは下記のようになります。非常にシンプルになりました。

```views.py
from django.shortcuts import render
from .models import Coupon
from rest_framework import viewsets, filters
from .serializer import CouponSerializer


class CouponViewSet(viewsets.ModelViewSet):
    queryset = Coupon.objects.all() # 全てのデータを取得
    serializer_class = CouponSerializer
```


### URL_patternを定義する

　`ami_coupon_api/urls.py`と、`coupon/urls.py`を編集します。実感としては`coupon/urls.py`から編集した方が良さそうです。

`coupon/urls.py`の編集内容は下記の通りです。

* rest_framework の routers をインポート
* views.py で定義した、CouponViewSet をインポート
* router を定義して、`router.register` に URLとそのURLがリクエストされた時に呼び出すview.pyのクラス（ここでは CouponViewSet）を紐付ける。


```coupon/urls.py
from django.urls import path
from . import views
from rest_framework import routers
from .views import CouponViewSet

router = routers.DefaultRouter()
router.register(r'coupons', CouponViewSet)

```
上記で、`r’coupons’` の　`‘coupons’` の部分が リクエストURLの後ろに付きます。


`ami_coupon_api/urls.py`の編集内容は下記の通りです。

* `django.conf.urls` の url と　include をインポート
* `coupon/urls.py` で定義した router を coupon_routerとしてインポート 
* urlpatternsにURLと呼び出し先を定義
* URLが　`admin/` の場合は、django の コンソールへ進むように設定
* `api/` の場合は、router で設定したオブジェクトへ進むように設定

```ami_coupon_api/urls.py
from django.contrib import admin
from django.urls import path,include
from django.conf.urls import url, include
from coupon.urls import router as coupon_router

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include(coupon_router.urls)),
]
```


### curl コマンドを使って動作確認をする

　新しいターミナルを開いて GET、POST、PUT のリクエストを試してみます。リクエストの書き方は下記の通りです。-X はHTTPメソッド（GET、POST、PUT　など）を指定するためのオプションです。なお、GETとPOST は指定しなくても実行可能でした。

```
$ curl -X [HTTPメソッド] [URL] [リクエストパラメータ]
```

GETを試します。リクエストパラメータは付けていません。

```
$ curl -X GET http://127.0.0.1:8000/api/coupons/
```

こんなjsonが返ってきました。

---
[{"id":1,"code":"0001","benefit":"お会計から1,000円割引","explanation":"5,000円以上ご利用のお客様限定。他クーポンとの併用不可。","store":"全店","start":"2019-10-01","deadline":"2019-12-31","status":true},{"id":2,"code":"0002","benefit":"お会計を10%オフ！","explanation":"他クーポンとの併用不可","store":"有楽町店","start":"2019-10-01","deadline":"2019-12-31","status":true},{"id":3,"code":"0003","benefit":"【ハロウィン限定】仮装して来店すると30%オフ","explanation":"全身の50%以上を仮装されているお客様限定（判断はスタッフの感覚とさせて頂きます）。他クーポンとの併用不可","store":"神田店","start":"2019-10-31","deadline":"2019-10-31","status":true},{"id":4,"code":"0004","benefit":"【9月限定】お月見団子サービス","explanation":"ご希望のお客様に月見団子をプレゼント！　他クーポンとの併用可能です！","store":"全店","start":"2019-09-01","deadline":"2019-09-30","status":true},{"id":5,"code":"0005","benefit":"【雨の日限定】お会計から15%オフ","explanation":"クーポンが配信された時だけ利用可能です。他クーポンとの併用不可","store":"全店","start":"2019-10-01","deadline":"2019-12-31","status":false},{"id":6,"code":"0006","benefit":"【日曜日限定】乾杯テキーラサービス","explanation":"テキーラを人数分サービスします。他クーポンとの併用可。","store":"神田店","start":"2019-11-03","deadline":"2019-12-01","status":true}]

---

POSTを試します。

```
curl -X POST http://127.0.0.1:8000/api/coupons/ -d "code=0007" -d "benefit=お会計から19%引き" -d "explanation=１２月２９日～１２月３１日限定。 " -d "store=神田店" -d "start=2019-12-29" -d "deadline=2019-12-31" -d "status=true"
```

上手くいくとPOSTしたデータがjsonで返って来ます。

---
{"id":7,"code":"0007","benefit":"お会計から19%引き","explanation":"１２月２９日～１２月３１日限定。","store":"神田店","start":"2019-12-29","deadline":"2019-12-31","status":true}

---


PUTを試します。
PUTの時はHTTPメソッドを指定する必要があります。それより重要なのは、**URLにテーブルのプライマリキーを指定する**必要があります。couponモデルでは”id”がプライマリキーになっているので、URLの末尾に上書きしたいデータのid（ここでは 7 ）を指定します。

```
curl -X PUT http://127.0.0.1:8000/api/coupons/7/ -d "code=0007" -d "benefit=お会計から19%引き" -d "explanation=１２月２９日～１２月３１日限定。他のクーポンとの併用不可 " -d "store=神田店" -d "start=2019-12-29" -d "deadline=2019-12-31" -d "status=true"
```

上手くいくと上書きされたデータがjsonで返って来ます。

---
{"id":7,"code":"0007","benefit":"お会計から19%引き","explanation":"１２月２９日～１２月３１日限定。他のクーポンとの併用不可","store":"神田店","start":"2019-12-29","deadline":"2019-12-31","status":true}

---

DELETEを試します。
先ほどPOSTしたリクエストをもう一度実行し、id=8のクーポンを作ります。GETリクエストかDjangoサーバのコンソールでid=8のクーポンが追加されたことを確認してください。

次に、下記のリクエストでid=8のクーポンを削除します。PUTと同様に削除対象のクーポンのプライマリキーの指定が必要です。

```
curl -X DELETE http://127.0.0.1:8000/api/coupons/8/
```

上手くいくと何も返って来ません。
GETリクエストかDjangoサーバのコンソールでid=8のクーポンが削除されたことを確認してください。



ここまでで基本的なRest Framework が出来ました。ここから [条件に合うクーポンのみをGETするためのフィルタや認証機能を追加していきます。](https://qiita.com/Ajyarimochi/items/7e22de20292ca57ea8dc)