🫵

【Django】AbstractBaseUserを使用したアカウント認証機能の実装

2023/11/30に公開

この記事は何??🧐

この記事ではDjangoについているAbstractBaseUserを使用しアカウント機能を実装する手順を詳しく解説します。ユーザー登録やログイン、ユーザー情報の取得や更新、そしてアカウントの削除といった機能をカバーし、セキュアなアカウント管理システムを簡単に構築できるようになります。

目次

  1. はじめに
  • AbstractBaseUserとは?

2.プロジェクトのセットアップ

  • Djangoプロジェクトとアプリの作成方法の説明
  • DRFのインストールとDjangoプロジェクトへの組み込み方
  • データベースの設定とマイグレーションの実行方法

3.アカウント認証の実装

  • 新規登録機能
  • ログイン機能
  • ログアウト機能
  • ユーザー情報取得
  • ユーザー情報更新機能
  • アカウント削除機能

4.最後に

  • AbstractBaseUserを使用したアカウント認証機能の実装に対する感想と振り返り

1.はじめに

以前実践した超簡易的なアカウント実装

以前私が記事に書いた超簡易的なアカウント実装はこちらです。
この記事は自作したUserモデルを使用し超簡易的なものなので、動ければなんでもいいんだよ!!
って考えの方はこちらをお勧めします!

https://zenn.dev/iccyan/articles/66e0245c854137

そしてこれが今回使用したコードです。
わからなければこちらをみてください!!

https://github.com/Iccyan21/AbstractBaseUser-Django

AbstractBaseUserとは?

AbstractBaseUserはDjangoウェブフレームワークにおいて、ユーザー認証システムの基礎となるクラスです。このクラスは、カスタムユーザーモデルを作成する際に拡張されます。Djangoでは標準的なUserモデルが提供されていますが、AbstractBaseUserを使用することで、ユーザー認証に関するより詳細なカスタマイズが可能になります。

例えば、標準のUserモデルではユーザー名とパスワードで認証が行われますが、AbstractBaseUserを使用すると、ユーザー名の代わりにメールアドレスや電話番号など、異なる認証フィールドを使用するカスタムユーザーモデルを作成することができます。また、必要なフィールドやメソッドを自由に追加・変更することができるため、プロジェクトの特定のニーズに合わせてユーザーモデルを柔軟に設計することが可能です。

AbstractBaseUserを使用するメリット

1.柔軟な認証フィールドの定義:
標準のUserモデルはユーザー名とパスワードを使用しますが、AbstractBaseUserを使うと、メールアドレスや電話番号など、任意のフィールドを主要な認証フィールドとして設定できます

2.必要なフィールドのカスタマイズ:
企業やプロジェクト固有の要件に基づいて、ユーザーモデルに必要なフィールドを自由に追加・調整することができます。

3.セキュリティの向上:
標準的なユーザーモデルをカスタマイズすることで、特定のセキュリティ要件(パスワードの複雑さの規則など)に合わせた認証システムを構築することが可能です。

これらのメリットにより、AbstractBaseUserは、標準的なUserモデルよりも高度なカスタマイズや特定のビジネスロジックの要件に合わせたユーザー管理システムを構築する際に非常に有効です

AbstractBaseUserを使用することにより、手間が少なく実践的なUserモデルが作成できるようになり開発の実践度が大幅に上昇すると思います😎

それでは実際に作ってみましょう🫡

プロジェクトのセットアップ

ここでは手間をだいぶ省いてますが、もっと詳しく設定するなら下記の記事を参考にしてください!
ほぼ同じ設定なのでわかりやすいです!

https://zenn.dev/iccyan/articles/66e0245c854137

Djangoプロジェクトとアプリの作成方法の説明

PythonやDjangoなどの環境は、installされているものとして話を進めていきます
まずはDjangoのプロジェクトを作成してみましょう!

下記はMacでのDjango プロジェクト作成コマンドです

django-admin startproject mysite 

これでmysiteという名のプロジェクトファイルが作成されます。もしプロジェクトファイル名を変更する場合は、適宜指定してください

プロジェクトが作成されたらVscodeでも何でもいいのでエデイタからプロジェクトファイルを開きましょう

エデイタを開いたらターミナルで下記のコマンドを入力しアプリファイルを作成しましょう

