Django REST frameworkでAPIを作成する

# Django REST frameworkでAPIを作成する
備忘録
間違い等ありましたらご指摘いただけると幸いです

## この記事を読んだら
- Django REST framework（drf）で簡単なAPIを作成できる

Djangoとかdrfについての説明は省きます

## 1. 環境構築
実行環境
```
Python 3.10.4
Django 4.1.4
rest_framework 3.14.0
```
### 1.1. ライブラリのインストール
```
# poetry
$ poetry add django
$ poetry add djangorestframework

# pip
$ pip install django
$ pip install djangorestframework
```

### 1.2. Djangoプロジェクト作成
Djangoにはプロジェクトとアプリがあり、アプリは何かしらの処理を行うwebアプリケーションのことで、アプリをまとめたものがプロジェクトである。
プロジェクトを作成する
```
$ django-admin startproject <project_name>
```
このコマンドでデータベースの設定やDjangoのオプション、アプリケーションの設定などを定義するファイルが自動生成された。
現時点でファイル構成は以下のようになる。
```
<path>/<project_name>/
    manage.py
    <project_name>/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
```
これでプロジェクトが完成した。次ステップで`manage.py`を実行するのでディレクトリを移動しておく。
```
$ cd <project_name>
```
次はアプリを作成する

### 1.3. Djangoアプリ作成
`manage.py`でアプリを作成する
```
$ python manage.py startapp <app_name>
```
ディレクトリ構成は以下のようになる
```
<project_name>/
    manage.py
    <project_name>/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
    <app_name>/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        views.py
```
### 1.4. 設定ファイルの修正
アプリを作ったので設定ファイル(`<project_name>/settings.py`)に追記する
```project_name/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    '<app_name>', # アプリ追加
    'rest_framework', # drfも追加する
]
...
```
## 2. APIを作る
顧客管理APIを試しに作ってみる。

必要な機能は
- 顧客登録（POST）
- 顧客情報閲覧（GET）
- 顧客情報の修正（PATCH）
- 顧客情報の削除（DELETE）

基本的に編集するのは`<app_name>`の下の
- `models.py`
- `serializers.py`
- `views.py`
- `urls.py`

の四つ。各ファイルの役割を説明する
#### `models.py`
DBのレイアウトを定義する。
APIがDBを必要としない場合はこのファイルはいじらない
今回は顧客をDBに登録したいので必要。

#### `serializers.py`
シリアライザーを定義する。
シリアライズはクエリセットやモデルインスタンスなどの複雑なデータをJSONやXMLなどのコンテンツタイプに変換する処理。
シリアライザーはデシリアライズ（シリアライズの逆）も提供している。

#### `views.py`
リクエスト（GET, POST, PUT, PATCH, DELETE）に対する振る舞いを定義する。APIの本体。

#### `urls.py`
APIのurlを定義する。


### 2.1. ファイル作成
`<app_name>`フォルダに
- `serializers.py`
- `urls.py`

を作成する

### 2.2. コードを書く
#### 2.2.1. `models.py`
次のようなテーブルを作る
|カラム名|型|Null可否|デフォルト|備考|
|-|-|-|-|-|
|created_at|DATETIME|否|現在時刻|インスタンスが作成された時間|
|updated_at|DATETIME|可||データが更新された時間|
|user_id|STRING|否|uuid自動生成|ユーザーid。PK。uuidを自動生成する|
|name|STRING|否||ユーザーの名前|
|age|INTEGER|可||ユーザーの年齢|
|gender|INTEGER|可||ユーザーの性別。男、女、その他を選択できるようにする|

