アプリ開発ナレッジ

アプリ開発のナレッジを掲載します

マッチングアプリを個人開発する~Djangoバックエンドその2 モデル作成~

この記事はマッチングアプリを自作してみようと試みている記事です
バックエンドを実装しています


前回の記事はこちらです
shinseidaiki.hatenablog.com


前回はDjangoの導入まで終わりました
次にモデルを作成していきましょう

目次

アーキテクチャ設計

さて、続きのAPIを作成する前に前回のガバガバなシステム構成図ではバックエンドの中身をどう作るのかについて全く未検討だったのでここでバックエンド側のアーキテクチャ設計を行います
具体的には、画面遷移図、DB設計、Model設計、View設計、RestAPI URI設計です
なお、Djangoにはコードを元にER図などのドキュメンテーションを作成してくれる機能があるため、ここでは簡単な絵図だけを描くことにします

画面遷移図

画面遷移図はバックエンドのアーキテクチャの設計のイメージがつきやすくなるためここで簡略図を作成する
f:id:shinseidaiki:20220206233859p:plain

ER図

ざっくりとテーブルのリレーションがわかるだけの簡略図を作ります
f:id:shinseidaiki:20220207101401p:plain


ユーザーの実装はDjangoにUserモデルが標準装備されているので、そのUserモデルをカスタマイズすることで実装するのが一般的となっている
その際、基本情報はUserモデルのフィールドとして持ち、nicknameなど新たに持たせたいフィールドは1対1のリレーションで新たに作成したProfileモデルに設定するのが一般的である
またマッチングクラスではいいねの送信者と受信者が双方向にいいねを送り合ったときに双方のapprovedをtrueに変更しマッチングとする設計にする
メッセージモデルに関しては単にUserモデルと1対多の関係性をとるのみとし、マッチングしているユーザー同士のみがメッセージのやり取りができる機能に関してはView側で実装していく
各モデルの他のフィールド(others......)に関しては実装時に具体的にしていく

RestAPI 設計

次にバックエンドで作成するRestAPIの機能とそのURIエンドポイントを設計します
原則としてREST APIはすべてログイン中のユーザーのみが操作できるようにして、それ以外のアクセスは拒否します

エンドポイント 機能内容
/api/authen/ ユーザー認証に利用される
/api/users/create/ ユーザーを作成することができる
/api/users/{pk}/ 自分自身のユーザー情報を取得・編集できる。他人のユーザー情報は取得・編集できない
/api/users/profile/{pk} 自分自身のプロフィール情報を作成・取得・編集できる。他人のプロフィール情報は取得・編集できない
/api/profiles/ 自身のプロフィールを作成したり、異性のプロフィール一覧を取得することができる。自分のプロフィールがない状態ではプロフィール一覧を取得することはできない
/api/favorite/ いいねを送ったユーザといいねをくれたユーザ一覧を取得したりいいねを送ったり、いいねを承認したりすることができる
/api/dm-message/ ユーザー自身が送信したDMの一覧を取得する。新たにメッセージを送信することができる
/api/dm-inbox/ ユーザー自身が受信したDMの一覧を取得する

Djangoの初期設定~設定項目の編集~

Djangoを本格的に開発していくためにはsettings.pyの設定を編集していくことが欠かせませんので、これから開発にあたって必要となってくる設定をここで初めにしておきます

プロジェクト構成に合わせて空フォルダを作成する

以下のようなフォルダ構成のフォルダとファイルを作成する
matchingappapi(プロジェクトルート)
┗ secrets - オープンソースなどでソースコードを外部公開する際に秘匿にしておく情報を扱う
 ┗ .env.dev
 ┗ .env.prod
┗ media - 開発環境のみでクライアントとの画像処理に使用する
┗ .gitignore - githubにアップロードしないファイルを定義する

シークレット情報の環境変数

上記で作成した.envファイルに環境変数を記載する

.env.dev(開発環境)

SECRET_KEY=django-xxxxxxxxxxxxxx
DEBUG=True
ALLOWED_HOSTS=*
CORS_ORIGIN_WHITELIST=http://localhost:3000
DATABASE_URL=sqlite:///db.sqlite3