python manage.py startapp accounts

作成したら今回使用する必要なものをインストールしていきましょう!

インストールするもの

まずは必要なものをインストールしましょう!
まず最初に使用するdjangorestframeworkをインストールしましょう
ターミナルで以下のコマンドを入力してください

pip install djangorestframework

インストールが終了したらmysiteファイルにある、settings.pyのINSTALLED_APPSにさっき作ったアプリとDRFを追加しましょう。上の方にあります。

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "accounts", #作ったアプリを追加
    "rest_framework", #DRFを追加(さっきインストールしたやつ)
    "rest_framework.authtoken" # 認証トークン
]

rest_framework.authtokenってなんぞや🧐

rest_framework.authtokenは、Django REST Framework(DRF)の一部で、トークンベースの認証システムを提供するモジュールです。このモジュールを使用すると、各ユーザーに一意の認証トークンが割り当てられ、APIリクエストの際にこのトークンを使用してユーザーを認証することができます

そしてsettings.pyの下部にあるLANGUAGE_CODEとTIME_ZONEも変更しましょう

# 変更前
LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"
# 変更後
LANGUAGE_CODE = "ja"

TIME_ZONE = "Asia/Tokyo"

上記の変更後に書き換えましょう!!

そして最後にmysiteのurls.pyに以下を記入しましょう

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

urlpatterns = [
    path("admin/", admin.site.urls), # 管理画面
    path("accounts/", include("accounts.urls")), #accountsのurl
]

ここではinclude関数を使用して、accountsアプリのルーティング設定をmysiteプロジェクトのURLに追加しています。accountsアプリのURL設定はaccounts/urls.pyファイルで行います。

この設定により、mysite/accounts/以下のURLにアクセスした場合には、accountsアプリのURL設定が適用されます。

それではコードを書いていきましょう🙌

AbstractBaseUserを使用したUserモデルの書き方

それでは本題のAbstractBaseUserを使用したUserモデルを書いていきます。
accountにあるmodels.pyにアカウントのモデルを追加していきます。

from django.db import models
from django.core.validators import RegexValidator
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.utils.translation import gettext_lazy as _

class UserManager(BaseUserManager):
    # 通常のユーザーを作成
    # どの値作成する時に必要かを指定
    # =Noneは任意の引数なんで記入しなくても良い
    def create_user(self, username,email,password,**extra_fields):     
        # 記入ミスをチェック
        if not username:
            raise ValueError('UserIDを入力してください')
        
        
        if not email:
            raise ValueError('メールアドレスを入力してください')
        
        if not password:
            raise ValueError('パスワードを入力してください')
        
        # Emailを正規化
        email = self.normalize_email(email)
        # モデルインスタンスを作成
        user = self.model(
            username = username,
            email = email,
            # ここでこれを返さないと謎のエラーでsupercreateuserがつくれない
            **extra_fields  
        )
        # パスワードをハッシュ化
        user.set_password(password)
        # ユーザーを保存
        user.save(using=self._db)
        return user
    
    # スーパーユーザーを作成
    # 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.'))
        
        # createuserを呼び出しているため同じ値が必要
        return self.create_user(username,email,password,**extra_fields)
    
    
class User(AbstractBaseUser,PermissionsMixin):
    # usernameをユーザーIDとして使用するため、英数字のみを許可するバリデータを設定
    # ユーザー名が英字(大文字・小文字)および数字のみで構成されているかをチェック
    username_validator = RegexValidator(
        regex=r'^[a-zA-Z0-9]*$',  # 英数字のみを許可する正規表現
        message="ユーザー名は英数字のみで構成してください。"
    )
    # blank=Trueは空欄でも良いという意味
    username = models.CharField(max_length=20,unique=True,validators=[username_validator],blank=False) 
    email = models.EmailField(unique=True,blank=False)
    nickname = models.CharField(max_length=20,blank=False)
    coment = models.CharField(max_length=100,blank=True)
    # ログインしているかどうか
    is_active = models.BooleanField(default=True)
    # 管理サイトにアクセスできるかどうか
    # デフォルトは権限無し
    is_staff = models.BooleanField(default=False)
    
    # ユーザー作成やスーパーユーザー作成などのカスタムロジックを実装
    objects = UserManager()
    # ログインする時にはusername(userid)を使用
    USERNAME_FIELD = 'username'
    # 管理コマンドを使用してスーパーユーザーを作成する際に必要とされるフィールド
    # 特にないので空のリストを指定
    REQUIRED_FIELDS = ['nickname']
    
    
    # 管理者などで区別がつきやすいようにusernameで表示
    def __str__(self):
        return self.username

