DjangoRestFrameworkのTips

# バージョン情報
| module                 |       version     |
|:-----------------------|------------------:|
| Python                 |             3.7.7 |
| django-environ         |             0.4.5 |
| Django                 |            2.2.11 |
| djangorestframework    |            3.11.0 |
| djangorestframework-jwt|            1.11.0 |
| django-rest-swagger    |             2.2.0 |
| django-filter          |             2.2.0 |
| mysqlclient            |             1.4.6 |


# Swaggerを使用したい
## django-rest-swaggerをpipインストール
```shell
pip3 install django-rest-swagger==2.2.0
```

## settings.pyを修正
``` backend/src/config/settings/settings.py
.
..
...
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    )
}
```

## development.pyを修正
``` backend/src/config/settings/developement.py
.
..
...
INSTALLED_APPS += [
    'rest_framework_swagger',
]

REST_FRAMEWORK['DEFAULT_SCHEMA_CLASS'] = 'rest_framework.schemas.coreapi.AutoSchema'
```

## urls.pyを修正
``` backend/src/config/urls.py
from django.contrib import admin
from django.urls import path
from django.conf import settings  # 追記
from rest_framework_jwt.views import obtain_jwt_token  # 追記

urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/v1/login/', obtain_jwt_token),  # なんか1つ以上APIがないとSwagger-UIを開けないみたいなので、とりあえずログインのAPIを登録
]

# 以下全て追記
if settings.DEBUG:  # DEBUG=True（開発時）の場合のみ使用
    from rest_framework.schemas import get_schema_view
    from rest_framework_swagger import renderers
    schema_view = get_schema_view(
        title='API一覧',
        public=True,
        renderer_classes=[renderers.OpenAPIRenderer, renderers.SwaggerUIRenderer])
    urlpatterns += [
        url(API_ROOT + 'api-auth/', include('rest_framework.urls')),
        url('swagger-ui/', schema_view),
    ]

```

## Swagger-uiにアクセス
[http://0.0.0.0:8000/swagger-ui/](http://0.0.0.0:8000/swagger-ui/)にアクセスして下記のような画面が出たら右上の`Log in`を押下してログインする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/27569eb0-1b7e-2abb-b7e2-e81537032a81.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/aae55f5c-6bf2-5b0d-7643-6b3134ae8faa.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/f985d4de-9bf4-2c03-4f2b-ae4acaf64824.png)


# ViewSets編
## とりあえずモデルに対するGET, POST, PUT, DELETE...のAPIを公開したい
みんな大好き`ModelViewSets`が使えます。

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import ModelViewSet  # コレ


class UserViewSets(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
```

出来上がり（swagger-ui使ってます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/d6dfa29a-ce63-bf60-1639-ce112793b44f.png)


## 公開するHTTPリクエストメソッドは絞りたい
### 方法1: GenericViewSetと各種mixinsを組み合わせる
後でリクエストメソッドを増やしたいといった場合は対応するmixinsをつけてあげればいい

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import GenericViewSet  # コレ
from rest_framework.mixins import CreateModelMixin  # コレ


class UserViewSets(GenericViewSet, CreateModelMixin):
    queryset = User.objects.all()
    serializer_class = UserSerializer

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/9a23f1a2-bee4-bbd3-31b1-af649a4476f3.png)

### 方法2: genericsを使用する
後でリクエストメソッドを増やしたいといった場合は対応するAPIViewをつけてあげればいい

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.generics import CreateAPIView  # コレ


class UserViewSets(CreateAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
```

genericsの場合、urls.pyの指定方法が異なる

``` backend/src/config/urls.py
# =========== viewsets ===========
router = DefaultRouter()
router.register('user', user_views.UserViewSets)  # viewsetsの場合はrouterが使える

urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/v1/login/', obtain_jwt_token),
    url('', include(router.urls)),
]
```

``` backend/src/config/urls.py
# =========== generics ===========
urlpatterns = [
    path('admin/', admin.site.urls),
    url('api/v1/login/', obtain_jwt_token),
    url('user/', user_views.UserViewSets.as_view()),  # urlpatternsにas_views()で追加する
]
```

結果は方法1と同じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/07c62781-8479-9872-ad03-13794988a801.png)


## ModelViewSetを使いたい、でもHTTPリクエストメソッドは絞りたい（照）
そんなワガママさんには`http_method_names`じゃ。
実装はしたけどまだ公開したくない、みたいな場面で使うのかな・・・。

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.viewsets import ModelViewSet


class UserViewSets(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    http_method_names = ['get', 'post']  # HTTPリクエストメソッドは大文字じゃなくて、小文字（重要）
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/351e7c03-7cb1-fe05-8453-bbebf29174f4.png)

## APIをリクエストできるのは認証済みのユーザーだけに絞りたい
permission_classに`IsAuthenticated`を指定しましょう。

まずsettings.pyを修正します。

``` backend/src/config/settings/settings.py
.
..
...
REST_FRAMEWORK = {
    # 追加
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
        # 'common.permissions.IsSuperuser',  # 自作のpermissionを作成した場合は、ここに追加。
    ),
    ...
    ..
    .
}
```

未認証状態でAPIをリクエストすると、`403`が返ってきます。

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.permissions import IsAuthenticated  # 追加
from rest_framework.viewsets import ModelViewSet


class UserViewSets(ModelViewSet):
    permission_classes = [IsAuthenticated,]  # 追加
    queryset = User.objects.all()
    serializer_class = UserSerializer
```

## ルーティング可能なアドホックメソッドを追加したい（detail=False）
そんな時は`@action`デコレータじゃ。
（DRFバージョンが古いと、`@list_route`や`@detail_route`デコレータがこれにあたります。`@action`デコレータに統合されたようですね。）

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework.decorators import action  # 追加
from rest_framework.viewsets import ModelViewSet


class UserViewSets(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=False, methods=['get'], url_path='get_user', url_name='get_user')
    def get_user(self, request, *kwargs):
        """ ログインユーザー情報を返す
        """
        return UserSerializer(self.request.user).data
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/10e9135e-c813-1ae3-7146-ec26bf26c9f5.png)

## ルーティング可能なアドホックメソッドを追加したい（detail=True）
``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet


class UserViewSets(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['get'], url_path='get_user', url_name='get_user')
    def get_user(self, request, pk=None, **kwargs):
        """ ログインユーザー情報を返す
        """
        # 処理はdefail=Falseと同じです。すみません(. _ .)
        return Response(
            status=status.HTTP_200_OK,
            data=UserSerializer(self.request.user).data)
```

`api_root/{pk}/url_name/`といったAPIになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/395b223b-f73a-fe0a-c3b4-cad22af8c25e.png)