.env.prod(本番環境 - デプロイする場合)

SECRET_KEY=django-xxxxxxxxxxxxxx
DEBUG=False
ALLOWED_HOSTS=[特定のホスト]
CORS_ORIGIN_WHITELIST=[特定のホスト]
DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME

なおこの時環境変数に格納する値はクオテーションなしで登録する

DjangoはデフォルトでSQLiteを使用しているため、.env.devではSQLiteを使用することにする
また本番環境ではPostgresを使用するため.env.prodではPostgressを使用する設定とする
大文字になっている箇所は任意の名前を入力する
ちなみに余談ですがHerokuではデプロイした先でデフォルトで環境変数にDATABASE_URL=Postgresのパスが設定されているみたいです

setting.pyの編集

アプリケーション適用

まずはプロジェクトにアプリケーション(basicapi)を適用します
settings.py

INSTALLED_APPS = [
    'basicapi.apps.BasicapiConfig',
]
環境変数適用

次にsetting.pyに先ほど.envに外部ファイル化した環境変数を適用していきます
setting.py

import environ

env = environ.Env()
root = environ.Path(os.path.join(BASE_DIR, 'secrets'))

# 本番環境
# env.read_env(root('.env.prod'))

# 開発環境
env.read_env(root('.env.dev'))


SECRET_KEY = env.str('SECRET_KEY')

DEBUG = env.bool('DEBUG')

ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])

rootに関してはsecretsフォルダを作成したことで変数化しており、プロジェクト直下に配置する構成の場合は記載する必要はなくなる
なお次のような書き方もある
root = environ.Path(BASE_DIR / 'secrets')

パッケージ適用

次に最初にインストールしたpythonパッケージをこれから作るアプリに適用していきます
setting.py

import os
from datetime import timedelta

INSTALLED_APPS = [
    'rest_framework',
    'corsheaders',
    'djoser',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
]

CORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST', default=[])

CORS_ORIGIN_WHITELIST はフロントエンドからのアクセスを許可するためのホワイトリスト設定で、SIMPLE_JWT は認証トークンに使用するJWTの設定です

アクセス制限の適用

次に、バックエンドをREST API化したときのセキュリティの設定としてAPIの利用はデフォルトで認証された(ログイン中)ユーザーのみに制限します
また認証にはJWTを利用することにしており、Tokenの有効期限を1日に設定しています
settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=1440)
}

Djangoの設計自体は認証に関する権限に関してはViewに各画面毎に定義することになっているが、ベストプラクティスとしては基本となる認証ロジックに関してはsetting.pyに記述して差分をViewで適用する方法の方が良い実装だとされている模様で、本アプリではベストプラクティスに沿った実装を行おうと思います

DB設定

次に、Databaseの設定を行います
デフォルト(ローカル)ではsqliteを使用して、本番環境ではPOSTGRESを使用する構成にします
django-environのパッケージを使用すると環境変数でDBも取り扱えるようになる
環境変数にDATABASE_URLが記載されている場合そちらを優先して使用され、DATABASE_URLが無記載の場合はenv.db()がDjangoデフォルトのSQLiteを設定してくれます
settings.pyを以下のように書き換えます
settings.py

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': BASE_DIR / 'db.sqlite3',
#     }
# }

DATABASES = {
    'default': env.db(),
}
カスタムユーザー適用準備

次に、今後Djangoで扱うユーザーはカスタムユーザーを使用していくので、ユーザーの設定を記載します
settings.py

AUTH_USER_MODEL = 'basicapi.User'
ロケーション変更

次に、ロケーションを日本に変更します
settings.py

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'
StaticRootの設定

次に、静的ファイルの配信最適化をする設定を行います
本番環境(Heroku)にデプロイする際にばらばらに配置されている静的ファイルを指定した一つのフォルダにまとめてくれます
settings.py

STATIC_ROOT = str(BASE_DIR / 'staticfiles')
Mediaファイルの取り扱い設定

次に、静的ファイルを取り扱えるようにmediaの設定をします
こちらも環境変数化し、本番環境と開発環境を切り分ける必要があるが後々行うこととします
settings.py

# 開発環境
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