このモデルの解説

Userモデルについて

カスタムバリデータの使用:

username_validatorは、ユーザー名が英字(大文字と小文字)と数字のみで構成されているかをチェックするカスタムバリデータです。これは正規表現[1]*$を使用しており、英数字のみを許可します。

ユーザー名フィールドの定義:

usernameフィールドは、ユーザーIDとして機能し、一意でなければなりません(unique=True)。このフィールドは空白を許可しない(blank=False)設定になっており、カスタムバリデータを使用しています。

追加フィールド

email: ユーザーの電子メールアドレスを格納し、これも一意である必要があります。
nickname: ユーザーのニックネームを格納するフィールドで、空白は許可されません。
coment: ユーザーのコメントを格納するフィールドで、空白を許可します(任意フィールド)。
ステータスフィールド:

is_active: ユーザーがアクティブかどうかを表します。デフォルトはTrue(アクティブ)です。
is_staff: ユーザーが管理サイトにアクセスできるかどうかを示します。デフォルトはFalse(アクセス不可)です。
カスタムユーザーマネージャ:
UserManagerは、ユーザー作成やスーパーユーザー作成などのカスタムロジックを実装するためのカスタムマネージャです。

ログインフィールドと必須フィールド:

USERNAME_FIELD: ログイン時に使用されるフィールドをusernameに設定しています。
REQUIRED_FIELDS: スーパーユーザーを作成する際に必要なフィールドを指定します。ここではnicknameが必須です。
文字列表現:
__str__メソッドは、オブジェクトの文字列表現を定義し、ユーザー名(username)で表示されます。

UserManagerモデルについて

create_userメソッド

目的: 通常のユーザーを作成するためのメソッドです。
パラメータ: username, email, passwordを引数として受け取ります。これらは新しいユーザーを作成する際に必要な基本情報です。
バリデーション: 各引数が提供されているかどうかを確認し、何れかが欠けている場合はValueErrorを発生させます。
メールアドレスの正規化: normalize_emailメソッドを使用して、メールアドレスを正規化(標準的な形式に変換)します。
ユーザーの作成: 指定されたデータを使用して新しいユーザーインスタンスを作成します。
**extra_fieldsを使用することで、追加の属性を柔軟に設定できます。
パスワードのハッシュ化: set_passwordメソッドを使用して、提供されたパスワードを安全にハッシュ化します。
ユーザーの保存: 最後にユーザーをデータベースに保存します。

create_superuserメソッド

目的: スーパーユーザー(管理者)を作成するためのメソッドです。
パラメータ: create_userメソッドと同様ですが、is_staffとis_superuserフラグをTrueに設定します。
管理者フラグの設定: extra_fieldsを使用して、is_staffとis_superuserをTrueに設定し、これらがTrueでない場合はValueErrorを発生させます。
スーパーユーザーの作成: 最後にcreate_userメソッドを呼び出して、スーパーユーザーを作成します。この際、追加されたis_staffとis_superuserのフラグも適用されます。

注意点

これら二つのモデルは非常に複雑なところもあり、たとえば自分専用のモデルに変更しようと思ってもなかなかうまくいかないことがただありますので、しっかり上記の性質を理解してモデルをカスタマイズしていくのが大事です!!

モデルを書き終えたらsetting.pyの下の方にこちらを追加しましょう

# 認証ユーザーのモデルを指定
AUTH_USER_MODEL = 'accounts.User'

この設定は、Djangoに対して「accounts.Userが認証に使用されるユーザーモデルである」と宣言します。この宣言がないと、Djangoはデフォルトのユーザーモデルを使用しようとします。従って、カスタムユーザーモデルを正しく機能させるためには、この設定が不可欠です。

それではデーターベースを更新しましょう!

python manage.py makemigrations
python manage.py migrate