## ルーティング可能なアドホックメソッドを追加したい。URLに主キー含めたいけど、@actionデコレータのdetailはFalseがいい（照）
そんなワガママさんは`@action`デコレーターの`url_path`じゃ。

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet


class UserViewSets(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=False, methods=['get'], url_path='get_user/(?P<user_id>[0-9]+)', url_name='get_user')
    def get_user(self, request, user_id=None):
        """ ログインユーザー情報を返す
        """
        return Response(
            status=status.HTTP_200_OK,
            data=UserSerializer(self.request.user).data)
```

`detail=True`で生成されるURLが気に食わない人にはおすすめかもしれません笑

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/baefa341-0de7-cef4-9a93-da9337824596.png)

## クエリパラメータでの検索を可能にしたい
django-filterをpipインストール

```shell
pip3 install django-filter==2.2.0
```

filters.py（ファイル名は任意）を作成して、filterを定義する

``` backend/src/api/users/filters.py
from common.models import User
from django_filters import FilterSet


class UserSearchFilter(FilterSet):
    class Meta:
        model = User
        fields = '__all__'

        # クエリパラメータに指定されたくないフィールドはexcludeに追加してあげると良い
        # exclude = [
        #     'password',
        #     'date_created',
        #     'date_updated',
        # ]
```

定義したfilterをviewsで使用する

``` backend/src/api/users/views.py
from api.users.serializers import UserSerializer
from common.models import User
from django_filters.rest_framework import DjangoFilterBackend  # 追加
from api.users.filters import UserSearchFilter  # 追加
from rest_framework.viewsets import ModelViewSet


class UserViewSets(ModelViewSet):
    filter_backends = [DjangoFilterBackend,]  # 追加
    filter_class = UserSearchFilter  # 追加
    queryset = User.objects.all()
    serializer_class = UserSerializer
```
クエリパラメータを指定してGETできるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/63c19ab4-3e8a-8345-f1ba-371b35772249.png)




# Serializer編
## みんな大好きModelSerializer
``` backend/src/api/users/serializers.py
from rest_framework.serializers import ModelSerializer
from common.models import User


class UserSerializer(ModelSerializer):
    """ ユーザーシリアライザー
    """
    class Meta:
        model = User
        fields = '__all__'
```

## パラメータを絞りたい
groupsとかuser_permissionsとかをパラメータで投げられたくない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/953b9519-af90-2ba9-19f5-457f993cc944.png)

そんな時は`extra_kwargs`に指定します。

``` backend/src/api/users/serializers.py
from rest_framework.serializers import ModelSerializer
from common.models import User