`models.py`は以下のようになる。
```project_name/app_name/models.py
from django.db import models
import uuid
# Create your models here.

# モデルを作成するのにmodels.Modelを継承する
class Customer(models.Model):
    # 性別の選択肢を作成するリスト
    GENDER = [
        (0, '男'),
        (1, '女'),
        (2, 'その他')
    ]
    # フィールドを定義する
    created_at = models.DateTimeField(auto_now_add=True, null=False)
    updated_at = models.DateTimeField(auto_now=True)
    user_id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False, null=False)
    name = models.CharField(max_length=100, null=False)
    age = models.IntegerField()
    gender = models.IntegerField(choices=GENDER)

    class Meta:
        # created_atで並べる
        ordering = ['created_at']
```
基本的にフィールドを`型名Field`で定義していく。必要に応じてオプションを追加する
コード内のオプションはそれぞれ
`null` : nullを許可するかどうか
`auto_now_add` : インスタンス作成時の時間を追加する
`auto_now` : インスタンスを保存するたびに時間を更新する
`default` : デフォルト値
`primary_key` : pkであるかどうか
`editable` : 編集可能かどうか
`max_length` : 最大の長さ（CharFieldはこれがないとエラーになる）
`choices` : 選択肢を提供する
となっている。他にも色々とあるので[モデルフィールドリファレンス](https://docs.djangoproject.com/en/4.1/ref/models/fields/)を参照。

#### 2.2.2 `serializers.py`
`serializers.py`の中ではレスポンスで表示するカラムを設定する。
```project_name/app_name/serializers.py
from rest_framework import serializers
from .models import Customer

# シリアライザー作成はserializers.ModelSerializerを継承する
class CustomerSerializer(serializers.ModelSerializer):
    class Meta:
        # 先ほど作成したモデルを指定
        model = Customer
        # 読み取り専用にするフィールドを指定
        read_only_fields = ('user_id', 'created_at', 'updated_at')
        # read_only以外の全てのフィールドをシリアライズする
        fields = '__all__'
```

#### 2.2.3 `views.py`
リクエストに対して色々処理を加えて返すこともできるが、ここではシンプルにCRUDができるようなAPIを作成する。
```project_name/app_name/views.py
from .models import Customer
from .serializers import CustomerSerializer
from rest_framework import generics

# Create your views here.

# 一覧表示&顧客登録ができるview
class CustomerList(generics.ListCreateAPIView):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer

# 顧客の詳細閲覧&情報の削除&修正ができるview
# このviewはuser_idをパラメータに指定する予定であり、顧客を特定しないとできないような動作を行う
class CustomerDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer
```
`generics.ListCreateAPIView`は一覧表示・インスタンス作成ができるAPI、`generics.RetrieveUpdateDestroyAPIView`は特定のインスタンスの閲覧・削除・修正ができるAPIを作成できる。

### 2.2.4 `urls.py`
APIのurlを設定する。
```project_name/app_name/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # 一覧表示・インスタンス作成ができるAPIのエンドポイント
    path('customers/', views.CustomerList.as_view()),
    # pk(user_id)を指定してインスタンスの閲覧・削除・修正ができるAPIのエンドポイント
    # 例：customers/d20dbce4-b092-4047-b00b-79e8b829b140
    path('customers/<str:pk>/', views.CustomerDetail.as_view()),
]
```
`<project_name>/urls.py`も変更する。
```project_name/urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
    # 追加、APIのurlはdomain/api/<api_name>となる
    path('api/', include('drf_app.urls')),
]
```
### 2.3. マイグレーション
データベースへのマイグレーションを行う。
```
# マイグレーションファイルの作成
$ python manage.py makemigrations
# マイグレート
$ python manage.py migrate
```
### 2.4. 実行
サーバーを立ち上げる。
```
$ python manage.py runserver
```
エラーが出ずサーバーが立ち上がったら成功。次のようなログが出るはず
```
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
April 06, 2023 - 14:28:53
Django version 4.1.4, using settings '<project_name>.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

```


`http://localhost:8000/api/customer/`にアクセスすると次のような画面になる。
![Screenshot 2023-04-06 at 23.27.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/2f0d4681-a381-ca85-d9f8-c3dd81d963ce.png)
値を入れてPOSTすると...
![Screenshot 2023-04-06 at 23.30.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/556fcfbd-ccc5-3f1e-136b-52ed48800371.png)
ちゃんと登録できた。
![Screenshot 2023-04-06 at 23.30.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/e48bae69-9b54-b460-ce07-4c7e25f39f06.png)
`http://localhost:8000/api/customer/`に再度アクセスすると、POSTしたデータが表示されているのがわかる。
![Screenshot 2023-04-06 at 23.33.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/750f057a-dc2f-922a-c3b0-55115ea94ef8.png)
user_idがわかったので`http://localhost:8000/api/customer/6835dd62-5e2e-4a76-96a0-3cb61873693d`にアクセスしてみると
![Screenshot 2023-04-06 at 23.31.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2626540/67bce76d-7ff2-0644-8cba-fb060dd43e15.png)
詳細を取得することができた。この画面からPUT、DELETEもできる。

権限も特に付けてないのでcurl経由とかでもデータを取得できる。
```
$ curl http://127.0.0.1:8000/api/customers/6835dd62-5e2e-4a76-96a0-3cb61873693d/
>> {"user_id":"6835dd62-5e2e-4a76-96a0-3cb61873693d","created_at":"2023-04-06T14:30:45.073879Z","updated_at":"2023-04-06T14:30:45.073929Z","name":"江田島平八","age":33,"gender":0}
```
ありがとうございました。
# 参考
[drf公式ドキュメント](https://www.django-rest-framework.org/tutorial/quickstart/)
[Django公式ドキュメント](https://docs.djangoproject.com/en/4.2/intro/tutorial01/)