そして管理ユーザーも作りましょう。ターミナルに以下のコマンドを入力

python manage.py createsuperuser

これで管理ユーザーが作成できれば、成功です!
usernameとpasswordは忘れないように😤

アカウント認証の実装

新規登録機能

アカウント認証の実装の新規登録機能への処理を書いていこうと思います。
ますはaccountsにserializers.pyを作成し下記のコードを書きましょう。

from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.contrib.auth import authenticate
import re
# カスタムユーザーモデルを使用している場合は、
# 取得するユーザーモデルを指定する必要があります。
User = get_user_model()

# 新規登録用のシリアライザ
# ModelSerializerはDjangoモデルをJSONに変換したり、
# JSONをモデルに変換したりするためのロジックを持っています。
class  UserRegistrationSerializer(serializers.ModelSerializer):
    password_confirm = serializers.CharField(write_only=True)
    
    # シリアライザをカスタマイズ
    class Meta:
        model = User
        fields = ('username','email','password', 'password_confirm',)
        # パスワードを書き込み専用にしてレスポンスに含めないようにする
        extra_kwargs = {
            'password': {'write_only': True}
        }
    
    # シリアライザの値が有効かどうかを検証する
    def validate(self, attrs):
        if attrs['password'] != attrs['password_confirm']:
            raise serializers.ValidationError({"password": "確認パスアードと一致しません"})
        return attrs
    
    # usernameに英数字以外が含まれていないかを検証する
    def validate_username(self, value):
        if not re.match(r'^[a-zA-Z0-9]*$', value):
            raise serializers.ValidationError("ユーザー名は英数字のみで構成してください。")
        return value
    
    # 残りのデーターをしようしてユーザーを作成する
    def create(self, validated_data):
        validated_data.pop('password_confirm')
        # ここでModelsのcreate_userを呼び出している
        user = User.objects.create_user(
            username=validated_data['username'],
            email=validated_data['email'],
            password=validated_data['password'],
        )
        return user

このコードの解説

UserRegistrationSerializerクラス

継承: serializers.ModelSerializerを継承しており、これによりDjangoモデルをJSONにシリアライズ(変換)する機能、またJSONをDjangoモデルにデシリアライズする機能が提供されます。
フィールド定義: username, email, password, password_confirmフィールドを含んでいます。password_confirmは新規登録時のパスワード確認用フィールドであり、write_only=Trueに設定されているため、APIのレスポンスには含まれません。

モデル指定: Meta内部クラスで、使用するモデルがUser(カスタムユーザーモデル)と指定されています。

追加設定: passwordフィールドもwrite_only=Trueに設定されており、これによりAPIのレスポンスには含まれません。

検証メソッド

validateメソッド: 入力されたパスワードと確認用パスワードが一致しているかを検証します。一致しない場合は例外を投げます。
validate_usernameメソッド: ユーザー名が英数字のみで構成されているかを検証します。正規表現を使ってチェックし、不適切な場合は例外を投げます。

ユーザー作成

createメソッド: 検証されたデータを使って新しいユーザーを作成します。password_confirmフィールドはここで取り除かれ、create_userメソッド(UserManagerに定義)を呼び出してユーザーをデータベースに保存します。

次にaccountsのviews.pyにこのコードを書きましょう。

from rest_framework import generics
from .serializers import UserRegistrationSerializer
from rest_framework.response import Response
from rest_framework import status

#CreateAPIViewを使う理由は作成に特化してるから(別にAPIViewでもいい)
class UserRegistrationView(generics.CreateAPIView):
    # シリアライザの指定
    serializer_class = UserRegistrationSerializer
    
    def post(self,request, *args, **kwargs):
        # 取得したデーターをシリアライザに渡す
        serializer = self.get_serializer(data=request.data)
        # 要件が満たされているかどうかを検証する
        # パラメータが設定されているので、raise_exception=Trueを指定する
        if serializer.is_valid(raise_exception=True):
            user = serializer.save()
            return Response({
                "user": UserRegistrationSerializer(user).data,
                "message": "ユーザー登録が完了しました!"
            },status=status.HTTP_201_CREATED)
            
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

UserRegistrationViewクラス

