【Django】ユーザーモデルAbstractBaseUserの実装（Twitterっぽく）

## ユーザーモデルの種類

* User（標準モデル）
* AbstractUser
* AbstractBaseUser（オススメ）

## 各モデルの違い

#### 標準モデル（難易度低）
　簡単だが応用が効かない。

```models.py
from django.contrib.auth.models import User
```

#### AbstractUser（難易度中）
　フィールドのカスタマイズ（**追加・変更**）ができる。

```models.py
from django.contrib.auth.models import AbstractUser
```

#### AbstractBaseUser（難易度高）
　フィールドのカスタマイズ（**追加・変更・削除**）ができる。

```models.py
from django.contrib.auth.models import AbstractBaseUser
```

#### 公式推奨
　後でユーザーモデルを変更するのは、超大変なので最初からAbstractBaseUserを使うのがオススメだそうです。

:::note info
チュートリアル等はUser（標準モデル）でも十分です。
:::
:::note warn
初回migrate前にAbstractBaseUserを定義する
:::
:::note warn
プロジェクト開始前にどのタイプにするかよく考える事
:::

## デフォルトのフィールド

デフォルトのフィールドを参考にする。

引数はあまり気にせず、どんなフィールドが作られるのかだけを見てもらえればOKです！

この中からよく使われそうな物をピックアップして、さらに新たなフィールドを追加してみたいと思います。

ちなみに、`User（標準モデル）`と`AbstractUser`を使った場合は、以下のフィールドが作成されます。

```migtarion.py
fields=[
    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
    ('password', models.CharField(max_length=128, verbose_name='password')),
    #最終ログイン日時
    ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
    #スーパーユーザー真偽
    ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
    #ユーザーネーム
    ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
    #ファーストネーム
    ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
    #ラストネーム
    ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
    #メールアドレス
    ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
    #スタッフ権限
    ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
    #ログイン可不可
    ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
    #登録した日時
    ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
    #グループ
    ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
    #パーミッション
    ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
```

## モデルの作成
とりあえず適当に必要なフィールドを書いちゃいます。

* ユーザーネーム（Twitterの@以降を想定）
* email
* ニックネーム
* 誕生日
* プロフィール画像
* 血液型（choices=のサンプルとして）
* url
* 自己紹介文
* 登録日
* ログインの可否（管理者用）

```python:models.py
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin

#usernameでバリーデートするために入れときました（無くてもOK）
from django.core.validators import MinLengthValidator, RegexValidator

class MyUser(AbstractBaseUser, PermissionsMixin):
    BLOOD_CHOICES = [
        ("A型","A型"),
        ("B型","B型"),
        ("AB型","AB型"),
        ("O型","O型")
        ]

    username = models.CharField(verbose_name='username', max_length=10, unique=True, validators=[MinLengthValidator(5,), RegexValidator(r'^[a-zA-Z0-9]*$',)])
    email = models.EmailField(verbose_name='Email', max_length=50, unique=True)
    nickname = models.CharField(verbose_name='ニックネーム', max_length=10, blank=False, null=False)
    date_of_birth = models.DateField(verbose_name="誕生日", blank=True, null=True)
    image = models.ImageField(verbose_name='プロフィール画像', upload_to="image/", blank=True, null=True)
    blood_type = models.TextField(verbose_name="血液型", choices=BLOOD_CHOICES, blank=True, null=True)
    url = models.URLField(verbose_name='リンク', blank=True, null=True)
    introduction = models.TextField(verbose_name='自己紹介', max_length=300, blank=True, null=True)
    date_joined = models.DateTimeField(verbose_name='登録日', auto_now_add=True)
    is_active = models.BooleanField(default=True)

    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    #AbstractBaseUserにはMyUserManagerが必要
    objects = MyUserManager()
    #一意の識別子として使用されます
    USERNAME_FIELD = 'email'
    #ユーザーを作成するときにプロンプ​​トに表示されるフィールド名のリストです。
    REQUIRED_FIELDS = ['username']

    def __str__(self):
        return self.username
```

#### is_activeについて
イメージしやすいように例えるとBAN機能のようなものです。
チェックを外すとそのユーザーがログインできなくなります。

#### MyUserManager
管理者が<b>管理コマンド上</b>（ターミナル等）でユーザーを作成する時に必要です。次の項目で書きます。

#### USERNAME_FIELD
ログインする時の一意の識別子です。
Twitterであれば、emailとかusernameになりますね！

:::note info
unique=Trueのフィールドのみ設定できます。
:::

#### REQUIRED_FIELDS
管理者が<b>管理コマンド上</b>（ターミナル等）でユーザーを作成する時に表示されるリストです。

上記では'username'だけになっていますが、複数設定することも可能です。

```python:models.py
    #複数設定する場合はこんな感じ
    REQUIRED_FIELDS = ['username', 'date_of_birth']
```


## MyUserManagerの実装
前述の通り、管理者が<b>管理コマンド上</b>（ターミナル等）でユーザーを作成する時に必要なので作成したMyUser（`AbstractBaseUser`）の上に書いておく。

```python:models.py
from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, PermissionsMixin
from django.core.validators import MinLengthValidator, RegexValidator

class MyUserManager(BaseUserManager):
    def create_user(self, username, email, password=None):
        if not username:
            raise ValueError('Users must have an username')
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            username=username,
            email=self.normalize_email(email),
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, username, email, password):
        user = self.create_user(
            username=username,
            email=self.normalize_email(email),
            password=password,
        )
        user.is_admin=True
        user.is_staff=True
        user.is_superuser=True
        user.save(using=self._db)
        return user

class MyUser(AbstractBaseUser, PermissionsMixin):
    #省略
```

大体こんな感じで大丈夫！

変更するとしたら`if not hoge:`と同時に`user=`の引数くらいでしょうか？

他はコピペで大丈夫だと思います。

## デフォルトのユーザーモデルに上書き

上書きしないとエラーになるので`setting.py`に設定を書きます。

```python:setting.py
AUTH_USER_MODEL = 'アプリ名.モデルのクラス名'

#サンプル
AUTH_USER_MODEL = 'accounts.MyUser'
```

## マイグレーションしてみる
```terminal:terminal
$ python manage.py makemigrations

$ python manage.py migrate
```

## スーパーユーザーの作成
:::note warn
スーパーユーザーはmigrate後に作る事
:::

```terminal:terminal
$ python manage.py createsuperuser
```

## まとめ
`AbstractBaseUser`はかなり応用が効くみたいで、SNS認証が必要な場合にも使えます。

サービス公開を目的とする場合は是非`AbstractBaseUser`を利用してみて下さい！

応用が効きすぎて色んな設定がありますので、詳しく知りたい方は公式ドキュメントを参照してくださいね！

https://docs.djangoproject.com/en/3.2/topics/auth/customizing/