class UserSerializer(ModelSerializer):
    """ ユーザーシリアライザー
    """
    class Meta:
        model = User
        fields = '__all__'
        extra_kwargs = {
            'is_superuser': {'read_only': True},
            'date_created': {'read_only': True},
            'date_deleted': {'read_only': True},
            'is_staff': {'read_only': True},
            'is_active': {'read_only': True},
            'groups': {'read_only': True},
            'user_permissions': {'read_only': True},
        }
```

無事、絞れました↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/24d5208d-63a1-592f-4ff4-c4fa9a02f6f2.png)

読み取り専用(read_only)にしただけなので、取得(GET)時は`extra_kwargs`に指定したものも含めてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/980f5990-3da3-ccba-cad3-bb10ff39dada.png)

## 関数/メソッドの結果をフィールドの値として返したい
例えば、リレーション先の値とかは主キーだけが入った状態です。
※下記画像`school`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/148e0c27-f709-6096-9ced-ccacb26afa69.png)

こんな時は`SerializerMethodField`が使えます。
フィールドを定義したら、`get_フィールド名`の関数を用意してあげます。

``` backend/src/api/users/serializers.py
import json  # 追加
from django.core.serializers import serialize  # 追加
from rest_framework.serializers import ModelSerializer, SerializerMethodField  # 追加
from common.models import User


class UserSerializer(ModelSerializer):
    """ ユーザーシリアライザー
    """
    school = SerializerMethodField()  # フィールド追加

    # get_フィールド名の関数追加
    def get_school(self, obj):
        """ schoolオブジェクトをJSONシリアライズしたオブジェクトを返す

        Args:
            obj (User): Userオブジェクト
        
        Returns:
            dict: UserオブジェクトをJSON形式にシリアライズしたオブジェクト
        """
        if obj.school is not None:
            data = serialize('json', [obj.school,])
            objs = json.loads(data)
            return json.dumps(objs[0]['fields'])
        return None

    class Meta:
        ...
```
さっき主キーだった`school`に、schoolオブジェクトが展開されました(. _ .)
SerializerMethodFieldを使えば割と任意の値を一緒に返せるので便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/f2a4ab2c-7800-5052-b69b-e2afb4d824f3.png)


### リレーション先のオブジェクトを返したい。でもSerializerMethodFieldよくわからん（照）
そんなワガママさんは`depth`オプションじゃ。

``` backend/src/api/users/serializers.py
from common.models import User


class UserSerializer(ModelSerializer):
    """ ユーザーシリアライザー
    """
    class Meta:
        model = User
        fields = '__all__'
        extra_kwargs = {
            'is_superuser': {'read_only': True},
            'date_created': {'read_only': True},
            'date_deleted': {'read_only': True},
            'is_staff': {'read_only': True},
            'is_active': {'read_only': True},
            'groups': {'read_only': True},
            'user_permissions': {'read_only': True},
        }
        depth = 1  # デフォルトは0なので、0を指定しても意味ない
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/6d9f1b04-0f32-9c7a-0d5b-2c220f51b08d.png)

`depth = 2`とかすると、さらに先のリレーションまで取得してくれます。（この例だと、`school`がさらにリレーションを先を持っている場合、そのオブジェクトも返してくれる）

※ただ、POSTで新規登録する場合めんどくさくなるのでやっぱり`SerializerMethodField`がオススメです。GETでしか使わない場合とかには有効かもしれない。

# djangorestframework-jwt編
## obtain_jwt_tokenのレスポンスにtokenの他にユーザー情報も含めたい
デフォルトだと`token`しか返してくれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/8359f30f-9766-81c3-f93c-4cf53fe13f4d.png)

ここにtokenの他にユーザー情報を含めたい場合は`jwt_response_payload_handler`を自作します。
jwt_utils.pyを作成（ファイル名は任意）

``` backend/src/common/jwt_utils.py
def jwt_response_payload_handler(token, user=None, request=None):
    """ JWT認証のカスタムレスポンス
    """
    return {
        'token': token,
        'first_login': user.first_login,
        'id': user.id,
    }
```

settings.pyを修正します。

``` backend/src/config/settings/settings.py
.
..
...
# JWT_AUTHを追加
JWT_AUTH = {
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'common.jwt_utils.jwt_response_payload_handler',  # JWT_RESPONSE_PAYLOAD_HANDLERに自作のjwt_response_payload_handlerを指定する
}
```

レスポンスを増やすことができました(. _ .)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/86529ccb-4571-b3c9-b422-fb91e3cc9d4c.png)