継承: generics.CreateAPIViewを継承しています。CreateAPIViewは、オブジェクトの作成に特化した汎用ビューで、新規作成のAPIエンドポイントに適しています。
シリアライザ: UserRegistrationSerializerをシリアライザクラスとして指定しています。このシリアライザは、リクエストデータの検証とユーザーの作成を担います。

postメソッド

データの受け取り: request.dataから送信されたデータを受け取ります。
シリアライザの初期化: 受け取ったデータでシリアライザのインスタンスを作成します。
データの検証: is_validメソッドを呼び出してデータの検証を行います。raise_exception=Trueが指定されているため、データが無効であれば例外が発生し、自動的に400 Bad Requestレスポンスが返されます。
ユーザーの作成: データが有効であれば、saveメソッドを呼び出して新しいユーザーを作成します。
レスポンスの返送: 新しいユーザーの情報と成功メッセージを含む201 Createdレスポンスを返します。ここでもUserRegistrationSerializerを使用して、作成されたユーザーをJSON形式にシリアライズしています。
エラーレスポンス: データが無効であれば、シリアライザのエラーと400 Bad Requestレスポンスが返されます。

そしてmysiteのurlsにこのコードを追加しましょう。

urlpatterns = [
    path("admin/", admin.site.urls), # 管理画面
    path("accounts/", include("accounts.urls")), #accountsのurl
]

そしてaccountのurlsにのコードを追加しましょう。

urlpatterns = [
    path('register/', UserRegistrationView.as_view(), name='register'), #ユーザー登録
]

コードを書いたらサーバーを起動しみていきましょう。

python manage.py runserver

サーバーを起動しhttp://127.0.0.1:8000/accounts/register/ このurlのアクセスすると

このような画面が出てきますのでデータを入力してPOSTボタンを押すと

このような画面になれば成功です。

ログイン機能

続いてはログイン機能です。
serializers.pyに下記のコードを追加しましょう

# ログインシリアライザ
class LoginSerializer(serializers.Serializer):
    # username(userId)とpasswordを受け取る
    username = serializers.CharField() 
    #style={'input_type': 'password'}により、APIブラウザでパスワードフィールドがマスクされます。
    password = serializers.CharField(style={'input_type': 'password'}, trim_whitespace=False)
    
    def validate(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        
        # 両方定義されてるかどうかを検証する
        if username and password:
            #  Djangoの authenticate 関数を使用して、提供されたユーザー名とパスワードでユーザーを認証
            user = authenticate(request=self.context.get('request'),username=username, password=password)
            if not user:
                mag = '提供された認証情報ではログインできません。'
                raise serializers.ValidationError(mag, code='authorization')
            
        else:
            mag = 'usernameとpasswordを入力してください。'
            raise serializers.ValidationError(mag, code='authorization')
        
        # 認証が成功した場合、attrs 辞書に認証されたユーザーを追加します。
        attrs['user'] = user
        # attrs 辞書を戻り値として返します。これには、必要な認証情報や追加の検証後のデータが含まれます
        return attrs

解説

LoginSerializerクラス

フィールド定義:
username: ユーザー名を受け取るためのフィールド。

password: パスワードを受け取るためのフィールド。style={'input_type': 'password'}により、APIブラウザでこのフィールドがパスワードマスクされるように設定されています。

validateメソッド

データの取得: attrs辞書からusernameとpasswordを取得します。

ユーザー認証:
まず、usernameとpasswordが両方とも提供されているかを検証します。
両方提供されていれば、Djangoのauthenticate関数を使用して、これらの認証情報でユーザーを認証します。この関数は、認証が成功した場合にユーザーオブジェクトを返し、失敗した場合にはNoneを返します。
ユーザーが見つからない場合は、カスタムのエラーメッセージと共にValidationErrorを発生させます。

成功時の処理:
認証が成功した場合には、attrs辞書に認証されたユーザーを追加し、attrs辞書をそのまま戻り値として返します。

from .serializers import UserRegistrationSerializer,LoginSerializer
from rest_framework.views import APIView
from django.contrib.auth import login
from rest_framework.authtoken.models import Token

# ログイン用のAPIView
class LoginAPIView(APIView):
    def post(self,request):
        serializer = LoginSerializer(data=request.data, context={'request': request})
        if serializer.is_valid():
            # ログイン処理
            user = serializer.validated_data['user']
            # Djangoの login 関数を使って、ユーザーをログインさせます。
            # これにより、セッションが開始され、ユーザーは認証済みとして扱われる
            login(request, user)
            
            token, created = Token.objects.get_or_create(user=user)
            
            return Response({"message": "ログインが成功しました!", 
                            'token': token.key,
                            'username': user.username},
                            status=status.HTTP_200_OK)
        
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

LoginAPIViewクラス

継承: APIViewを継承しています。APIViewは、カスタムのビューロジックを実装するための基本クラスです。

postメソッド

シリアライザの使用: リクエストデータをLoginSerializerに渡して初期化し、入力データの検証を行います。

データの検証: is_validメソッドを使用してリクエストデータを検証します。データが有効でなければ、400 Bad Requestレスポンスを返します。

ログイン処理:
検証されたデータからユーザーオブジェクトを取得します。
Djangoのlogin関数を使用してユーザーをログインさせます。これにより、セッションが開始され、ユーザーは認証済みとして扱われます。

トークンの生成: Token.objects.get_or_createメソッドを使用して、ユーザーに対応するトークンを取得または新規作成します。

成功レスポンス: ログインが成功した場合、メッセージ、トークン、ユーザー名を含む200 OKレスポンスを返します。

これを書いたらurlに下記を追加しましょう

path('login/', LoginAPIView.as_view(), name='login'), #ログイン

urlに追加したらサーバーを立ち上げてhttp://127.0.0.1:8000/accounts/login/ ここにアクセスし以下のような形式でデータを入れましょう

{
 "username": "さっき登録したusername",
 "password": "さっき登録したpassword"
}

入力できたらPOSTボタンを押すと

こちらのような画面が出てくれば成功です!!

またここのTokenはLogout機能で使用するのでどこかにコピペしといてください😶

ユーザー情報取得

ユーザー情報の取得の処理を書いていきます。
serializers.pyに下記のコードを書きましょう

# ユーザー情報取得のシリアライザ
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'nickname', 'email','coment') 