またmediaの設定はurls.pyも編集が必要となるので、忘れないうちに以下のコードを追加しておきます
urls.py

from django.conf.urls.static import static
from django.conf import settings

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

余談として、デフォルトで記述されているTEMPLATES は本アプリでは使用しないためsetting.pyから削除してもよいはずです

URLの設定

プロジェクト側urls.pyの設定

後でもいいですが、ここでルーティングのガワを決めておきます
matchingappapi/urls.py

from django.conf.urls import include

urlpatterns = [
    path('api/', include('basicapi.urls')),
    path('authen/', include('djoser.urls.jwt')),
]
アプリ側urls.pyの設定

basicapi直下にurls.pyを作成してアプリのルーティングのガワを記載しておきます

basicapi/urls.py

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

router = DefaultRouter()

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

.gitignoreの編集

さて上記の設定作業では外部に流出したくないファイルを作成したので、そのファイルたちはGitHubにアップロードしないように設定したいので、.gitignoreを編集していきます
現時点では以下のあたりを記述しておく

.gitignore

basicapi/__pycache__/
basicapi/migrations/__pycache__/
matchingappapi/__pycache__/
media/
secrets/
db.sqlite3


こちらで初期設定はあらかた完了です

Modelの実装

それでは本題です
モデルを実装していきましょう

カスタムユーザーの実装

まずはカスタムユーザーを実装していきます
Djangoのドキュメントによると最初のMigrationでカスタムユーザーを使用しないとデフォルトユーザーを使用することになってしまい、さらにドキュメントにはカスタムユーザーの使用を推奨すると記載されているので、実質Djangoの初期作業となっています

カスタムユーザーの作り方としてはAbstractUserを継承する方法と、AbstractBaseUserを継承する方法の2種類がある

  • AbstractUser: すでに存在するフィールドをそのまま流用して、フィールドの増減を行うことができる

ただし、メールアドレスをログインIDにするなどのことができないため、限定的な利用にとどまる

  • AbstractBaseUser: Userモデルをほとんどゼロベースから作成することが可能

自由度が高くこちらをカスタムユーザーとするのが一般的で本アプリでもAbstractBaseUserでカスタム実装していきます

それではアプリのモデルを編集します
basicapi/models.pyを編集します

ユーザーマネージャーの実装

AbstractBaseUserを使用する場合はデータ挿入取得更新処理をするマネージャークラスを作成する必要があるのでマネージャークラスをまずは作ります
Djangoのデフォルトではユーザー名がログインに使用されますが、本アプリのカスタムユーザーはemailをユーザー名として利用するようにします

models.py

from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):

    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Users must have an email address')

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

        return user

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

        return user
ユーザークラスの実装

ユーザーマネージャーが作成できれば次はユーザークラスを作成します
models.pyにユーザークラスを追記します

models.py

from django.contrib.auth.models import AbstractBaseUser,  PermissionsMixin
import uuid

class User(AbstractBaseUser, PermissionsMixin):

    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    email = models.EmailField(max_length=255, unique=True)
    username = models.CharField(max_length=255, blank=True)
    is_active = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'

    def __str__(self):
        return self.email

idにはuuidを使用しています
is_staffは管理者画面にアクセスできる権限を付与してしまうので、基本デフォルトでFalseにします
またマッチングアプリの利用者は年齢確認をしなければならないという制約があるので、アカウントの有効化はクレジットカード決済が有効だったユーザーのみに限定したいのでデフォルトでis_activeをFalseにします
アカウントの有効化機能はREST APIを作成する中で作成していきます

Profileクラスの実装

カスタムユーザーを作成する場合は通常セットで作成されるクラスです
nicknameなどの新たなフィールドをユーザークラスに追加したいが、ユーザークラスを煩雑にしたくないケースに頻繁に利用されます
カスタムユーザーを作成するときにはProfileクラスをセットで作成するのがデファクトスタンダードになっているといっても遜色ないかと思います
Userと1対1の関係をとるのでOneToOneFieldを利用する

マッチングアプリは性別や年齢が大切になってくるので必要な最低限の項目を追加したものをまずは作ります

models.py