# Djangoプロジェクト, DjangoRestFrameworkの導入編
## django-environ, Djangoとdjangorestframeworkをpipインストール
```
pip3 install django-environ==0.4.5 Django==2.2.11 djangorestframework==3.11.0 mysqlclient==1.4.6
```

## プロジェクトルートとバックエンドのsrcディレクトリを作成
```
mkdir -p project_root/backend/src
```
　
下記の様なディレクトリ構成になる

```
project_root # プロジェクトルート
└── backend
    └── src
```
　
## Djangoプロジェクト作成
```
cd project_root/backend/src
django-admin startproject confing .
```

下記の様なディレクトリ構成になる（configに設定ファイルをまとめられる）

```
project_root # プロジェクトルート
└── backend
     └── src
         ├── config
         │    ├── __init__.py
         │    ├── settings.py
         │    ├── urls.py
         │    └── wsgi.py
         │
         └── manage.py
```

## 設定ファイルを開発用と本番用とで分割する
```
project_root # プロジェクトルート
└── backend
     └── src
         ├── config
         │    ├── __init__.py
         │    ├── settings
         │    │    ├── settings.py
         │    │    ├── development.py  # 開発用
         │    │    └── production.py  # 本番用
         │    │
         │    ├── urls.py
         │    └── wsgi.py
         └── manage.py
```
　
## 設定ファイル分割に伴う修正
settings.pyを修正
（BASE_DIRが`src`ディレクトリになるよう修正）

``` backend/src/config/settings/settings.py
.
..
...
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # >> /src/confing/settings
　│
　↓
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))  # >> /src
...
..
.
```

manage.pyを修正

``` backend/src/manage.py
.
..
...
def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings')
　    │
　    ↓
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings.development')  # developmentを読み込む様にする
...
..
.
```
　
デプロイを考慮してwsgi.pyを修正

``` backend/src/config/wsgi.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings')
　│
　↓
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'confing.settings.production')
```

development.pyを修正

``` backend/src/config/settings/development.py
from confing.settings.settings import *

ALLOWED_HOSTS = ['*']
```

production.pyを修正

``` backend/src/config/settings/development.py
from confing.settings.settings import *
```

## DRFを使えるようにする
settings.pyを修正

``` backend/src/config/settings.settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # 追記
]
```

## Dockerでwebとdbコンテナを立ち上げる
docker-compose.ymlとDockerfileを用意する

```
project_root # プロジェクトルート
├── backend
│    ├── src
│    │   ├── config
│    │   │    ├── __init__.py
│    │   │    ├── settings
│    │   │    │    ├── settings.py
│    │   │    │    ├── development.py
│    │   │    │    └── production.py
│    │   │    │
│    │   │    ├── urls.py
│    │   │    └── wsgi.py
│    │   |
│    │   └── manage.py
│    │
│    └── Dockerfile  # 追加
│
└── docker-compose.yml  # 追加
```

``` docker-compose.yml
version: '3'
services:
  web:
    build:
      context: ./
      dockerfile: ./backend/Dockerfile
    container_name: drf_web
    volumes:
      - './backend/src:/src'
    environment:
      - LC_ALL=ja_JP.UTF-8
    ports:
      - '8000:8000'
    depends_on:
      - db
    command: python3 manage.py runserver 0.0.0.0:8000
    restart: always
    tty: true

  db:
    image: mariadb:latest
    container_name: drf_db
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    environment:
      - MYSQL_ROOT_USER=root
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=db_drf
      - MYSQL_USER=user
      - MYSQL_PASSWORD=user
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - '3306:3306'

volumes:
  db_data:
    driver: local
```

``` Dockerfile
FROM ubuntu:18.04
RUN apt update && apt install -y locales python3-pip python3.7 python3-dev libssl-dev libffi-dev libgeos-dev libmysqlclient-dev
RUN apt-get update && apt-get install -y mysql-client python3-gdal
RUN mkdir /src \
    && rm -rf /var/lib/apt/lists/* \
    && echo "ja_JP UTF-8" > /etc/locale.gen \
    && locale-gen
WORKDIR /src
ADD ./backend/src /src/
RUN LC_ALL=ja_JP.UTF-8 pip3 install -r requirements.txt
```

Dockerコンテナ起動

```shell
docker-compose up -d
```
※もしかしたらwebコンテナがDB接続エラーで立ち上げ失敗してしまうかもしれませんが、その時は`docker-compose stop`してから`docker-compose up -d`してください。その対応が微妙だという方は`wait-for-it.sh`の導入を検討してください。

## Djangoのスタートアップページにアクセスする
この画面が出れば成功
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/482a369c-6dc2-c86c-7ce3-2e7028ec7c1a.png)