UserSerializerクラス

継承: serializers.ModelSerializerを継承しています。ModelSerializerは、Djangoモデルのインスタンスをシリアライズおよびデシリアライズするためのシリアライザの基本クラスです。

Meta内部クラス

モデル指定: model = User行で、このシリアライザが操作するモデルをUser(カスタムユーザーモデル)として指定しています。

フィールド指定: fields属性で、シリアライズに含めるフィールドを指定しています。ここではusername(ユーザー名)、nickname(ニックネーム)、email(メールアドレス)、coment(コメント)というフィールドが含まれています。

そしてviewsに下記のコードを書きましょう

# ユーザー情報取得用のAPIView
class UserDetailView(APIView):
    # 認証確認
    permission_classes = [IsAuthenticated]
    
    def get(self,request,username=None):
        # ユーザー名が指定されていない、または現在のユーザーのユーザー名の場合
        if username is None or username == request.user.username:
            user = request.user
        else:
            # 他のユーザーを取得
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                return Response({"message": "ユーザーが見つかりませんでした。"}, status=status.HTTP_404_NOT_FOUND)
            
        serializer = UserSerializer(user)
        return Response(serializer.data, status=status.HTTP_200_OK)

UserDetailViewクラス

継承: APIViewクラスを継承しています。これは、Django REST Frameworkの基本APIビュークラスで、カスタムのリクエストハンドラーを定義するのに使用されます。

認証: permission_classesにIsAuthenticatedを設定しています。これにより、このビューへのアクセスにはユーザーが認証されている必要があることを示しています。

getメソッド

ユーザーの特定: GETリクエストでusernameパラメータが指定されている場合、そのユーザー名に対応するユーザーを取得します。指定されていない場合、または指定されたユーザー名がリクエストを行ったユーザーのものと同じ場合は、リクエストしたユーザー自身の情報を返します。
ユーザー情報の取得: Userモデルから指定されたユーザー名を持つユーザーを取得します。ユーザーが存在しない場合は、404 Not Foundレスポンスを返します。

シリアライゼーション: 取得したユーザーオブジェクトをUserSerializerを使ってシリアライズし、JSON形式のデータに変換します。

レスポンスの返送: シリアライズされたユーザーデータを含む200 OKレスポンスを返します。