from django.conf import settings
from django.core.validators import MinValueValidator, MaxValueValidator


class Profile(models.Model):

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE, related_name='profile')
    is_kyc = models.BooleanField("本人確認", default=False)
    nickname = models.CharField("ニックネーム", max_length=20)
    created_at = models.DateTimeField("登録日時", auto_now_add=True)
    updated_at = models.DateTimeField("更新日時", auto_now=True, blank=True, null=True)
    age = models.PositiveSmallIntegerField(
        "年齢", validators=[MinValueValidator(18, '18歳未満は登録できません'),
                          MaxValueValidator(100, '100歳を超えて登録はできません')])
    SEX = [
        ('male', '男性'),
        ('female', '女性'),
    ]
    sex = models.CharField("性別", max_length=16, choices=SEX)
    introduction = models.TextField("自己紹介", max_length=1000)

    def __str__(self):
        return self.nickname

次に既存のアプリでは他の項目もたくさんあるので、主にPairsを参考にその他のフィールドも追加していきます
プロフィールクラスにさらに項目を追加した結果が以下です
一気にコードが増えました

models.py

from datetime import datetime, timedelta


def top_image_upload_path(instance, filename):
    ext = filename.split('.')[-1]
    return '/'.join(['images', 'top_image', f'{instance.user.id}{instance.nickname}.{ext}'])


class Profile(models.Model):

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE)

    """ Profile Fields """
    is_special = models.BooleanField(verbose_name="優良会員", default=False)
    is_kyc = models.BooleanField(verbose_name="本人確認", default=False)
    top_image = models.ImageField(
        verbose_name="トップ画像", upload_to=top_image_upload_path, blank=True, null=True)
    nickname = models.CharField(verbose_name="ニックネーム", max_length=20)
    created_at = models.DateTimeField(verbose_name="登録日時", auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name="更新日時", auto_now=True, blank=True, null=True)

    """ Physical """
    age = models.PositiveSmallIntegerField(
        verbose_name="年齢", validators=[MinValueValidator(18, '18歳未満は登録できません'),
                          MaxValueValidator(100, '100歳を超えて登録はできません')])
    SEX = [
        ('male', '男性'),
        ('female', '女性'),
    ]
    sex = models.CharField("性別", max_length=16, choices=SEX)
    height = models.PositiveSmallIntegerField(
        verbose_name="身長", blank=True, null=True,
        validators=[MinValueValidator(140, '140cm以上で入力してください'),
                    MaxValueValidator(200, '200cm以下で入力してください')])

    """ Environment """
    LOCATION = [
        ('hokkaido', '北海道'),
        ('tohoku', '東北'),
        ('kanto', '関東'),
        ('hokuriku', '北陸'),
        ('chubu', '中部'),
        ('kansai', '関西'),
        ('chugoku', '中国'),
        ('shikoku', '四国'),
        ('kyushu', '九州'),
    ]
    location = models.CharField(verbose_name="居住エリア", max_length=32, choices=LOCATION, blank=True, null=True)
    work = models.CharField(verbose_name="仕事", max_length=20, blank=True, null=True)
    revenue = models.PositiveSmallIntegerField(verbose_name="収入", blank=True, null=True)
    GRADUATION = [
        ('junior_high_school', '中卒'),
        ('high_school', '高卒'),
        ('trade_school', '短大・専門学校卒'),
        ('university', '大卒'),
        ('grad_school', '大学院卒'),
    ]
    graduation = models.CharField(
        verbose_name="学歴", max_length=32, choices=GRADUATION, blank=True, null=True)

    """ Appealing Point """
    hobby = models.CharField(
        verbose_name="趣味", max_length=32, blank=True, null=True)
    PASSION = [
        ('hurry', '今すぐにでも'),
        ('speedy', '1年以内に'),
        ('slowly', 'ゆっくり考えたい'),
        ('no_marriage', '結婚する気はない'),
    ]
    passion = models.CharField(
        verbose_name="結婚に対する熱意", max_length=32, choices=PASSION, blank=True, null=True, default='slowly')
    tweet = models.CharField(verbose_name="つぶやき", max_length=8, blank=True, null=True)
    introduction = models.TextField(verbose_name="自己紹介", max_length=1000, blank=True, null=True)

    """ Assesment Fields """
    send_favorite = models.PositiveIntegerField(
        verbose_name="送ったいいね数", blank=True, null=True, default=0)
    receive_favorite = models.PositiveIntegerField(
        verbose_name="もらったいいね数", blank=True, null=True, default=0)
    stock_favorite = models.PositiveIntegerField(
        verbose_name="いいね残数", blank=True, null=True, default=1000)

    class Meta:
        ordering = ['-created_at']

    def from_last_login(self):
        now_aware = datetime.now().astimezone()
        if self.user.last_login is None:
            return "ログイン歴なし"
        login_time: datetime = self.user.last_login
        if now_aware <= login_time + timedelta(days=1):
            return "24時間以内"
        elif now_aware <= login_time + timedelta(days=2):
            return "2日以内"
        elif now_aware <= login_time + timedelta(days=3):
            return "3日以内"
        elif now_aware <= login_time + timedelta(days=7):
            return "1週間以内"
        else:
            return "1週間以上"

    def __str__(self):
        return self.nickname

