Django初心者がデフォルトのUserモデル または、CustomUserモデルを設計・作成・使用する際の諸注意

　以下Qiitaの続きです。今もPythonとJavascriptを中心に、仕事とは別に家のご飯机でゆるゆる学習を続けています。（間違いありましたらガンガン編集リクエストお願いしますm(_ _)m）
 　**なお、学習はWindowsの私用ノートPC、VScodeで基本的にしています。今いじっているDjangoはPoetry配下でver5.1.3のものを使っています。**
 
<details><summary>本Qiitaに関連するQiita（転職活動後のポートフォリオ言語移行録（Laravel⇒Django））</summary>

Portfolio移行録（Laravel 9 ⇒Django＆Poetry移行録）①～プロジェクトの準備～
(https://qiita.com/thinking-weed/items/79569252644403452d8b)

Poetry配下におけるDjangoプロジェクトのconfig（settings.py）に関する学習備忘録
https://qiita.com/thinking-weed/items/fc6eb961a63ced70893a

</details>

 　[主な参考書籍①](#主な参考書籍)を半分くらいまで読み進め、[管理ツール](#管理ツールとは)をざっと使い、基本的なCRUDをとりあえず作ってみている中でUserモデルについて諸々勝手に混乱したため個人的に整理しました。
　この記事を８０％ぐらい書いてから以下の記事を見つけ、あ～（´д｀）本当はこのぐらい簡潔にまとめられる方が良いんだろうなとグダグタ書いちゃう自分の文章力の無さに萎えているのですが消すのも勿体ないし、内容が被っていない部分もあったのでこのまま出します。

#### カスタムユーザーの作成
@Hina_Developer (Hina🐣 | 開発者)さん Qiita
https://qiita.com/Hina_Developer/items/5f3ceb173dc151c0488b

# 主旨

## （１）Djangoは Laravelとは異なり、デフォルトUserモデルの存在・居場所が分かりにくいが、プロジェクト作成段階から藪蛇のようにヒッソリ佇んでいる。

<details><summary>デフォルトUserモデルの居場所を見つけるまでの迷走</summary>

<h3>（i）「DjangoってデフォルトでUserモデル無いん？？（´д｀）？？」と勘違いして先にCustomUserモデルをいじった過程</h3>

　自分はプログラミングスクール・公共職業訓練利用からの異業種未経験転職組なのですが、プログラミングスクールのメインはLaravelでした。
（※当時はver９か10、composerとか使って雛形は作っていました。下の画像は転職活動時にLaravelで作ったポートフォリオのソースの一部です。）
 
 Laravelはプロジェクトの雛形作成段階で、**以下の場所に明示的にUser.phpがある**ので「あ～、これがUserモデルとかいうやつかぁ（´д｀）」とすぐ存在が分かります

<details><summary>LaravelのデフォルトのUserモデル</summary>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/fdaebd6e-10cc-dbc5-0322-79b8a41e1469.png)

</details>

_____

　一方、**Djangoは下記のコマンドで各機能（FlaskでいうBlueprint）にあたるアプリケーションの雛形を作った時点では、models.pyというファイルはできるものの、そいつの中には「Create your models here」とかいうコメントがあるぐらいで中身はほぼ空。初学者からすると初見全然居場所が分かりません。** （※実際はsettings.pyにすでに潜んでいたんですが(-_-)・・）

```powershell
python3 manage.py startapp new_app_name
#new_app_nameは作成するアプリ名
#python3の代わりにpythonでもいける可能性があります。
```

<details><summary>上記のコマンド前</summary>

![スライド8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/5b17788d-c3dd-7789-7fb4-4bd059f11d08.png)

**※今回PoetryをLinux（Ubuntu）に入れて、その配下でDjangoプロジェクトをいじっているのでwsl（Windows System for Linux）を使っています（以下同様）**

</details>

<details><summary>上記のコマンド後、models.pyはあるが・・・</summary>

![スライド9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/04c0118d-a288-44cf-c9a9-085f36b88ef7.png)

**※左が各アプリ（FlaskでいうBlueprint）の雛形を作ったときのmodels.py**
</details>

____

　「Django User Model」とかで調べてみても、大概「カスタムユーザー」という言葉がヒットしてデフォルトのUserモデルを使う例は見つけられませんでした。
 　結果、「Djangoは１からUserモデル作る **『しかない』** のかなぁ（´｡｀)。〇」と勝手に勘違いし、とりあえず以下の感じで**カスタムユーザーモデルを作りました。**

Djangoにおけるカスタムユーザーの作り方 @hiroki-yod (余田 大輝) in 株式会社スカラコミュニケーションズ　アライアンス開発部　さんQiita
https://qiita.com/hiroki-yod/items/21c7a3ec67c21756244e

____

## ・CustomUser実装してみたときに加筆修正または作成したファイル、フォルダ

※Djangoの基本的なフォルダ構成は、例えば以下の例のような感じになります。
(poetry配下で作っているので、本当はpyproject.tomlとかpoetry.lockとかもっと細々ありますが、そのへんは省略してあります。)
<details><summary>Djangoのフォルダ構成例</summary>