そしてurlに下記を追加しましょう

path('profile/',UserDetailView.as_view(),name='detail') #ユーザー情報取得

そしてサーバーを立ち上げ http://127.0.0.1:8000/accounts/profile/ このurlにアクセスすると

先ほどログインしたユーザーの情報を取得できています。
これでユーザー情報取得処理は終わりです。

ユーザー情報編集処理

続いてはユーザー情報編集処理です。

serializers.pyに下記のことを書きましょう。

# ユーザー情報編集のシリアライザ
class UserEditSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'nickname', 'email','coment')
    
    def update(self, instance, validated_data):
        instance.username = validated_data.get('username', instance.username)
        instance.nickname = validated_data.get('nickname', instance.nickname)
        instance.email = validated_data.get('email', instance.email)
        instance.coment = validated_data.get('coment', instance.coment)
        instance.save()
        return instance

UserEditSerializerクラス

継承: serializers.ModelSerializerを継承しています。これは、DjangoのモデルインスタンスをJSON形式に変換するシリアライザの基本クラスです。

モデル指定: Meta内部クラスでmodel = Userと指定しており、このシリアライザが操作するモデルがUser(カスタムユーザーモデル)であることを示しています。

フィールド指定: fieldsで、シリアライズに含めるフィールドをusername, nickname, email, comentとして指定しています。

updateメソッド

インスタンスの更新: このメソッドは、モデルインスタンス(instance)を、検証済みのデータ(validated_data)に基づいて更新します。

validated_data.getを使用して、各フィールドの新しい値を取得します。新しい値が提供されていない場合は、インスタンスの現在の値を維持します。

フィールドのマッピング:
usernameはそのままusernameフィールドにマッピングされます。
nicknameはnicknameフィールドに、
emailはemailフィールドに、
comentはcomentフィールドにマッピングされます。

保存: 変更を加えたインスタンスを保存します。
インスタンスの返却: 更新されたインスタンスを戻り値として返します。

続いてviews.pyにpremisson.pyを追加し下記のコードを追加しましょう

from rest_framework import permissions

class IsUserOrReadOnly(permissions.BasePermission):
    """
    オブジェクトレベルの権限で、ユーザーが自分自身の情報のみ更新できるようにする。
    読み取りは全ユーザーに許可する。
    """

    def has_object_permission(self, request, view, obj):
        # 読み取りメソッドは常に許可
        if request.method in permissions.SAFE_METHODS:
            return True

        # 書き込み権限はそのユーザー自身にのみ許可
        return obj.username == request.user.username

続いてviews.pyに下記を記述しましょう。

from .models import User
from .premisson import IsUserOrReadOnly
from rest_framework.exceptions import PermissionDenied, NotFound

# ユーザー情報取得用のAPIView
class UserDetailView(APIView):
    # 認証確認
    permission_classes = [IsAuthenticated]
    
    def get(self,request,username=None):
        # ユーザー名が指定されていない、または現在のユーザーのユーザー名の場合
        if username is None or username == request.user.username:
            user = request.user
        else:
            # 他のユーザーを取得
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                return Response({"message": "ユーザーが見つかりませんでした。"}, status=status.HTTP_404_NOT_FOUND)
            
        serializer = UserSerializer(user)
        return Response(serializer.data, status=status.HTTP_200_OK)

UserDetailViewクラス

継承: APIViewクラスを継承しています。これは、Django REST Frameworkの基本APIビュークラスで、カスタムのリクエストハンドラーを定義するのに使用されます。

認証の確認: permission_classesにIsAuthenticatedを設定しています。これにより、このビューへのアクセスにはユーザーが認証されている必要があることを示しています。

getメソッド

ユーザーの特定: リクエストでusernameパラメータが指定されているかどうかを確認します。指定されていない場合、または指定されたユーザー名がリクエストを行ったユーザーのものと同じ場合は、リクエストしたユーザー自身の情報を返します。

他のユーザーの取得: 指定されたユーザー名が他のユーザーのものであれば、そのユーザーをUserモデルから取得しようとします。指定されたユーザーが存在しない場合は、404 Not Foundレスポンスを返します。

シリアライズ: 取得したユーザーオブジェクトをUserSerializerでシリアライズし、JSON形式のデータに変換します。