verbose_nameはフィールドの詳細名です
validatorsはバリデータを設定できる項目で最小値や最大値などの制約を加えることができます
choicesは選択式のフィールドに対応する項目です
選択肢に対応する値はタプルの配列で格納します
DB上ではKeyが格納されコード上では辞書型として提供されます
最終ログイン日を教えてくれるサービスがあるので本アプリもそれにならいます
Userクラスのコードの見通しはよくしておきたいのでProfileクラスにメソッドを定義します
もしかしたらアンチパターンかもしれませんが......

またDjangoのモデルのフィールドはキャメルケースと迷いましたがDjangoに書いたフィールド名がDBのフィールド名にそのまま記載されたはずなので本アプリではスネークケースで書いています
デファクトスタンダードなのはどっちなのか正直わかっていません

Matchingクラスの実装

次にマッチングクラスを実装します
いいねを送った人ともらった人を格納するテーブルです
マッチングの実装については、いいねをもらった側のユーザーが承認(approvedをTrue)すると同時に相手側にいいねを送り返して双方向のいいねが成立した時点でマッチングとします
マッチング後、すなわちapprovedがTrueになっている場合にメッセージをやり取りできるような実装とします
またちなみにDjangoでは複合主キーはサポートされていないですが、フィールド同士の組み合わせ制約をunique_togetherで付与することができ、マッチングの重複を避けることができます

models.py

class Matching(models.Model):
    approaching = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='approaching',
        on_delete=models.CASCADE
    )
    approached = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='approached',
        on_delete=models.CASCADE
    )
    approved = models.BooleanField(verbose_name="マッチング許可", default=False)
    created_at = models.DateTimeField(verbose_name="登録日時", auto_now_add=True)

    class Meta:
        unique_together = (('approaching', 'approached'),)

    def __str__(self):
        return str(self.approaching) + ' like to ' + str(self.approached)

最初に設計したER図とずれがあるのは気にしないで下さい
実装が正です

DM(ダイレクトメッセージ)クラスの実装

最後にDirectMessageクラスの実装を行います
マッチングが成立している(MatchingクラスのapprovedがTrueになっている)ユーザー同士のメッセージが格納されます
余力がある人は画像データなどのマルチメディアのデータを送れるような実装にしてもよいかもしれませんが、このアプリではテキストデータのみを扱えることとします

models.py

class DirectMessage(models.Model):

    sender = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='sender',
        on_delete=models.CASCADE
    )
    receiver = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='receiver',
        on_delete=models.CASCADE
    )
    message = models.CharField(verbose_name="メッセージ", max_length=200)
    created_at = models.DateTimeField(verbose_name="登録日時", auto_now_add=True)

    def __str__(self):
        return str(self.sender) + ' --- send to ---> ' + str(self.receiver)

他にも作成したほうが良いモデルクラスは多分にありますが、今回はここまでの実装とします
参考にしているPairsにはコミュニティ機能があるので余力があればコミュニティクラスを作ったりタグクラスを作ったりしてもよいかもしれません

Migrationの実行

