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】でメール送信を実装してみる

Posted at

VSCodeにフロント側Next.jsから登録済みのメールアドレスにワンタイムパスワード付きのメールをDjangoに送信してもらうコードサンプルです。
##フロントエンドの実装

NextJSDjangoProject/frontend/generalUsers/regeneratePassword/page.tsx
'use client'
import { useState } from "react"
import { useRouter } from "next/navigation"
import { useForm } from "react-hook-form"
import axios from "axios"
import {
    Button,
    Dialog,
    DialogHeader,
    DialogBody,
    DialogFooter
} from "@material-tailwind/react"

export default function RegeneratePassword(){
    const router = useRouter();
    const defaultValues = {
        Email:''
    }
    const clickBackToTopPage =()=>{
        router.push("/");
    }
    const {register,handleSubmit,formState:{errors}} = useForm({
        defaultValues
    });

    function isValidEmail(email:string):boolean{
        const emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
        return emailRegex.test(email);
    }
    
    const [email,SetEmail] = useState('');
    const [modalVisible,setModalVisible] = useState(false);
    const [modalMessage,setModalMessage] = useState('');

    const onSubmit = async(data:any)=>{
        if(!isValidEmail){
            //Modal Daialogを表示
            setModalMessage('Your email is required or invalid. Please input a valid email.');
            setModalVisible(true);
            return
        }

        try{
            const formData = new FormData();
            formData.append('generalUserEmail',email);
            const response = await axios.post('http://127.0.0.1:8000/generalusers/sendGeneralUserEmailToChangePassword/',formData);

            if(response.status !== 200){
                setModalMessage('Email has not been registered.');
                setModalVisible(true);
                return
            }
            
            setModalMessage('A regenerated password has been sent to your registered email. Please check it.');
            setModalVisible(true);
        }catch(error){
            console.log('Error:',error.message);
            setModalMessage('An error occurred while sending your request. Please try again later.');
            setModalVisible(true);
        }
    }

    return (
        <main>
        <div className="flex items-center justify-center h-screen">
            <form onSubmit = {handleSubmit(onSubmit)} className="border rounded px-4 py-4">
                <label>Email</label>
                <input
                    type="email"
                    className="w-full border border-gray-500 rounded shadow apperance-none  focus:shadow-outline text-gray-700 leading-tight px-3 py-2"
                    defaultValue={defaultValues.Email}
                    {...register('Email',{
                        required:'Email is required.'
                    })}
                    placeholder="Sample@email.com"
                    onChange={(e)=>SetEmail(e.target.value)}
                />
                <div className="text-rose-500">{errors.Email?.message}</div>
                <div>
                    <button
                        type="submit"
                        className="bg bg-blue-500 rounded text-white font-bold hover:bg-blue-700 cursor-pointer px-4 py-4 mr-4"
                    >
                        Send
                    </button>
                    <button
                        type="button"
                        className="bg bg-green-500 rounded text-white font-bold hover:bg-green-700 cursor-pointer px-4 py-4"
                        onClick={()=>clickBackToTopPage()}
                    >
                        BackToPrePage
                    </button>
                </div>
            </form>
        </div>
            {/*--- Modal Dialog ---*/}
            <Dialog open={modalVisible} size="sm" handler={()=>setModalVisible(false)}>
                <DialogHeader className="w-full text-lg font-semibold">Notice</DialogHeader>
                <DialogBody className="w-full text-gray-700">{modalMessage}</DialogBody>
                <DialogFooter className="w-full">
                    <Button
                        onClick={() => setModalVisible(false)}
                        className="bg bg-blue-500 rounded text-white font-bold px-4 py-4 hover:bg-blue-700 cursor-pointer"
                    >
                        OK
                    </Button>
                </DialogFooter>
            </Dialog>
        </main>
    )
}

サーバサイドの実装

VSCodeのコンソールで試したいときは、settings.pyに下記を追加します。

NextJSDjangoProject/backend/todoproject/settings.py
# ローカル開発環境用:メールをターミナルに表示
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

つぎに、views.pyにメール送信の処理を追加します。

