0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js×Django連携】管理者ログイン画面を作成する方法

Last updated at Posted at 2025-04-03

サンプル

このような管理者ログイン画面を作っていきます。
image.png

フロントエンド側(Next.js)の実装

ちょっとしたchip

POST リクエストを送信しているURLが APPEND_SLASH が有効な状態で、URLの末尾にスラッシュ(/)が不足しないように、スラッシュ(/)を付与しておく。

const response = await axios.post('http://127.0.0.1:8000/api/loginAdminUsers/',formData);

Djangoの設定でAPPEND_SLASHを無効にする

Djangoの設定で、APPEND_SLASHを False に設定することでも、末尾のスラッシュを強制しないようにできます。この設定を有効にすることで、URLの末尾にスラッシュを追加しなくてもDjangoはリダイレクトしません。

settings.py 内の APPEND_SLASH を False に変更します。

# settings.py
APPEND_SLASH = False
frontend/app/adminUsers/login/page.tsx
'use client'
import { useState } from "react"
import { useForm } from "react-hook-form"
import axios from "axios"
import { useRouter } from "next/navigation"

export default function AdminLogin(){
    const router = useRouter();
    const defaultValues = {
        adminUserName:'',
        adminUserEmail:'',
        adminUserPassword:''
    };

    const {register,handleSubmit,formState:{errors}} = useForm({
        defaultValues
    })

    const [adminUserName,setAdminUserName] = useState('');
    const [adminUserEmail,setAdminUserEmail] = useState('');
    const [adminUserPassword,setAdminUserPassword] = useState('');

    const onSubmit = async(data:any)=>{
        try{
            const formData = new FormData();
            formData.append('adminUserName',data.adminUserName);
            formData.append('adminUserEmail',data.adminUserEmail);
            formData.append('adminUserPassword',data.adminUserPassword);
    
            const response = await axios.post('http://127.0.0.1:8000/api/loginAdminUsers/',formData);
    
            if(response.status === 201){
                alert('Success to Login');
                console.log(response.data);
            }else{
                alert('Error');
                console.log(response.data);
            }
        }catch(error){
            console.log(error);
        }

    }

    const clickBackToTopPage = ()=>{
        router.push('/');
    }

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <div>
                <label>Name</label>
                <input
                    type="text"
                    className="w-full border border-gray-500 rouden-md"
                    defaultValue={defaultValues.adminUserName}
                    {...register('adminUserName',{
                        required:'AdminUserName must be required.'
                    })}
                    onChange={(e)=>setAdminUserName(e.target.value)}
                />
                <div className="text-rose-500">{errors.adminUserName?.message}</div>
            </div>
            <div>
                <label>Email</label>
                <input
                    type="email"
                    className="w-full border border-gray-500 rouden-md"
                    defaultValue={defaultValues.adminUserEmail}
                    {...register('adminUserEmail',{
                        required:'Email must be required.'
                    })}
                    onChange={(e)=>setAdminUserEmail(e.target.value)}
                />
                <div className="text-rose-500">{errors.adminUserEmail?.message}</div>
            </div>
            <div>
                <label>Password</label>
                <input
                    type="password"
                    className="w-full border border-gray-500 rouden-md"
                    defaultValue={defaultValues.adminUserPassword}
                    {...register('adminUserPassword',{
                        required:'Password must be required.'
                    })}
                    onChange={(e)=>setAdminUserPassword(e.target.value)}
                />
                <div className="text-rose-500">{errors.adminUserPassword?.message}</div>
            </div>
            <div>
                <button
                    type="submit"
                    className="bg bg-blue-500 rouden-md text-white px-4 py-4"
                >
                    Login
                </button>
                <button
                    type="button"
                    className="bg bg-green-500 rouden-md text-white px-4 py-4"
                    onClick={clickBackToTopPage}
                >
                    Back To TopPage
                </button>
            </div>
        </form>
    )
}

サーバ側(Django)の実装

ルーティング設定は、下記の通り。
今回はログイン機能ですので、login_admin_user関数をルートに含めます。
なので、まずはlogin_admin_user関数をインポートします。

backend/api/url.py
from .views import TodoViewSet, AdminUsersViewSet, login_admin_user

つぎにlogin_admin_user関数をルートに含めます。これにより、POST リクエストで /loginAdminUsers/ エンドポイントにアクセスできるようになります。

backend/api/url.py
urlpatterns = [
    path('loginAdminUsers/', login_admin_user, name='login_admin_user'),  # ログイン用のエンドポイント
    path('', include(router.urls)),
]

全体のコードはこちら↓

backend/api/url.py
from django.urls import path, include
from .views import TodoViewSet, AdminUsersViewSet, login_admin_user  # login_admin_user をインポート
from rest_framework.routers import DefaultRouter
from .views import TodoViewSet,AdminUsersViewSet

router = DefaultRouter()
router.register('todos', TodoViewSet, basename='todo')
router.register('adminusers',AdminUsersViewSet,basename='create_admin_user')

urlpatterns = [
    path('loginAdminUsers/', login_admin_user, name='login_admin_user'),  # ログイン用のエンドポイント
    path('', include(router.urls)),
]

パスワードの照合

つづいて、views.pyについてです。

パスワードの照合は、下記の処理を追加します。