レスポンスの返送: シリアライズされたユーザーデータを含む200 OKレスポンスを返します。

そして最後にurlsに

path('profile/edit/',UserEditView.as_view(),name='update'), #ユーザー情報更新

を追加しサーバーを立ち上げましょう

そしてここに更新する内容を書きPUTを押すとデーターが更新されています。
これで完了です。

ログアウト処理

続きましてはLogout処理です。

views.pyに下記のコードを追加しましょう

#ログアウト用のAPIView
class LogoutAPIView(APIView):
    # このビューがトークン認証を使用し、認証済みのユーザーのみアクセスできるようにします
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
    
    def post(self, request):
        # トークン削除
        request.auth.delete()
        return Response({"message":"ログアウトしました"},status=status.HTTP_200_OK)
    

そしてurlに

path('logout/',LogoutAPIView.as_view(),name='logout'), #ログアウト

を追加しサーバーを立ち上げましょう

注意

このログアウト機能はログイン時に取得したトークンを使用します!
その取得したトークンをPOSTMANのHeadersに

このように貼りつけて http://127.0.0.1:8000/accounts/logout/ にPOSTで送信しましょう!!

すると

このようなメッセージがPOSTMANのBodyに表示されれば成功です!

アカウント削除機能

最後にアカウント削除機能です。
views.pyに下記のコードを追加します

# User削除用のAPIView
class UserDeleteView(generics.DestroyAPIView):
    queryset = User.objects.all()
    permission_classes = [permissions.IsAuthenticated]

    def get_object(self):
        return self.request.user

    def delete(self, request, *args, **kwargs):
        # オブジェクトを取得して削除
        obj = self.get_object()
        obj.delete()

        # カスタムレスポンスを返す
        return Response({"message": "ユーザーを削除しました!"}, 
                        status=status.HTTP_204_NO_CONTENT)

解説

UserDeleteViewクラス

継承: generics.DestroyAPIViewを継承しています。DestroyAPIViewは、オブジェクトの削除に特化したジェネリックビューで、DELETEリクエストを処理するのに適しています。

クエリセット: querysetで、このビューが操作するモデルのクエリセットをUser.objects.all()として設定しています。これは、このビューがUserモデルに対して動作することを意味します。

権限: permission_classesにpermissions.IsAuthenticatedを設定しており、このビューにアクセスできるのは認証済みのユーザーのみであることを示しています。

get_objectメソッド

オブジェクトの取得: このメソッドは、削除するオブジェクトを特定します。ここでは、リクエストを行ったユーザー自身(self.request.user)を削除対象として返しています。

deleteメソッド

削除処理: get_objectメソッドを使用して削除対象のオブジェクト(ユーザーアカウント)を取得し、deleteメソッドを呼び出してアカウントを削除します。
レスポンス: ユーザーが削除されたことを示すカスタムメッセージとともに、204 No Contentレスポンスを返します。204ステータスコードは、成功したがコンテンツは返さないことを意味します。

そしてurlに

path('delete/',UserDeleteView.as_view(),name='delete'), #ユーザー削除

を記述しサーバーを立ち上げましょう!!
サーバーを立ち上げ http://127.0.0.1:8000/accounts/delete/ ここへアクセスするとこのような画面が出てきます。

※ 一度ログアウトしたので、ログインしてください!!

そしてDELETEボタンを押すと

このような画面が出てくれば完了です。

これでAbstractBaseUserを使用したアカウント認証機能の実装は終了です。

お疲れ様でした🫡

AbstractBaseUserを使用したアカウント認証機能の実装に対する感想と振り返り

これでAbstractBaseUserを使用したアカウント認証機能の実装は終了となります

特に後半は睡魔に襲われながら書いていたので確認しましたが間違ってるかもしれません😱

もしわからないところがあれば気軽に質問してください!!

下記にGitのコードを載せておくので、参考程度に拝見ください!

https://github.com/Iccyan21/AbstractBaseUser-Django

こちらがtwitterアカウントなのでよければフォローお願いします!!

https://twitter.com/GIANT_KILLING_0

最後まで拝見いただき、ありがとうございました!!

脚注
  1. a-zA-Z0-9 ↩︎

Discussion