```
Djangoes(プロジェクト格納フォルダ)
   |
   |__django_portfolio（プロジェクト全体）
　　　　　　　|
　　　　　　　|--django_portfolio（プロジェクト作成時にデフォルトでできるフォルダ、プロジェクトと同名）
　　　　　　　|　　　　|--__init__.py（初期化処理を行うスクリプトファイル）
　　　　　　　|　　　　|--asgi.py（ASGIという非同期Webアプリケーションのためのプログラム）
　　　　　　　|　　　　|--settings.py（プロジェクトの設定情報を記述するファイル）
　　　　　　　|　　　　|--urls.py（プロジェクトで使うURLを管理するファイル）
　　　　　　　|　　　　|--wsgi.py（WSGIというWebアプリケーションのプログラム）
　　　　　　　|
　　　　　　　|--manage.py（このプロジェクトで実行する機能に関するコマンドなどが記述されているデフォルトであるファイル）
　　　　　　　|--sample_app1（上記コマンドでできる各機能としてのアプリ）
　　　　　　　|　　　　|--migrations（マイグレーションファイル（.py）がドンドン貯まっていくフォルダ）
　　　　　　　|　　　　|　　　|--__init__.py
　　　　　　　|　　　　|　　　|--・・・
　　　　　　　|　　　　|
　　　　　　　|　　　　|--__init__.py
　　　　　　　|　　　　|--admin.py（モデルを管理ツールに登録する等管理ツール周りをいじるためのファイル）
　　　　　　　|　　　　|--apps.py（各アプリ特有の設定を記述するファイル）
　　　　　　　|　　　　|--urls.py（アプリ毎の最終的な各エンドポイントを設定するファイル、いわゆるRouter）
　　　　　　　|　　　　|--models.py（各アプリで用いるモデルのクラス等を定義するファイル
　　　　　　　|　　　　|--tests.py（各アプリのユニットテストや統合テストを記述するためのファイル）
　　　　　　　|　　　　|--views.py（各エンドポイントにおける処理を記述するファイル、いわゆるController）
　　　　　　　|　　　　|--templates
　　　　　　　|　　　　|　　　|
　　　　　　　|　　　　|　 sample_app1（views.pyとかで処理して表示したいHTMLファイルを格納するフォルダ、各アプリと同名）
　　　　　　　|　　　　|--static
　　　　　　　|　　　　|　　　|
　　　　　　　|　　　　|　 sample_app1（各アプリで用いるJSやCSSのフォルダを格納するフォルダ、各アプリと同名）
　　　　　　　|　　　　|--management
　　　　　　　|　　　　|　　　|
　　　　　　　|　　　　|　 commands（各アプリで用いるSeederファイルなどを格納するフォルダ）
　　　　　　　|　　　　|
　　　　　　　|　　　　・・・
　　　　　　　|　　　　
　　　　　　　|--sample_app2
　　　　　　　|
       　　・・・・
```
</details>

**※なお以下、「acrobat_paro」と名付けたアプリ内に作ったCustomUserモデル関連のファイルを例に挙げていきます。**

____

<h3>① ※class User(AbstractUser)の部分：Userモデルに相当</h3>

<details><summary>models.py</summary>

```django_portfolio（プロジェクト全体）/acrobat_paro/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class Created_PDF(models.Model):
    #migrationsのinitial.pyを見ると分かるように、Djangoでは、明示的にAutoFieldを指定しなくても、
    #モデルにプライマリキーが設定されていない場合、自動的にidフィールドがAutoFieldとして作成される
    file_name = models.CharField(max_length=100,help_text="作成したPDF名")
    description = models.TextField(null=True,blank=True,help_text="作成したPDFを説明する文章（任意）")
    file_path = models.FileField(upload_to='pdfs/') #アップロード先のディレクトリを指定。この場合、MEDIA_ROOT/pdfs/にファイルが保存される
    created_at = models.DateTimeField(auto_now_add=True,help_text="作成日時")
    updated_at = models.DateTimeField(auto_now=True,null=True,help_text="更新日時")
    delete_flag = models.BooleanField(null=True,help_text="論理削除用フラッグ")

    def __str__(self):
        return f'<CreatedPdf:id={self.id}, {self.file_name}: {self.description}>'

class User(AbstractUser):
    # 必要に応じて追加フィールドを定義
    phone_number = models.CharField(max_length=15, blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    profile_image = models.ImageField(upload_to='profile_images/', blank=True, null=True)

    def __str__(self):
        return self.username
    
class Partner_Company(models.Model):
    name = models.CharField(max_length=100)
    mail = models.EmailField(max_length=200)
    address = models.TextField(blank=True, null=True)    
    phone_number = models.CharField(max_length=15, blank=True, null=True)

    def __str__(self):
        return '<Partner_Company:id=' + str(self.id) + ', ' + self.name +\
            '(' + str(self.mail) + ', ' + str(self.phone_number) + ')>'
    # 管理ツールで登録したレコードを表示するときにどのように表示されるか
```
</details>

