Docker & Django API 実装 やってみよう

## **はじめに**
最近学習している Docker と Django REST framework を使って、Todo アプリの API 作成にチャレンジします、しました。
初心者奮闘記をここに記します。（ソースコードは[こちら](https://github.com/Yi-Gaoqiao/homework)）

## **到達目標**
- Docker を用いた開発環境構築
- Django REST framework を使った簡易な API 作成

## **環境**
    macOS Catalina 10.15.2
    docker desktop community 2.3.0.3
    docker version Client: 19.03.8
    docker version Server: 19.03.8

## **インプット**
###Django REST framework の概要
Django が Python で Webアプリケーションを作成するためフレームワークだったのに対し、Django REST Framework は Django を用いた **WebAPI の開発をサポートしてくれるフレームワーク**のこと。Django REST framework を使えば RESTful な API バックエンドを簡単に構築することができます。
RESTful API に関しては[こちら](http://e-words.jp/w/RESTful_API.html)をご参照ください。

###Docker の概要
Dockerは**コンテナ仮想化技術**を簡単に扱えるようにしたソフトウェアのこと。
従来の仮想環境構築に比べ、必要最小限のハードウェアのリソースでアプリケーションを利用できる、アプリケーションの起動が速い、OSやアプリケーションなどの環境が構築済みのコンテナイメージが用意されており、新たなアプリケーションの構築が簡単、といった点で優れているみたいです。
Docker に関しては[こちらの記事](https://qiita.com/SatoshiSobue/items/a612ebbb3a9242c09db5)が大変参考になりました。何度も読ませていただいてます。

## **Docker インストール**
[Docker公式](https://docs.docker.com/) から Docker アカウントを作成し、Docker をインストールします。
インストールのページに進むと、`Get Stable` と `Get Edge` があるかと思います。
どうやらこれは更新頻度の違いらしく、**Stable** は3ヶ月に1度、**Edge** は毎月更新みたいです。
これは後からでも変更できるので、とりあえずどちらでも問題ないでしょう。

アカウント作成とインストールの詳細な手順は[こちら](https://pigblog.org/django-restframework-basic2)の記事をご参照ください。

## **レッツ環境構築**
### Github でレポジトリ作成
[Github](https://github.co.jp/) にて適当なレポジトリを作成し、ローカルにクローンしましょう。
Github アカウントを持ち合わせていない方は[こちらの記事](https://pikawaka.com/word/github)を参考にアカウント作成をお願いします。
<img width="650" alt="スクリーンショット 2020-07-20 7.43.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/0f80e3e3-ef54-4003-3263-84b96f3a4298.png">

### ローカルPCにクローン
<img width="682" alt="スクリーンショット 2020-07-20 7.58.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/04467e67-fd2e-b650-800c-bfa9d661fa75.png">

URL をコピーし、ターミナルで git コマンドを入力します。

    $ git clone https://github.com/Yi-Gaoqiao/todo-api.git

クローンしたレポジトリに移動し、中身を確認したらエディタを開きます。

    $ cd todo-api/         # 作業ディレクトリに移動
    $ ls                   # 中身を確認
    LICENSE   README.md
    $ code .               # vscode起動

### **Dockerfile の作成**
Dockerfile（Dockerのイメージをビルドするための設定ファイル）を新規作成し、以下の通り記述します。
/todo-api/Dockerfile

    FROM python:3.7-alpine                       # ベースとするDockerイメージを指定
    LABEL architecture="Your Name"               # 構築担当者をラベル付け

    ENV PYTHONUNBUFFERD 1                        # リアルタイムでログを見れるように環境変数を指定

    COPY ./requirements.txt /requirements.txt    # ローカルのrequirements.txtをコンテナにコピー
    RUN pip install -r /requirements.txt         # requirements.txtに従ってパッケージを一括でインストール

    RUN mkdir /django-api                        # Djangoプロジェクトを置くディレクトリをコンテナ上に作成
    WORKDIR /django-api                          # コンテナ上の作業ディレクトリを変更
    COPY ./django-api /django-api                # ローカルのdjango-apiディレクトリをコンテナにコピー

    RUN adduser -D user                          # アプリケーションを実行するためのユーザを作成する
    USER user                                    # ユーザをrootから変更

ここで忘れないように、ローカルに django-api ディレクトリを作成しておきます。
### **requirements.txt の作成**
必要なパッケージとパージョンをここに記述し、それらを一括でインストールします。
今回必要なパッケージは Django と djangorestframework です。各パッケージのバージョンは [PyPI](https://pypi.org/) で検索しましょう。
ファイルの中身は以下の通りです。

/todo-api/requirements.txt

    Django>=3.0.7,<3.1.0
    djangorestframework>=3.11.0,<3.12.0

### **docker-compose.yml の作成**
docker-compose.yml とはアプリケーションを動かすための処理を記述しているファイルです。
また、複数のコンテナを用いたアプリケーションの Docker イメージのビルドや各コンテナの起動・停止が簡単にできてしまう優れものです。
今回は app という1つのコンテナのみ起動させたいと思います。

/todo-api/docker-compose.yml

    version: "3"

    services:
      app:
        build:
          context: .
        ports:
          - "8000:8000"
        volumes:
          - ./django-api:/django-api
        command: >
          sh -c "python manage.py migrate &&
                 python manage.py runserver 0.0.0.0:8000"


以下、それぞれのコマンドの説明です。もっともっと知りたい方は[こちらの記事](https://knowledge.sakura.ad.jp/16862/)を参照ください。

- **services**
    - この下にコンテナを定義。

- **app**
    - コンテナの名前を指定。

- **build**
    - Dockerfile が置いてあるディレクトリのパスを記述。
    - 配布されているイメージ（公式イメージなど）を取得する場合は指定不要。

- **ports**
    - ポートマッピングを指定。"ホスト側：コンテナ側"。

- **volumes**
    - ディレクトリマウント。
    - ホスト側からファイルを配置したり編集したりできるようマウントする。

- **command**
    - コンテナ起動時に実行されるコマンド。サーバー起動時にマイグレーションするよう記述しています。


### **Dockerfile から Docker イメージのビルド**
ターミナルで次のコマンドを実行します。

    $ docker-compose build
    Building app
    ...
    Successfully built f624466b62a4
    Successfully tagged todo-api_app:latest


コマンド実行後、Successfully tagged...のようになればばっちりです。
作成されたイメージは次のコマンドで確認できます。

    $ docker images
    REPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
    todo-api_app                     latest              840de00a35b5        6 seconds ago       116MB

ここまでは問題なさそうですね。
## Django プロジェクトの作成
ターミナルで次のコマンドを実行します。
コマンド実行後は先ほどの django-api ディレクトリの中に todo_project が作成されているか確認してください。

    $ docker-compose run --rm app sh -c "django-admin.py startproject todo_project ."

- **--rm**
    - コンテナを起動したりイメージを build したりしていると、どんどん使っていないコンテナやイメージなどが溜まっていき、ディスク容量を圧迫してしまいます。それを防ぐために、コマンド実行時にコンテナを削除してしまいます。
- **sh -c ""**
    - ""内に実行するシェルスクリプトを記述します。このコマンドは必須ではないみたいですが、個人的にどこからどこまでが docker-compose のコマンドか明確にしたいので使うようにしています。

## Django アプリの作成
アプリの作成は以下のコマンドを実行します。アプリ名はお好みでどうぞ（あたくしは core でいきます）。

    $ docker-compose run --rm app sh -c "python manage.py startapp core"

### カスタム User モデルの作成
Django 標準の User モデルではユーザー認証時、名前とパスワードが用いられます。
いや普通ユーザー登録ってなったら名前ではなくメールアドレスだろう、という皆々様のお声に応え、今回は特別に User モデルをカスタマイズします。
ごめんなさい嘘です。以下、[Django 公式ドキュメント](https://docs.djangoproject.com/ja/3.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)より
>新しくプロジェクトを始める場合は、デフォルトの User で十分である場合でも、カスタムユーザーモデルを作成することを強く推奨します。このモデルはデフォルトのユーザーモデルと同様に動作しますが、必要に応じて将来的にカスタマイズすることができます:

ということで次のように記述します。

/django-api/core/models.py

    from django.db import models
    from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
    from django.conf import settings    # あとで使います。

    class UserManager(BaseUserManager):
    
        def create_user(self, email, password=None, **extra_fields):
            """Creates and saves a new user"""
            if not email:
                raise ValueError('User must have an email address')

            email = self.normalize_email(email)
            user = self.model(email=email, **extra_fields)
            user.set_password(password)
            user.save(using=self._db)

            return user

        def create_superuser(self, email, password):
            """Creates and saves a new superuser"""
            user = self.create_user(email, password)
            user.is_staff = True
            user.is_superuser = True
            user.save(using=self._db)

            return user


    class User(AbstractBaseUser, PermissionsMixin):
        """Custom user model that supports using email instead of username"""
        email = models.EmailField(max_length=255, unique=True)
        name = models.CharField(max_length=255)
        is_active = models.BooleanField(default=True)
        is_staff = models.BooleanField(default=False)

        objects = UserManager()

        USERNAME_FIELD = 'email'    # デフォルトは名前入力、今回はメールアドレスにカスタム


- **AbstractBaseUser**
    - 最低限のフィールド及び機能以外は持ち合わせていないので、動作を柔軟に定義したい際に利用。
    - パーミッション関連の機能を持ち合わせていないので、パーミッションの機能を利用したい場合は、PermissionMixin を同時に継承しておく必要がある。
- **BaseUserManager**
    - マネージャーをカスタマイズする際に継承。
    - マネージャーに関しては[こちら](https://blog.narito.ninja/detail/105/)をご参照ください。

### アプリの追加登録と、カスタム User モデル利用の宣言
/django-api/todo-project/settings.py

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

    ~~~~
     略
    ~~~~

    STATIC_URL = '/static/'

    AUTH_USER_MODEL = 'core.User'    # ファイル最下部に記述


**注意点**：一番初めの migrations(init(0001)) の前にカスタム User モデルの作成・登録を済ませないと後々不都合が生じるので、気をつけましょう。

### マイグレーション
    $ docker-compose run --rm app sh -c "python manage.py makemigrations core"
    Migrations for 'core':
      core/migrations/0001_initial.py
        - Create model User

## 第一回コンテナ起動
さぁ、みなさんお待ちかね。コンテナ起動のお時間がやってまいりました。

    $ docker-compose up
    ...
    app_1  | Watching for file changes with StatReloade

dockerfile に記述したコマンドが実行され、コンテナが起動します。
起動がうまくいったら、`http://127.0.0.1:8000/`に行ってロケットの打ち上げをこの目に焼き付けましょう！
<img width="985" alt="スクリーンショット 2020-07-22 7.10.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/1d08c0da-3ee4-210a-170f-fafc41851949.png">

## 管理者（superuser）の作成
    docker-compose run --rm app sh -c "python manage.py createsuperuser"
    Email: superuser@dummy.co.jp
    Password: 
    Password (again): 
    Superuser created successfully.
任意のメールアドレスとパスワードを入力します（パスワードは画面に表示されません）。

### 管理画面にモデルを登録
先ほど作成した User モデルを管理画面で確認できるよう、admin.py に変更を加えます。
User モデルをカスタマイズしていたので、管理画面に表示させる項目もカスタマイズします。

/django-api/core/admin.py

    from django.contrib import admin
    from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
    from django.utils.translation import gettext as _

    from core import models


    class UserAdmin(BaseUserAdmin):
        ordering = ['id']
        list_display = ['email', 'name']
        fieldsets = (
            (None, {'fields': ('email', 'password')}),
            (_('Personal Info'), {'fields': ('name',)}),
            (
                _('Permissions'),
                 {'fields': ('is_active', 'is_staff', 'is_superuser')}
            ),
            (_('Important dates'), {'fields': ('last_login',)})
        )

    admin.site.register(models.User, UserAdmin)

ユーザーは id 順に並べられ、メールアドレスと名前が表示されるようにしています。また fieldsets では各ユーザーの詳細情報について表示する項目を指定しています。

- **BaseUserAdmin**
    - デフォルトの UserAdmin を BaseUserAdmin としてインポートし、それを継承することでカスタマイズします。

- **gettext as _**
    - gettext は多言語対応のために利用されます。慣習的に as _ としてインポートするみたいです。今回、言語設定に関してはデフォルトの英語のまま進めるので、翻訳に関して気になる方は[こちらの記事](https://blog.narito.ninja/detail/86/)をご参照ください。

### ブラウザで確認
コードを見るだけではイメージ沸きにくいかと思うので、実際に管理画面にてどのように表示されるか見に行きましょう。
`docker-compose up` でコンテナを起動し、 `http://127.0.0.1:8000/admin/`に移動して先ほど作った superuser を確認します。
確認ができたら `admin.py` の記述内容と照らし合わせてみましょう。

`http://127.0.0.1:8000/admin/core/user/1/change/`
<img width="943" alt="スクリーンショット 2020-07-22 8.16.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/51023ad2-a8f6-40a3-cb9e-f92fd961a88d.png">

## User モデル対応 app の作成
わかりやすいように app の名前は user にします。

    docker-compose run --rm app sh -c "python manage.py startapp user"

app を作成したら例の如く settings.py に登録します。

/django-api/todo-project/settings.py

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'rest_framework',               # これから使います
        'rest_framework.authtoken',     # これから使います
        'core',
        'user',                         # 追加
    ]


### Serializer の作成
#### シリアライズとは
クエリセットやモデルのインスタンスのような複雑なデータをJSON、XMLなどの出力可能なデータに書き出すこと。Django のモデルを異なるフォーマットに翻訳する、みたいな感じ。元の形式に復元する処理はデシリアライズと呼ぶ。（参考：[Django REST framework 公式](https://www.django-rest-framework.org/api-guide/serializers/)）

serializers.py はデフォルトで用意されていないので、自分で作成します。
/django-api/user/serializers.py

    from django.contrib.auth import get_user_model

    from rest_framework import serializers


    class UserSerializer(serializers.ModelSerializer):
        """Serializer for the user object"""

        class Meta:
            model = get_user_model()
            fields = ('email', 'password', 'name')
            extra_kwargs = {'password': {'write_only': True, 'min_length': 8}}
    
        def create(self, validated_data):
            """Create a new user with encrypted password and return it"""
            user = get_user_model().objects.create_user(**validated_data)

            return user

- **ModelSerializer**
    - Django のモデルと紐づいています。モデルに基づいてフィールドとバリデータが自動的に Serializer にも適用されます。（Serializer はこれに限らず数種類用意されています。例えばただの Serializer は最も基本的なシリアライザで、対象が Django のモデルである必要はありません。このあと登場予定です。）

- **get_user_model()**
    - get_user_model 関数は、そのプロジェクトで使用している User モデルを取得します。つまりデフォルトの User か、カスタムした User が返ります。汎用的な処理が書けるようになるので、ユーザーをインポートするときは get_user_model 関数を使うといいですね。


### View の作成
#### View とは
View はクライアント（ブラウザなど）からのリクエストに応じ、どういった処理をするか（どの API を提供するか）を司っています。実装はめちゃんこシンプル。

/django-api/user/views.py

    from rest_framework import generics

    from user.serializers import UserSerializer


    class CreateUserView(generics.CreateAPIView):
        """Create a new user in the system"""
        serializer_class = UserSerializer

serializer_class に先ほど作った UserSerializer を指定しています。
ここまでで Serializer と View の作成はひとまずおっけいです。ひとまずね。
urls.py を新規作成します。

/django-api/user/urls.py

    from django.urls import path

    from user import views


    app_name = 'user'

    urlpatterns = [
         path('create/', views.CreateUserView.as_view(), name='create'),
    ]

- **.as_view**
    - こちらは View の条件を満たす関数を自動で実行してくれる優れものでございます。リクエストメソッド（GET, POST など）に応じた条件分岐の記述を省略してくれます。

続いて /user/urls.py を /todo_project/urls.py にルーティングします。

/django-api/todo_project/urls.py

    from django.contrib import admin
    from django.urls import path, include           # 追加

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/user/', include('user.urls')),    # 追加
    ]

### ブラウザで確認
`docker-compose up` でコンテナを起動し、`http://127.0.0.1:8000/api/user/create/` に行きましょう。
実際にメールアドレス、パスワード、名前を入力してユーザーを新規登録してみましょう。
<img width="1160" alt="スクリーンショット 2020-07-23 9.10.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/4dbb8f7d-974c-5e6a-400d-0b1ca064cf4d.png">

<img width="1159" alt="スクリーンショット 2020-07-23 9.11.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/32eb7efa-6880-c36f-4295-6c455eac8368.png">

<img width="1160" alt="スクリーンショット 2020-07-23 9.12.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/79734dcf-7dd1-29f6-fd21-e8f68dfcc9e8.png">

うまく登録されたようです。

## トークン認証
### トークン認証とは
いわゆる本人確認のようなもの。トークンはサーバーがユーザーに対して発行する認証情報のこと。
流れはこんな感じ ↓
<img width="580" alt="スクリーンショット 2020-07-23 9.24.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/5cc4e54e-0bb1-9b45-083c-d992149283db.png">

1. クライアント（ここではブラウザ）がサーバーにユーザー情報（ここではメールアドレスとパスワード）を送る。
2. サーバがユーザー情報を認証し、トークンを払い出す。
3. 次回以降そのトークンをHTPPリクエストのヘッダーに含めることで、ユーザーが認証される。

例えば先ほど作成したユーザーがパスワードを変更したいとなった時、認証されたユーザー（本人）だけが変更できるように制御します。
まずはトークンの発行を実装します。

/django-api/user/serializers.py

    from django.contrib.auth import get_user_model, authenticate  # 追加

    from rest_framework import serializers


    class UserSerializer(serializers.ModelSerializer):

    # 追加
    class AuthTokenSerializer(serializers.Serializer):
        """Serializer for the user authentication object"""
        email = serializers.CharField()
        password = serializers.CharField(
            style={'input_type': 'password'},
            trim_whitespace=False
        )

        def validate(self, attrs):
            """Validate and authenticate the user"""
            email = attrs.get('email')
            password = attrs.get('password')

            user = authenticate(
                request=self.context.get('request'),
                username=email,
                password=password
            )
            if not user:
                msg = ('Unable to authenticate with provided credentials')
                raise serializers.ValidationError(msg, code='authentication')

            attrs['user'] = user
            return attrs

トークンの発行に必要なユーザー情報の項目（メールアドレスとパスワード）を設定し、validate 関数で検証を行います。
入力された情報が一致しなかった場合にエラーメッセージを表示するようにしています。

/django-api/user/views.py

    from rest_framework import generics
    from rest_framework.authtoken.views import ObtainAuthToken        # 追加
    from rest_framework.settings import api_settings                  # 追加

    from user.serializers import UserSerializer, AuthTokenSerializer  # 追加


    class CreateUserView(generics.CreateAPIView):
   
    # 追加
    class CreateTokenView(ObtainAuthToken):
        """Create a new auth token for user, restrict who can see Todo"""
        serializer_class = AuthTokenSerializer
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES

- **renderer_classes**
    - ブラウザ上で発行されたトークンを確認することができます。

/django-api/user/urls.py

    from django.urls import path

    from user import views


    app_name = 'user'

    urlpatterns = [
         path('create/', views.CreateUserView.as_view(), name='create'),
         path('token/', views.CreateTokenView.as_view(), name='token'),    # 追加
    ]

### ブラウザで確認
もうわかりますよね。そうです、`docker-compose up` から `http://127.0.0.1:8000/api/user/token/` に行きます。
先ほど登録したユーザーのトークンを発行してみましょう。
<img width="1163" alt="スクリーンショット 2020-07-23 10.11.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/dcb938db-723c-4648-b1fd-c78fa0e5dbcb.png">
<img width="1157" alt="スクリーンショット 2020-07-23 10.12.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/86b1ba77-6869-d10e-2fff-6f2c5ebdf51a.png">
<img width="1159" alt="スクリーンショット 2020-07-23 10.12.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/9c801499-1677-80b5-e71f-aeb27dd9434f.png">

不規則な謎の文字列が返ってきましたね。それ、トークンです。

### ユーザー情報の更新
認証されたユーザーが自身の登録情報を更新できるようにします。

/django-api/user/serializers.py

    class UserSerializer(serializers.ModelSerializer):
        """Serializer for the user object"""

        class Meta:
            model = get_user_model()
            fields = ('email', 'password', 'name')
            extra_kwargs = {'password': {'write_only': True, 'min_length': 8}}
    
        def create(self, validated_data):
            """Create a new user with encrypted password and return it"""
            user = get_user_model().objects.create_user(**validated_data)

            return user

        # 追加
        def update(self, instance, validated_data):
            """Update a user, setting the password correctly and return it"""
            password = validated_data.pop('password', None)
            user = super().update(instance, validated_data)

            if password:
                user.set_password(password)
                user.save()
        
            return user

- **.pop('password')**
    - 更新前のパスワードを削除します。（無ければ None が返ってきます。）

/django-api/user/views.py

    from rest_framework import generics, authentication, permissions  # 追加
    from rest_framework.authtoken.views import ObtainAuthToken
    from rest_framework.settings import api_settings

    from user.serializers import UserSerializer, AuthTokenSerializer


    class CreateUserView(generics.CreateAPIView):

    class CreateTokenView(ObtainAuthToken):


    # 追加
    class ManageUserView(generics.RetrieveUpdateAPIView):
        """Manage the authenticated user"""
        serializer_class = UserSerializer
        authentication_classes = (authentication.TokenAuthentication,)
        permission_classes = (permissions.IsAuthenticated,)

        def get_object(self):
            """Retrieve and return authentication user"""
            return self.request.user

- **authentication, permissions**
    - 読んで字の如く、認証と許可を司ります。ここでは認証方法はトークン認証を利用し、そして認証されたユーザーのみ閲覧・編集を許可する、ということであります。（状況に応じて、認証されていないユーザーは閲覧のみ許可、といった制限もできます。）

/django-api/user/urls.py

    from django.urls import path

    from user import views


    app_name = 'user'

    urlpatterns = [
        path('create/', views.CreateUserView.as_view(), name='create'),
        path('token/', views.CreateTokenView.as_view(), name='token'),
        path('update/', views.ManageUserView.as_view(), name='update'),  # 追加
    ]

### ブラウザで確認
`docker-compose up` から `http://127.0.0.1:8000/api/user/update/` に行きます。
<img width="1158" alt="スクリーンショット 2020-07-23 10.33.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/d6b41c40-9d18-b594-7851-f6ef4683ac0b.png">

そうです、実はこのままだとヘッダーにトークンが含まれていないため更新できません。
ここで chrome の ModHeader というツールを利用すると、ヘッダーにトークンを追加することができ、トークン有無での挙動の違いを検証できます。こちらは [chrome ウェブストア](https://chrome.google.com/webstore/search/modheader)から追加します。
追加後は画面右上のアイコンから Request header を追加し、Name に "Authorization" を、Value には "Token `http://127.0.0.1:8000/api/user/token/` で発行されたトークン" を設定し、左にチェックを入れます。
<img width="1680" alt="スクリーンショット 2020-07-23 10.43.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/2f73933a-75c0-2cf3-0dc6-b9761780bc66.png">

この状態でもう一度 `http://127.0.0.1:8000/api/user/update/` にアクセスしてみてください。ユーザー情報が更新できるようになっています。
チェックを外すとまたしても更新できなくなります。
<img width="1158" alt="スクリーンショット 2020-07-23 10.51.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/29a79ace-511e-80ef-3388-d0a1f298ba87.png">
<img width="1162" alt="スクリーンショット 2020-07-23 10.51.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/dc470899-d511-9ba5-7706-0d9e23bc4550.png">
<img width="1154" alt="スクリーンショット 2020-07-23 10.51.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/a0ddd95d-537a-af4c-db15-74c7008f0106.png">

お疲れ様でした。以上が User app に関わる記述です。
最後に Todo app を作成します。流れは User app とほぼ一緒ですので、ささっと行きます。

## Todo API
モデル作成 → マイグレーション → app 作成 → app 登録 → Serializer → View → URL
以上の流れで参ります。
### Todo モデルの作成


/django-api/core/models.py

    # 最下部に追加
    class Todo(models.Model):
        """Todo object"""
        user = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE
        )

        title = models.CharField(max_length=100)
        content = models.CharField(max_length=255)
        created_at = models.DateField(auto_now_add=True)
        is_completed = models.BooleanField(default=False)

        def __str__(self):
            return self.title

- **settings.AUTH_USER_MODEL**
    - 繰り返しになりますが今回は User モデルをカスタマイズしたので、User モデルの参照時はそれを参照するよう指定する必要があります。

/django-api/core/admin.py

    admin.site.register(models.User, UserAdmin)
    admin.site.register(models.Todo)  # 最下部に追加

   
### マイグレーション
モデルに変更が加えられたときは忘れずにマイグレーションを。

    $ docker-compose run --rm app sh -c "python manage.py makemigrations core"
    Migrations for 'core':
      core/migrations/0002_todo.py
        - Create model Todo

### Todo app の作成

    $ docker-compose run --rm app sh -c "python manage.py startapp todo"

app 作成後は settings.py への登録も忘れずに。
/django-api/todo_project/settings.py

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'rest_framework',
        'rest_framework.authtoken',
        'core',
        'user',
        'todo',  # 追加
    ]

### Serializer の作成
/django-api/todo/serializers.py（新規作成）

    from rest_framework import serializers

    from core.models import Todo
    from user.serializers import UserSerializer

    class TodoSerializer(serializers.ModelSerializer):
        """Serializer for Todo objects"""

        user = UserSerializer(read_only=True)

        class Meta:
            model = Todo
            fields = ('id', 'user', 'title', 'content', 'created_at', 'is_completed')
            read_only_fields = ('id', 'user',)

Todo モデルと紐づいている User モデルの user フィールドに関しては UserSerializer でシリアライズします。 

### View, Pagination の作成
/django-api/todo/views.py

    from rest_framework import viewsets, generics, pagination, response
    from rest_framework.authentication import TokenAuthentication
    from rest_framework.permissions import IsAuthenticated

    from core.models import Todo

    from todo import serializers


    class TodoPagination(pagination.PageNumberPagination):
        """Get 2 Todo items in a page"""
        page_size = 2

        def get_paginated_response(self, data):
            return response.Response({
                'next': self.get_next_link(),
                'previous': self.get_previous_link(),
                'count': self.page.paginator.count,
                'total_pages': self.page.paginator.num_pages,
                'current_page': self.page.number,
                'results': data,
                'page_size': self.page_size,
                'range_first': (self.page.number * self.page_size) - (self.page_size) + 1,
                'range_last': min((self.page.number * self.page_size), self.page.paginator.count),
            })


    class TodoViewSet(viewsets.ModelViewSet):
        """Handles creating, reading and updating todo items"""
        authentication_classes = (TokenAuthentication,)
        permission_classes = (IsAuthenticated,)
        serializer_class = serializers.TodoSerializer
        queryset = Todo.objects.order_by('-created_at')
        pagination_class = TodoPagination

        def perform_create(self, serializer):
            """Create a new Todo item"""
            serializer.save(user=self.request.user)

- **pagination.PageNumberPagination**
    - デフォルトでは1ページで全件取得してしまうため、ページネーション機能を追加して1ページあたりの取得件数を制限します。
    - response.Response の中身は英字通りで、デフォルトよりちょっと見栄えがよくなります。

- **ModelViewSet**
    - 基本的な APIView が備わっています。とても便利です。

以下、参考までに。

    .list()              # 全件取得
    .retrieve()          # 1件取得
    .create()            # 作成
    .update()            # 更新
    .partial_update()    # 一部更新
    .destroy()           # 削除


### url の作成
/django-api/todo/urls.py（新規作成）

    from django.urls import path, include
    from rest_framework.routers import DefaultRouter

    from todo import views


    router = DefaultRouter()
    router.register('todo', views.TodoViewSet)

    app_name = 'todo'

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

- DefaultRouter
    - Router を登録できるのは ViewSet に限ります。user app で作成した generics の view とか登録しようもんならちゃんとエラー発生します。
    - Router はめちゃめちゃ便利で、詳細な API（/todo/1/ とか /todo/4/）を自動的に付加して URL 登録してくれます。わざわざ /todo/<int:pk>/ みたいなこと書かなくていいんです。すごい。

/django-api/todo_project/urls.py

    from django.contrib import admin
    from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/user/', include('user.urls')),    
    path('api/', include('todo.urls')),      # 追加
]

これで本チャレンジの全工程が終了しました。
最後にブラウザで確認しましょう。

### ブラウザで確認（最終回）
`docker-compose up` から `http://127.0.0.1:8000/api/` に行きます。
`"todo": "http://127.0.0.1:8000/api/todo/"` に飛んでみましょう。
（繰り返しになりますが、認証されていないユーザーは閲覧すらできなくなっています。）

<img width="1163" alt="スクリーンショット 2020-07-23 11.59.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/2c9c9ae8-0275-1cf0-d354-c566789dc36b.png">

3つほど Todo を入力してみましょう。
<img width="1155" alt="スクリーンショット 2020-07-23 11.56.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/92bf1e8f-39da-14df-61d8-b64de78096a1.png">

入力した Todo もおしゃれなページネーションもばっちり反映されています。
我ながら上出来です。
<img width="933" alt="スクリーンショット 2020-07-23 11.57.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641843/e64bfb60-dd26-7843-74c7-5fd823c1f835.png">

## まとめ
今回は Docker で開発環境を構築し、Django REST framework で Todo アプリの API 実装にチャレンジしました。
初めて触れるものが多かったので完全理解とまではいきませんが、自分で調べながら完成させることができたので満足でございます。

## 参考
[本とプログラミングと時々ミニブタ](https://pigblog.org/category/programming/django-rest-framework)
[Django Brothers BLOG](https://djangobrothers.com/blogs/referencing_the_user_model/)
[さくらのナレッジ Docker入門](https://knowledge.sakura.ad.jp/16862/)
[Narito Blog](https://blog.narito.ninja/)
[Django 公式ドキュメント](https://docs.djangoproject.com/ja/3.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project)
[Django REST framework 公式](https://www.django-rest-framework.org/)
[slideship Django REST framework 実践入門](https://slideship.com/presentations/RjVo67zy1JyQiYqe3GgpLB)
[Dockerとはどういったものなのか、めちゃくちゃ丁寧に説明してみる](https://qiita.com/SatoshiSobue/items/a612ebbb3a9242c09db5)
[Build a Backend REST API with Python & Django – Beginner](https://www.udemy.com/course/django-python/)