ModelをDBに適用するためにマイグレーションを実行します

py .\manage.py makemigrations 
py .\manage.py migrate

なお、makemigrations を行った際にbasicapi/migrationsにスキーマファイル(ex. 0001_initial.py)ができていることを確認することをお勧めします
これで先ほど作ったモデルがDBに構築されました

管理者Adminサイトの整備

管理者画面を扱いたい場合はアプリフォルダ(basicapi)にあるadmin.pyを編集していく必要があります

カスタムユーザーのモデルクラスを管理画面で扱えるようにする

管理者画面にカスタムユーザーを追加

basicapi/admin.pyを以下のように編集してきます
admin.py

from django.contrib import admin
from .models import User
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin


class UserAdmin(BaseUserAdmin):
    ordering = ('id',)
    list_display = ('email', 'password')
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Personal Information', {'fields': ('username',)}),
        (
            'Permissions',
            {
                'fields': (
                    'is_active',
                    'is_staff',
                    'is_superuser',
                )
            }
        ),
        ('Important dates', {'fields': ('last_login',)}),
    )
    add_fieldsets = (
        (None, {
           'classes': ('wide',),
           'fields': ('email', 'password1', 'password2'),
        }),
    )


admin.site.register(User, UserAdmin)
Profileクラスをカスタムユーザーの詳細画面に挿入する

Inclineを利用するとテーブルが分かれているクラスを同一画面で見ることができて管理画面が利用しやすくなるのでUserにProfileクラスを挿入していきます
Inclineの行をUserAdminに追加します

admin.py

from .models import User, Profile


class UserAdmin(BaseUserAdmin):
    # ......
    inlines = (ProfileInline,)

実装したモデルクラスを管理画面で扱えるようにする

同様にadmin.pyを編集してユーザー以外のデータも見られるようにします

Prolileを管理者画面に追加

admin.py

from .models import Profile


class ProfileAdmin(admin.ModelAdmin):
    ordering = ('-created_at',)
    list_display = ('__str__', 'user', 'age', 'sex', 'tweet', 'created_at', 'from_last_login')


admin.site.register(Profile, ProfileAdmin)
Matchingを管理者画面に追加

admin.py

from .models import Matching

class MatchingAdmin(admin.ModelAdmin):
    ordering = ('-created_at',)
    list_display = ('__str__', 'approved', 'created_at')


admin.site.register(Matching, MatchingAdmin)
DirectMessageを管理者画面に追加

admin.py

from .models import DirectMessage

class DirectMessageAdmin(admin.ModelAdmin):
    ordering = ('-created_at',)
    list_display = ('__str__', 'message', 'created_at')


admin.site.register(DirectMessage, DirectMessageAdmin)

管理者ユーザー(Superuser)の作成

管理者画面にアクセスできるユーザーを作成しておきましょう
Djangoのデフォルトユーザーではusernameが作成時に必要ですがカスタムユーザーが適用されているとemailの設定が必要になっていることを確認できるはずです

py manage.py createsuperuser

管理画面の動作確認

管理者ユーザーを作成したらサーバーを起動して管理者画面にアクセスしてみましょう
http://127.0.0.1:8000/admin

emailとパスワードでログインできることを確認します
また実装したモデルが管理画面に表示されて追加などの各種CRUD操作ができることを確認します
f:id:shinseidaiki:20220210000747p:plain
またユーザーのパスワードがハッシュ化されていることやプロフィールがインラインで表示されていることも確認しておきます
これらが一通り確認できれば動作確認としては完了です


次回予告

さて、次はSerializersとViewsの実装を行っていよいよRestAPIを作成していきますが、長くなりましたのでここでいったん区切らせてもらいます

次の記事

shinseidaiki.hatenablog.com


今後の課題

今回年齢を格納するフィールドはProfileに持たせましたが、年齢は年を経るごとに更新されていくので生年月日の項目が必要であるのと、ユーザー新規作成時に年齢を入力してもらいたいという観点から、ユーザークラスに新規登録時年齢registered_ageと生年月日のフィールドを持っておいた方が良いですね
プロフィールは表示年齢として生年月日から自動計算にする実装にした方がいいですね