<h3>② 上記のmodels.pyをもとに以下のコマンドで作ったマイグレーションファイル</h3>

**〇マイグレーションファイルを作るコマンド**
```powershell
python3 manage.py makemigrations アプリ名
#例えばここでは、python3 manage.py makemigrations acrobat
```

<details><summary>作成されるマイグレーションファイル</summary>

**※同じmodels.pyに書いているのにファイルが分かれているのは、どの時点でモデルclassをmodels.pyに追記してマイグレーションをしたのかが違うためです。（後に書いてマイグレーションした方ほどファイル名につく番号が大きい）**
```django_portfolio（プロジェクト全体）/acrobat_paro/migrations/0001_initial.py
# Generated by Django 3.2.12 on 2025-01-04 05:57

import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('auth', '0012_alter_user_first_name_max_length'),
    ]

    operations = [
        migrations.CreateModel(
            name='Created_PDF',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('file_name', models.CharField(help_text='作成したPDF名', max_length=100)),
                ('description', models.TextField(blank=True, help_text='作成したPDFを説明する文章（任意）', null=True)),
                ('file_path', models.FileField(upload_to='pdfs/')),
                ('created_at', models.DateTimeField(auto_now_add=True, help_text='作成日時')),
                ('updated_at', models.DateTimeField(auto_now=True, help_text='更新日時', null=True)),
                ('delete_flag', models.BooleanField(help_text='論理削除用フラッグ', null=True)),
            ],
        ),
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.BigAutoField(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')),
                ('phone_number', models.CharField(blank=True, max_length=15, null=True)),
                ('address', models.TextField(blank=True, null=True)),
                ('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_images/')),
                ('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')),
            ],
            options={
                'verbose_name': 'user',
                'verbose_name_plural': 'users',
                'abstract': False,
            },
            managers=[
                ('objects', django.contrib.auth.models.UserManager()),
            ],
        ),
    ]
```

```django_portfolio（プロジェクト全体）/acrobat_paro/migrations/0002_partner_company.py
# Generated by Django 3.2.12 on 2025-01-04 06:45

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('acrobat_paro', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='Partner_Company',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=100)),
                ('mail', models.EmailField(max_length=200)),
                ('address', models.TextField(blank=True, null=True)),
                ('phone_number', models.CharField(blank=True, max_length=15, null=True)),
            ],
        ),
    ]
```

<h3>※models.py の class Userとマイグレーションファイル0001_initial.pyのUserテーブルのフィールド（テーブルのカラムを定義するもの）を比較すると、一見どこから出てきたんやみたいなフィールドがあります。時系列的には後で気づいたのですが、これは上記のCustomUserモデルがAbstractUserを継承していることにありました。</h3>
各フィールドの詳細については以下の公式ドキュメントを参照

https://docs.djangoproject.com/ja/5.1/ref/contrib/auth/

</details>

<h3>※マイグレーション・ロールバックをするためのコマンド</h3>

**どちらもpython3 manage.py migrate（またはpython manage.py migrate）から始まることに注意**
```powershell
#マイグレーション実行
python3 manage.py migrate
#ロールバック
python3 manage.py migrate App_name File_to_delete
#App_nameはロールバックしたいモデルを含むアプリ名、File_to_deleteはどの時点まで戻りたいかを示すマイグレーションファイル名
```
また若干本Qiitaの主旨とはそれますが、**models.pyを作っていなくてもセッションを使うためのデフォルトのセッションテーブル（django_session）を作成するのに上のマイグレーション実行コマンドが必要です**

なおロールバックは微妙に面倒くさく個人的には以下サイトが参考になりました。

①【Python Django】マイグレーション(DB)をロールバック、元に戻す　@Bashi50 (ゆとり PG)さん Qiita
https://qiita.com/Bashi50/items/6d3bb8be5afb7d27b9d2

②[Django]migrateをやり直す　@HiroHika1116 (Hikaru)さん Qiita
https://qiita.com/HiroHika1116/items/6c3b34028e7d6929c839

____

<h3>※マイグレーションを実行した結果</h3>

<details><summary>作成されたテーブルなどを下記の拡張機能を通して閲覧してみた図</summary>