## .env.developmentファイルを作成
``` shell
cd backend/src/
touch .env.development
```

### .env.developmentファイルの内容
``` backend/src/.env.development
DEBUG=True
DATABASE_URL=mysql://user:user@db:3306/db_drf
```

### development.pyを修正
``` backend/src/config/settings/development.py
import environ

ENV_FILE = os.path.join(BASE_DIR, '.env.development')
ENV = environ.Env()
ENV.read_env(ENV_FILE)

DEBUG = ENV.get_value('DEBUG', cast=bool)
DATABASES['default'] = ENV.db()  # DB接続情報読み込み（.env.developmentのDATABASE_URLを読み込んでくれる）
...
..
.
```

## ベースモデルの実装
### baseアプリケーションを作成
```
django-admin startapp base
```

### settings.pyのINSTALLED_APPSに追記
```
INSTALLED_APPS = [
    .
    ..
    ...
    'base',
]
```

### BaseModelを記述
``` backend/src/base/models.py
from django.db import models
from django.utils import timezone

# Create your models here.
from django.db import models
from django.utils import timezone

# Create your models here.
class BaseModel(models.Model):
    """ ベースモデル
    """
    date_created = models.DateTimeField('作成日時', default=timezone.now)
    date_updated = models.DateTimeField('最終更新日時', auto_now_add=True)
    date_deleted = models.DateTimeField('削除日時', null=True)

    class Meta:
        abstract = True  # ← 必須
```

## カスタムユーザーモデルの実装
### commonアプリケーションを作成
```
django-admin startapp common
```

### settings.pyのINSTALLED_APPSに追記
```
INSTALLED_APPS = [
    .
    ..
    ...
    'base',
    'common',
]
```

### まずユーザーマネージャーを実装
manager.pyを作成

``` shell
cd backend/src/base
touch manager.py
```

``` backend/src/base/manager.py
from django.contrib.auth.models import UserManager


class UserManager(UserManager):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
    def _create_user(self, username, email, password, **extra_fields):
        """
        Create and save a user with the given username, email, and password.
        """
        if not username:
            raise ValueError('The given username must be set')
        email = self.normalize_email(email)
        username = self.model.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(username, email, password, **extra_fields)
```

### カスタムユーザーモデル実装
公式がカスタムユーザーモデルの実装を推奨しているので従いましょう(. _ .)

``` backend/src/common/models.py
from django.db import models
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from base.manager import UserManager
from base.models import BaseModel


class User(AbstractBaseUser, PermissionsMixin, BaseModel):
    email = models.EmailField('メールアドレス', blank=True, null=True)
    username = models.CharField('ユーザー名', max_length=150, unique=True)
    display_name = models.CharField('画面表示名', max_length=30, blank=True, null=True)
    last_name = models.CharField('姓', max_length=150, blank=True, null=True)
    first_name = models.CharField('名', max_length=30, blank=True, null=True)
    is_staff = models.BooleanField('スタッフフラグ', default=False)
    is_active = models.BooleanField('有効フラグ', default=True)
    first_login = models.BooleanField('初回ログイン', default=True)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    class Meta:
        verbose_name = verbose_name_plural = 'users'
        db_table = 'user'
```

### settings.pyを修正する
``` backend/src/config/settings/settings.py
.
..
...
AUTH_USER_MODEL = 'common.User'  # 認証に使用するユーザーモデルに、カスタムユーザーモデルを指定。
```

`AUTH_USER_MODEL`の指定を忘れると、以下のようなエラーで悩まされます。

```shell
python3 manage.py makemigrations
SystemCheckError: System check identified some issues:

ERRORS:
auth.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with reverse accessor for 'User.groups'.
	HINT: Add or change a related_name argument to the definition for 'User.groups' or 'User.groups'.
auth.User.user_permissions: (fields.E304) Reverse accessor for 'User.user_permissions' clashes with reverse accessor for 'User.user_permissions'.
	HINT: Add or change a related_name argument to the definition for 'User.user_permissions' or 'User.user_permissions'.
common.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with reverse accessor for 'User.groups'.
	HINT: Add or change a related_name argument to the definition for 'User.groups' or 'User.groups'.
common.User.user_permissions: (fields.E304) Reverse accessor for 'User.user_permissions' clashes with reverse accessor for 'User.user_permissions'.
	HINT: Add or change a related_name argument to the definition for 'User.user_permissions' or 'User.user_permissions'.
```

## マイグレーションファイルを生成
```
python3 manage.py makemigrations
```

## マイグレーション実行
```
python3 manage.py migrate
```