NextJSDjangoProject/backend/generateUsers/views.py
#from django.shortcuts import render
from .models import GeneralUsers
from .serializers import GeneralUsersSerializer
from rest_framework import viewsets
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 django.core.exceptions import ObjectDoesNotExist #Add
from django.contrib.auth.hashers import check_password #Add
from .serializers import GeneralUsersSerializer
import secrets #追加
import string #追加
from django.core.mail import send_mail # 追加
import logging
# Create your views here.

class GeneralUsersViewSet(viewsets.ModelViewSet):
    queryset = GeneralUsers.objects.all()
    serializer_class = GeneralUsersSerializer

@api_view(['POST'])
def create_general_user(request):
    if request.method == 'POST':
        with transaction.atomic():
            serializer = GeneralUsersSerializer(data=request.data)
            if serializer.is_valid():
                password = serializer.validated_data['password']
                hashed_password = make_password(password)

                serializer.validated_data['generalUserPassword'] = hashed_password
                if serializer.is_valid():
                    serializer.save()
                    return Response(serializer.data,status=status.HTTP_201_CREATED)
                else:
                    return Response(serializer.errors,status=status.HTTP_500_INTERNAL_SERVER_ERROR)
                

@api_view(['PUT'])
def update_general_user(request):
    try:
        general_user_id = request.data.get('generalUserId')
        image_url = request.data.get('generalUserImage')

        user = GeneralUsers.objects.get(generalUserId=general_user_id)
        user.generalUserImage = image_url
        user.save()

        serializer = GeneralUsersSerializer(user)
        return Response(serializer.data,status=status.HTTP_200_OK)
    except GeneralUsers.DoesNotExist:
        return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
    except Exception as e:
        return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#ランダムなパスワードを生成する処理
def generate_random_password(length = 10):
    characters = string.ascii_letters + string.digits
    return ''.join(secrets.choice(characters) for _ in range(length))

#パスワード変更画面から登録済Emailあてにメール送信
@api_view(['POST'])
def send_general_user_email_to_change_password(request):
    try:
        general_user_email = request.data.get('generalUserEmail')

        #Emailが空文字の場合処理を終了
        if not general_user_email:
            return Response({'error':'Your Password is required.'},status=status.HTTP_404_NOT_FOUND)
        
        #ブラウザから取得したEmailから一意のデータを取得する
        user = GeneralUsers.objects.get(generalUserEmail = general_user_email)
        print('Userは',user)
        #ランダムなパスワードを生成
        new_plain_password = generate_random_password()

        #ハッシュ化して保存
        user.generalUserPassword = make_password(new_plain_password)
        #コンソールデバッグ
        print(f"Generated password: {new_plain_password}")
        user.save()

        #メールにはハッシュ化される前のパスワードを送信する
        
        send_mail(
            subject='【重要】新しいパスワードのお知らせ',
            message = f'新しいパスワードは以下の通りです。\n\n{new_plain_password}\n\nログイン後、パスワードの変更をおすすめします。',
            from_email='noreply@example.com',
            recipient_list=[general_user_email],
            fail_silently=False,
        )
        

        return Response({'message': 'Password has been reset and sent to your email.'},status=status.HTTP_200_OK)
    except Exception as e:
        return Response({'error':str(e)},status=status.HTTP_500_INTERNAL_SERVER_ERROR)

さいごに、urls.pyviews.pyの関数を追加しておきます。

NextJSDjangoProject/backend/generateUsers/urls.py
from django.urls import path,include
from .views import GeneralUsersViewSet #Add
from rest_framework.routers import DefaultRouter
#from .views import GeneralUsersViewSet
from .views import update_general_user #Add
from .views import send_general_user_email_to_change_password #Add

router = DefaultRouter()
router.register('registergeneralusers',GeneralUsersViewSet,basename='create_general_user')

urlpatterns =[
    path('updategeneralusers/', update_general_user),
    path('sendGeneralUserEmailToChangePassword/',send_general_user_email_to_change_password),
    path('',include(router.urls))
]

サイト

Material Tailwind

SMTODevメール

Pythonの複数行のコメントアウト

正規表現

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?