※この画像にはすでにレコードが入っていますが、当然それはスクショのタイミングの関係でマイグレーションした時点ではテーブル・カラムなどの「構造」しか表示されません。（またここにある名前はあくまでSeederや[管理ツール](#管理ツールとは)で入れたダミーデータです。）

![db_sqlite3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/44193a09-69a9-a5c0-488b-85e7f7286cc6.png)
</details>


　**DjangoにはデフォルトDBに SQLiteが組み込まれています。そのため、マイグレーションを実行すると、初回実行時に上図内のdb.sqlite3というファイルができます。** ただ何も設定しないままだとDBの中身を見ながらの作業がしずらいので、そのままSQLiteを用いて作業を進める場合、[管理ツール](#管理ツールとは)を立ち上げずとも、少なくとも下記の**２つの拡張ツールを入れると上記のようにVScode上で直接DBの中身がUI操作で見られるので楽です。（なお、コードをGithubで管理している場合、セキリティの都合上、個人的にはdb.sqlite3は.gitignoreに記載することを進めます）**
 
<details><summary>SQLiteをVScodeで閲覧するようにするための拡張機能</summary>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/f6ee1219-9288-1815-8bd7-c2d94180f6ec.png)

</details>

____

<h3>③ デフォルトの User モデルではなく、acrobat_paro というアプリ内の User モデルをプロジェクト全体でユーザーモデルとして使用するようにする追記</h3>

```django_portfolio（プロジェクト全体）/django_portfolio（プロジェクトと同名フォルダ）/settings.py

前略

AUTH_USER_MODEL = 'acrobat_paro.User'
```



<h3>④ @admin.register(User)（またはadmin.site.register(User)）：管理ツールに上記のUserモデルを組み込む</h3>

<details><summary>admin.py</summary>

```django_portfolio（プロジェクト全体）/acrobat_paro/admin.py
#管理ツール（superuser）で編集できるものを各アプリのここに記述
from django.contrib import admin  #Djangoの管理サイト機能を提供するモジュール
from django.contrib.auth.admin import UserAdmin  #Djangoの組み込みUserモデルを管理するための標準管理インターフェースを提供
from acrobat_paro.models import User,Created_PDF,Partner_Company


@admin.register(Created_PDF) #Created_PDFモデルをDjangoの管理サイトに登録し、以下のカスタマイズを適用
class Created_PDF_Admin(admin.ModelAdmin):
    # 管理ツール画面のリスト表示で表示するフィールドを指定
    list_display = ['id', 'file_name', 'description', 'file_path', 'created_at', 'updated_at', 'delete_flag']

@admin.register(User) #カスタムユーザーモデル（User）をDjangoの管理サイトに登録し、以下のカスタマイズを適用
class CustomUserAdmin(UserAdmin):
    model = User
    # 管理ツール画面のリスト表示で表示するフィールドを指定、管理ツール画面の並びも以下のリストに従う
    list_display = ['id',
                    'username', 
                    'email', 
                    'is_staff', #ユーザーが管理画面（Django Admin）にアクセスできるか権限があるかどうか
                    'is_active',#ユーザーアカウントがアクティブかどうか(ログインできるか否か)
                    'is_superuser'#管理者権限か否か
                ]

admin.site.register(Partner_Company)
# admin.site.registerは管理ツールに登録するメソッド
```

※list_displayの要素に入れたものが管理ツールのUIで表示されます。

</details>

**※圧倒的に管理ツールに組み込んだ方が諸々便利なように思いますが必ずしも必要ではないと思われます。今後要検証**

_____

ここまで来たら後は以下のような感じでControllerにあたるviews.pyとかテンプレートHTML内で以下のような感じで使えます。

<details><summary>views.py、テンプレートHTML</summary>

```django_portfolio（プロジェクト全体）/acrobat_paro/views.py
from django.shortcuts import render
from django.http import HttpResponse
from acrobat_paro.models import User　# 👈ココ

from django_portfolio import settings

#prefixのURLは「acrobat_paro/」

pdf_url_prefix = settings.MEDIA_ROOT
def menu_show(request):
    params = {
        'pdf_url_prefix':pdf_url_prefix
    }
    return render(request, 'acrobat_paro/menu.html', params)

def users_index(request):
    userdatas = User.objects.all() #Userの全レコードを取得する　👈ココ
    params = {
        'userdatas':userdatas
    }
    return render(request, 'acrobat_paro/users.html', params)
```

```django_portfolio（プロジェクト全体）/acrobat_paro/templates/acrobat_paro/users.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio_by_Django</title>

    <!-- BootstrapのCSS bootstrap-5.3.0-alpha1-dist/css/bootstrap.min.css -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="{% static 'acrobat_paro/css/users.css' %}">
</head>
<body class="container">
    <div class="outerwrap">
        <h1>Users</h1>
        <div class="main innerwrap">
            <table class="table">
                    <tr>
                        <th>No</th>
                        <th>USERNAME</th>
                        <th>EMAIL</th>
                        <th>LAST_NAME</th>
                    </tr>
                {% for userdata in userdatas %}　　<!-- 👈ココ -->
                    <tr>
                        <td>{{ forloop.counter }}</td> 
                        <!-- forloop.counterで１をスタートとしてループ回数を取り出す -->
                        <td>{{ userdata.username }}</td>
                        <td>{{ userdata.email }}</td>
                        <td>{{ userdata.last_name }}</td>
                    </tr>
                {% endfor %}
            </table>
        </div>
    </div>
    <footer>
        <div class="ml-4 text-left text-sm text-black-500 dark:text-gray-400 sm:text-left sm:ml-0">
            <p class="reference">　画像やアイコンは以下から引用したものをPhotoshopやIllustratorで加工したものを利用しています。</p><br>
                <ul>
                    <li>unsplash　(<a href="https://unsplash.com/ja">https://unsplash.com/ja</a>)</li>
                    
                    <li>Font Awesome　(<a href="https://fontawesome.com/search?o=r&m=free">https://fontawesome.com/search?o=r&m=free</a>)</li>
                </ul>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
```
</details>

____

<h2>（ii）デフォルトUserモデルの存在に気づいた過程</h2>

　少し話は逸れますが、現在の常駐先は「システム」エンジニアだけがいるわけではなく、冒頭にも書いたようにOJTでPythonのプログラミングを学ぶことはありません。そのため（？）基本的に学習の初期段階のキャッチアップはChat-GPT大先生を主に使っています。

**その中でプロンプトを見返したときに自分の勘違いの違和感に気づきました。**

<h3>そのときのpromptの一部</h3>

![スライド1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/a0fd687b-c33d-5051-2a61-a07ff454de39.png)
カスタムユーザーモデルの設定ばかりに目が行っていたけど拡張してるってことはやっぱりどっかにデフォルトUserモデルがいる(?_?)

![スライド2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/a41f9909-ca32-1a8c-a5c8-1230af5df012.png)

![スライド3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/e3ad7e41-4c59-a644-8459-74405ed6007d.png)
あーこいつかぁ（´д｀）その後、次のようにプロンプトを打ってみました。

![prompt_about_Usermodel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/c2557ce4-ad56-9be3-3dfd-a78739a560d9.png)

ということで次に公式ドキュメントの以下の「Django の認証方法のカスタマイズ」というセクションに目を通してみると答えが見つかりました。
https://docs.djangoproject.com/ja/5.1/topics/auth/customizing/
</details>

## デフォルトのUserモデルは結局どこにいた？？

結論として、以下の「Django の認証方法のカスタマイズ ( https://docs.djangoproject.com/ja/5.1/topics/auth/customizing/ )」というセクションのDjangoの公式ドキュメントに答えがありました。（**※多少は言語間でニュアンスなどの誤差出るとは思うのですが、Djangoの公式ドキュメントは最新版も右下で言語を切り替えられるようです。**)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/bff979c2-57e2-8f7d-0bbe-dc5c57953aaf.png)

上記の画像の

```python
from django.contrib.auth.models import User
```
をヒントに「**django.contrib.auth**」をVScodeの検索にかけると・・・

![django_contrib_auth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/288880d6-cc00-6583-8bf8-534ad3daf912.png)
INSTALLED_APPSのリストに実はいたということはアプリなん？（´д｀）？

結果、ここからさらに調べて見たところ、
<h3>デフォルトのUserモデルはDjango の組み込みアプリケーションとかいうやつ、具体的には Django の認証システム (authentication system) を提供するモジュール</h3>

でした。いやぁでも初学者的にはまさか、
<h3>settings.pyの１行でデフォルトのUserモデルの定義してる</h3>

とか思わんやろ・・しかも**～.auth**からとってくるとか・・
「auth」って一般には認証とか管理者権限ユーザー周りやろ（´д｀）
まぁでも、CustomUserモデルで継承したAbstractUserとかいうやつもここから取ってたわ(-_-;)

_____

　もうCustomUserモデルをある程度実装してしまったので、商用でもないし以降はとりあえず上記のCustomUsermodel進めようと思っているのですが、仮にデフォルトのUserモデルを使う場合は例えば上記公式docにあるように

```python
from django.contrib.auth.models import User
```
と書き始めて使っていくようです。

※まだほんの少ししか読めていないのですが「Django の認証方法のカスタマイズ」以外にUserモデル関係だと以下のセクションが重要そうでした。

①「django.contrib.auth」(https://docs.djangoproject.com/ja/5.1/ref/contrib/auth/#django.contrib.auth.models.User)
②「Djangoの認証システムを使用する」(https://docs.djangoproject.com/ja/5.1/topics/auth/default/)

____

### 思い返すと予兆はあった

[主な参考書籍①](#主な参考書籍)でCRUDの学習に入る前、**管理ツールを使うのにまず以下のコマンドで管理者権限ユーザーを設定したのですが、自分の記憶が定かであれば、「auth_users」というテーブルができ、その中に登録した管理者権限ユーザーのレコードがいました。**

※その時点ではカスタムユーザーモデルを定義していない、デフォルトのユーザーモデル（の居場所）が分からない状態でしたが、セッションを使ってみるのにマイグレーション実行コマンドを試していました。（詳しくは冒頭の折りたたみ「デフォルトUserモデルの居場所を見つけるまでの迷走」内）

```powershell
python3 manage.py createsuperuser
```
**※これを打つと途中でメールアドレスを聞かれますが、.gitignoreとかの設定面倒なので、あくまで仮のメールアドレスを登録することをお勧めします（嘘のアドレスでも十分使えます）**

なお、冒頭の折りたたみにあるように**CustomUserモデルを作って[管理ツール](#管理ツールとは)に組み込むと、以下のように管理者権限ユーザーに関するレコードはアプリ内（今回の場合「acrobat_paro」）のユーザーというテーブル内にいます。**

<details><summary>CustomUserモデル設定後、管理者権限ユーザーを管理ツール内で探してみた様子</summary>

![superuser1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/2c4dc0d5-131e-d760-c1a4-9c7f7e851b6d.png)

![superuser2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/1dd7a9fb-3fb0-5b73-1da8-9ddae4596244.png)


※これはあくまで以下のように追記していたことによると思われます。

```django_portfolio（プロジェクト全体）/django_portfolio（プロジェクトと同名フォルダ）/settings.py

前略

AUTH_USER_MODEL = 'acrobat_paro.User'
```
</details>

____
## （２）DjangoでUserモデルを使う場合、デフォルトUserモデルとCustomUserモデルを併用することは可能か？

自分が調べた限りでは **「併用」は無理**そうでした。（あるのかもしれないけど少なくとも今の自分の技術力では無理）。（途中、Laravel学習時 Userモデルをプロジェクト内に何故か２つ作ってしまい度々エラーになったことを思い出しました（－＿－；））

**ただし、**[上記のQiita](#カスタムユーザーの作成)や公式「Django の認証方法のカスタマイズ ( https://docs.djangoproject.com/ja/5.1/topics/auth/customizing/ )」の以下の部分にあるように、**AbstractUserモデルを継承してCustomUserモデルを作れば、ほぼデフォルトのUserモデルを踏襲している形になる**ようです。

![About_AbstarctUser.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/9c43f753-24eb-7f8b-b397-599b49cb3a52.png)

### それでもどうしてもAbstractUserではなくデフォルトのUserに＋＠する形にしたい場合

「OneToOneField」というフィールド・「ForeignKey」というのがどうも以下のように使えそうです。

<details><summary>詳細</summary>

### ① OneToOneField：新規作成するモデルが既存のあるモデルと１対１の関係を持つようにするためのフィールド

公式doc( https://docs.djangoproject.com/ja/5.1/topics/db/examples/one_to_one/ )

例えば、
```models1.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    # 必要に応じて追加フィールドを定義
    phone_number = models.CharField(max_length=15, blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    profile_image = models.ImageField(upload_to='profile_images/', blank=True, null=True)

    def __str__(self):
        return self.username
        # 管理ツールで登録したレコードを表示するときにどのように表示されるか
```
に意味合いとしては似たものをOneToOneFieldを使って作る場合
```models2.py
from django.db import models
from django.contrib.auth.models import User

class CustomUser(User):
    #標準のUserモデルを継承
    pass

#プロファイル情報を以下のクラスで追加
class UserProfile(models.Model):
    user = models.OneToOneField(CustomUser, #CustomUserと１対１で紐付け、user.～で参照できるようにする
                  on_delete=models.CASCADE, #ユーザーが削除されたとき、関連するプロファイルも削除されるようにする
                  related_name='profile' # 逆参照の名前を定義。この場合user.profileでプロファイル情報にアクセスできる
                  )
    phone_number = models.CharField(max_length=15, blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    profile_image = models.ImageField(upload_to='profile_images/', blank=True, null=True)
    
    def __str__(self):
        return self.user.username
         # 管理ツールで登録したレコードを表示するときにどのように表示されるか
```
※後者の場合、標準のUserモデルをそのまま利用しており、標準の認証（・認可）システムとの互換性が高いが、**マイグレーションしたときにテーブルがUserとUserProfileの２つに分かれる。**

_____

### ② ForeignKey：既存のあるモデルを「１」としたときに新規作成するモデルが「多」となるような、多対１のリレーションシップを定義するのに必要とする

公式doc ( https://docs.djangoproject.com/ja/5.1/topics/db/examples/many_to_one/ )

上記のmodel1.py、model2.pyに近いUserモデルをForeignKeyを使って定義する場合
```models3.py
from django.db import models
from django.contrib.auth.models import User

class CustomUser(User):
    pass

class UserProfile(models.Model):
    user = models.ForeignKey(CustomUser,  # ForeignKeyで関連付け
                             on_delete=models.CASCADE,  # 関連するユーザーが削除されたときプロファイルも削除
                             related_name='profiles')  # 逆参照でアクセス（多対1で名前が複数形になることに注意）
    phone_number = models.CharField(max_length=15, blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    profile_image = models.ImageField(upload_to='profile_images/', blank=True, null=True)
    class Meta:
        unique_together = ('user',)  # 1人のユーザーに対して1つのプロファイルだけ許可
    
    def __str__(self):
        return self.user.username
     # 管理ツールで登録したレコードを表示するときにどのように表示されるか
```
**※OneToOneFieldとForeignKeyどちらを使うかはモデル間の意味やデータ構造を考えて判断**

**※プロファイル情報が1人のユーザーに対して複数存在する可能性がある場合（例: 複数の住所や電話番号を管理する場合）**

```python
    class Meta:
            unique_together = ('user',)  # 1人のユーザーに対して1つのプロファイルだけ許可
```
を外す
</details>

## （３）どこのmodels.pyにユーザーは置くべきか

調べる限り、**特定のアプリ内でユーザーを使用する場合**は、[冒頭の折りたたみのデフォルトUserモデルの居場所を見つけるまでの迷走](#主旨)で作ったようにどこかのアプリ（今回ならacrobat_paro）内のmodels.pyでよいが

一方、

### プロジェクト全体で共通の認証モデルとして使用する場合、以下のような感じでUserや認証・認可システム周り専用のアプリを作る

のが最適解そう（**他のアプリに依存させないことで、独立性が高まり管理しやすくなる**）

<details><summary>ここにCustomUserモデルのクラスを含む</summary>

旧

```
Djangoes(プロジェクト格納フォルダ)
   |
   |__django_portfolio（プロジェクト全体）
　　　　　　　|
　　　　　　　|--django_portfolio（プロジェクト作成時にデフォルトでできるフォルダ、プロジェクトと同名）
　　　　　　　|　　　　|--__init__.py（初期化処理を行うスクリプトファイル）
　　　　　　　|　　　　|--asgi.py（ASGIという非同期Webアプリケーションのためのプログラム）
　　　　　　　|　　　　|--settings.py（プロジェクトの設定情報を記述するファイル）
　　　　　　　|　　　　|--urls.py（プロジェクトで使うURLを管理するファイル）
　　　　　　　|　　　　|--wsgi.py（WSGIというWebアプリケーションのプログラム）
　　　　　　　|
　　　　　　　|--manage.py（このプロジェクトで実行する機能に関するコマンドなどが記述されているデフォルトであるファイル）
　　　　　　　|--sample_app1（上記コマンドでできる各機能としてのアプリ）
　　　　　　　|　　　　|--migrations（マイグレーションファイル（.py）がドンドン貯まっていくフォルダ）
　　　　　　　|　　　　|　　　|--__init__.py
　　　　　　　|　　　　|　　　|--・・・
　　　　　　　|　　　　|
　　　　　　　|　　　　|--__init__.py
　　　　　　　|　　　　|--admin.py（モデルを管理ツールに登録する等管理ツール周りをいじるためのファイル）
　　　　　　　|　　　　|--apps.py（各アプリ特有の設定を記述するファイル）
　　　　　　　|　　　　|--urls.py（アプリ毎の最終的な各エンドポイントを設定するファイル、いわゆるRouter）
　　　　　　　|　　　　|--models.py（各アプリで用いるモデルのクラス等を定義するファイル
　　　　　　　|　　　　|--tests.py（各アプリのユニットテストや統合テストを記述するためのファイル）
　　　　　　　|　　　　|--views.py（各エンドポイントにおける処理を記述するファイル、いわゆるController）
　　　　　　　|　　　　|--templates
　　　　　　　|　　　　|　　　|
　　　　　　　|　　　　|　 sample_app1（views.pyとかで処理して表示したいHTMLファイルを格納するフォルダ、各アプリと同名）
　　　　　　　|　　　　|--static
　　　　　　　|　　　　|　　　|
　　　　　　　|　　　　|　 sample_app1（各アプリで用いるJSやCSSのフォルダを格納するフォルダ、各アプリと同名）
　　　　　　　|　　　　|--management
　　　　　　　|　　　　|　　　|
　　　　　　　|　　　　|　 commands（各アプリで用いるSeederファイルなどを格納するフォルダ）
　　　　　　　|　　　　|
　　　　　　　|　　　　・・・
　　　　　　　|　　　　
　　　　　　　|--sample_app2
　　　　　　　|
       　　・・・・
```


新

```
Djangoes(プロジェクト格納フォルダ)
   |
   |__django_portfolio（プロジェクト全体）
　　　　　　　|
　　　　　　　|--django_portfolio
　　　　　　　|　　　　|--settings.py（プロジェクトの設定情報を記述するファイル）
　　　　　　　|　　　　|--urls.py（プロジェクトで使うURLを管理するファイル）
　　　　　　　|　　　・・・
　　　　　　　|
　　　　　　　|--manage.py
　　　　　　　|--sample_app1（これがacrobat_paroでここのmodels.pyに元々置いてしまっていた）
　　　　　　　|--sample_app2
　　　　　　　|
       　　・・・・
　　　　　　　|--accounts/  # Userや認証・認可システム周り専用のアプリ　👈
　　　　　　　|　　　　|--migrations/
　　　　　　　|　　　　|--__init__.py
　　　　　　　|　　　　|--admin.py # CustomUserの管理ツールへの登録などもここへ移行　👈
　　　　　　　|　　　　|--apps.py
　　　　　　　|　　　　|--models.py  # CustomUserモデルをここに記述　👈
　　　　　　　|　　　　|--tests.py
　　　　　　　|　　　　|--views.py
```

</details>


## （４）設計段階では作成する物に認証 (authentication) 機能または認可 (authorization) 機能（※下記参照）を付与するかどうかをよく考えて早めに設計の方針を固めた方がよさそう

　とりあえず今回、**Djangoに組み込まれている認証・認可周りの機能、それこそデフォルトの管理ツールなどを流用して開発を進めたいか否か**がDjangoプロジェクトにおけるUserモデルの設計の指針になるように感じました。（X等を見ていると、自分でオリジナルの管理ツール的なものを作っている、超つよつよの方もいらっしゃいますが・・（´д｀））

例えば
・認証システム周りを使わない簡易的なものを設計している場合⇒デフォルトUserそのまま
・一部のユーザーしかアクセスできないようなアプリを作りたい⇒デフォルトの管理ツールとかを流用できる形でCustomUserモデルを実装

____

※公式Docの「Djangoの認証システムを使用する」( https://docs.djangoproject.com/ja/5.1/topics/auth/default/ )の冒頭に
<h4>Django authentication provides both authentication and authorization together and is generally referred to as the authentication system, as these features are somewhat coupled.<br>（Djangoの認証は、認証 (authentication) 機能と認可 (authorization) 機能の両方を提供しています。そして、一般的に、これらの機能を合わせて認証システムと呼びます。）</h4>

とあります。

_____
## 管理ツールとは

　Djangoにデフォルトで組み込まれている、以下のような**Djangoプロジェクト内のCRUDを管理者権限をもつ開発者がUI操作できるようになるDB管理システム**です。

<details><summary>管理ツールにアクセスした様子</summary>

※デフォルトの場合、開発段階では開発用サーバーを起動すると http://127.0.0.1:8000/ というURLが出てくると思うのですが、これに＋＠して http://127.0.0.1:8000/admin というURLにアクセスすると以下の画面が表示されます。

![スライド1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/e5d352e2-5f99-369b-0b86-ffdeeda8ba64.png)

上記の[管理者権限ユーザーを作るコマンド](#思い返すと予兆はあった)入力時求められる管理者権限のユーザー名とパスワードを入力すると以下の画面に遷移

![スライド2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/ac984695-afc6-9db9-cc40-94fe8a1891b3.png)

※右上の追加のボタンを押すとこんな感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3584834/2cb54f9d-6360-d994-56c2-fed21707ff46.png)


</details>


____

※上記の[管理者権限ユーザーを作るコマンド](#思い返すと予兆はあった)で管理者権限ユーザーを作る等いくつかの設定をして初めて使えるようになります。（設定の詳細は例えば上記の折りたたみ「デフォルトUserモデルの居場所を見つけるまでの迷走」を参照）

※なお、１つ１つ管理ツールでデータを増やすのが面倒だったので調べたところ、以下のように**faker（とfactory-boy）というライブラリーを使うと、簡単にLaravelでいうところのSeeder（とFactory）がUserモデルに限らず他のクラスでも結構簡単に組めました。**（このDjangoプロジェクトに組み込んでみた様子は今後別途書いて紐付けます）

Python で Faker をカスタムして独自のダミーデータを生成する方法　@Tadataka_Takahashi さん Qiita
https://qiita.com/Tadataka_Takahashi/items/75f8023b004e8cc126a3

factory_boyを使ってテストコードを作成しよう！　@shun198 さん Qiita
https://qiita.com/shun198/items/80bd2a79e483e7a72c6d



## 主な参考書籍

①Python Django 4 超入門　掌田津耶乃 (著)　秀和システム
<details><summary>Amazonリンク</summary>

https://www.amazon.co.jp/Python-Django-4-%E8%B6%85%E5%85%A5%E9%96%80-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83/dp/4798062413/ref=sr_1_2_sspa?crid=MZGZWVELQ6MA&dib=eyJ2IjoiMSJ9.eEG-HVhGiMXwCJ6xOkfrdY5LDwm8oswFB-HEsTcpi7tolQxne-QjUkBh47VWZQ3x4gpZLBuD4C4SqnayOqtCyOBniyYUG3bXWPEWSZmAj1fIy7ZbHDamJ4_lHsR9wXue9mv-YjuM8yXSL0seZrmHPABx8VgWAMcPo71jJeFlQ4DxWIqgTx3W9XULh4FOyhfMkpoRwry1MlAttgFF1WWYY7EjKvJzQn2oP-fSodDEL7TDRVZ0gVNFU2d8wOckHko20_hKYcHBKAlv61MLnPOC6KB9bd6IXk1Zhp75Y2V6lnY.cYHcC9HbRGJ5EP_NWX1lkmumKLak2nM1yfP1DvKPD4A&dib_tag=se&keywords=python+django+4+%E8%B6%85%E5%85%A5%E9%96%80&qid=1735366082&sprefix=Python+Dj%2Caps%2C262&sr=8-2-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&psc=1
</details>

②Python FlaskによるWebアプリ開発入門 物体検知アプリ&機械学習APIの作り方 　佐藤 昌基 ・ 平田 哲也 (著), 寺田 学 (監修)　翔泳社

<details><summary>Amazonリンク</summary>

https://www.amazon.co.jp/Python-Flask%E3%81%AB%E3%82%88%E3%82%8BWeb%E3%82%A2%E3%83%97%E3%83%AA%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80-%E7%89%A9%E4%BD%93%E6%A4%9C%E7%9F%A5%E3%82%A2%E3%83%97%E3%83%AA-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92API%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9-%E4%BD%90%E8%97%A4/dp/4798166464
</details>