views.py
# パスワード照合
            if check_password(request.data['adminUserPassword'], loginAdminUser.adminUserPassword):  # ここで adminUserPassword を確認
                # シリアライザーを使ってユーザー情報を返す
                user_data = {
                    "id": loginAdminUser.id,
                    "adminUserName": loginAdminUser.adminUserName,
                    "adminUserEmail": loginAdminUser.adminUserEmail,
                    # 必要に応じて他の情報を追加
                }
                return Response(data=user_data, status=status.HTTP_200_OK)
            else:
                return Response(data={"message": "Invalid credentials."}, status=status.HTTP_401_UNAUTHORIZED)

全体のコードはこちら↓

backend/api/views.py
#from django.shortcuts import render
from rest_framework.decorators import api_view #追加
from rest_framework.response import Response # 追加
from rest_framework import status #追加
from django.db import transaction # 追加
from django.contrib.auth.hashers import make_password #追加
from rest_framework import viewsets
from .models import Todo,AdminUsers
from .serializers import TodoSerializer,AdminUsersSerializer,AdminUserLoginSerializer
from django.core.exceptions import ObjectDoesNotExist #Add
from django.contrib.auth.hashers import check_password #Add


# Create your views here.
class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSerializer

# Serialize to create adminuser
class AdminUsersViewSet(viewsets.ModelViewSet):
    queryset = AdminUsers.objects.all()
    serializer_class = AdminUsersSerializer

@api_view(['POST'])
def create_admin_user(request):
    if request.method == 'POST':
        # Start transaction
        with transaction.atomic():
            serializer = AdminUsersSerializer(data=request.data)
            if serializer.is_valid():
                #Generate Password Hash
                password = serializer.validated_data['adminUserPassword']
                hashed_password = make_password(password)
                
                #Set hasedPassword to Data
                serializer.validated_data['adminUserPassword'] = hashed_password#hashed_password
                if serializer.is_valid():

                    serializer.save()
                    return Response(serializer.data,status=status.HTTP_201_CREATED)#serializer.data
            else:       
                return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)#serializer.data

# AdminLoginMethod
@api_view(['POST'])
def login_admin_user(request):
        if request.method == 'POST':
            print("Request Data:", request.data)  # デバッグ用にリクエストデータを表示

            # シリアライズしてバリデーション
            serializer = AdminUserLoginSerializer(data=request.data)

            # バリデーションエラーの場合
            if not serializer.is_valid():
                return Response(data={"message": "Invalid data provided."}, status=status.HTTP_400_BAD_REQUEST)
            
            try:
                # メールアドレスを使ってユーザーを一意に取得
                loginAdminUser = AdminUsers.objects.get(adminUserEmail=request.data['adminUserEmail'])
            except ObjectDoesNotExist:
                return Response(data={"message": "There is no admin information."}, status=status.HTTP_404_NOT_FOUND)

            # パスワード照合
            if check_password(request.data['adminUserPassword'], loginAdminUser.adminUserPassword):  # ここで adminUserPassword を確認
                # シリアライザーを使ってユーザー情報を返す
                user_data = {
                    "id": loginAdminUser.id,
                    "adminUserName": loginAdminUser.adminUserName,
                    "adminUserEmail": loginAdminUser.adminUserEmail,
                    # 必要に応じて他の情報を追加
                }
                return Response(data=user_data, status=status.HTTP_200_OK)
            else:
                return Response(data={"message": "Invalid credentials."}, status=status.HTTP_401_UNAUTHORIZED)

        

画面に返却するデータの制御

今回は①管理者名と②E-mailをレスポンスにしたいので、下記のようにする。

serializers.py
     class Meta:
        model = AdminUsers
        fields = ['adminUserName','adminUserEmail', 'adminUserPassword']  

さいごにSerializer.pyについてです。
こちらは、管理者登録とは少し作りが変わります。

AdminUserLoginSerializer関数の引数は、serializers.Serializerとなります。
引数をserializers.ModelSerializerにしてしまうと、serializerがcreate関数が呼び出してしまいデータベースに登録されてしまいます。

これでは困るので、引数をserializers.Serializerに設定します。

backend/api/serializers.py
from rest_framework import serializers
from .models import Todo,AdminUsers
from django.contrib.auth.hashers import make_password

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ['id', 'title', 'completed', 'attachment']  # Include necessary fields

class AdminUsersSerializer(serializers.ModelSerializer):
    class Meta:
        model = AdminUsers
        fields = ['id','adminUserName','adminUserEmail','adminUserPhone','adminUserPassword','adminUserCompany','adminUserDepartment']
        adminUserPassword = serializers.CharField(write_only=True)

        extra_kwargs = {
            'adminUserPassword':{'write_only':True}
        }
   
    def create(self, validated_data):
        # パスワードをハッシュ化
        password = validated_data['adminUserPassword']
        hashed_password = make_password(password)
        validated_data['adminUserPassword'] = hashed_password
    
        # 新しいユーザーを作成
        return super().create(validated_data)

class AdminUserLoginSerializer(serializers.Serializer):
    adminUserEmail = serializers.EmailField()  # メールアドレスを受け取る
    adminUserPassword = serializers.CharField(write_only=True)  # パスワードを受け取る

    def validate_adminUserEmail(self, value):
        # メールアドレスのバリデーション
        if not value:
            raise serializers.ValidationError("Email must be provided.")
        return value
    
    def validate_adminUserPassword(self, value):
        # パスワードのバリデーション
        if not value:
            raise serializers.ValidationError("Password must be provided.")
        return value
    
    

参考サイト

Python | Django | React | Todo webアプリの作成方法

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?