LoginSignup
6
7

【Django】仮登録ユーザーをEmail認証後にアクティベーションする実装ナレッジ

Last updated at Posted at 2022-05-27

はじめに

巷に出回っているアプリなどではユーザー登録されるときに、Emailによるアクティベーションを要求されることがあると思いますが、この記事ではDjangoでのその実装方法を記述していこうと思います

Djangoではデフォルトでユーザーモデルが定義されており、フィールドにis_activeというフィールドがデフォルトで定義されておりこちらを上手に使っていきます

Djangoの設定

PythonとDjangoは導入できている前提とします

通常の手順通りアプリを作成していきます

django-admin startproject [プロジェクト名]
django-admin startapp [アプリ名]

アプリ名はbasicappとしておきます

settings.py

INSTALLED_APPS = [
    'basicapp.apps.BasicappConfig',
]

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

from django.conf.urls import include

urlpatterns = [
    path('', include('basicapp.urls')),
    path('accounts/', include('django.contrib.auth.urls')),
]

accountsのurlはDjangoにデフォルトで組み込まれているログイン認証機能を使用します
用意されているパターンはこちら

accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']

アプリ側urls.pyの設定

basicapp/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)),

]

Email機能の設定

以下を記載するとコンソールにEmailを表示させることができる
これだけでEmail機能を簡単にデモすることができる

settings.py

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # コンソールにメール内容を表示する
EMAIL_HOST = ホスト情報
EMAIL_PORT = ポート情報
DEFAULT_FROM_EMAIL = デフォルト送信元
EMAIL_HOST_USER = ユーザー
EMAIL_HOST_PASSWORD = パスワード
EMAIL_USE_TLS = TLSを使うかどうか

例 
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL = 'hoge@hoge.com'
EMAIL_HOST_USER = 'hoge@hoge.com'
EMAIL_HOST_PASSWORD = 'password'
EMAIL_USE_TLS = True

なおメールを実際に飛ばす場合は以下の設定になる
settings.py

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # メールを送信する

カスタムユーザー定義

カスタムユーザー適用準備

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

settings.py

AUTH_USER_MODEL = 'basicapp.User'

カスタムユーザーの実装

カスタムユーザーを使用するためのマネージャークラスとカスタムユーザーのモデルを作ります
Djangoのデフォルトではユーザー名がログインに使用されますが、emailをユーザー名として利用するようにします(USERNAME_FIELD の部分で変更可能)
アカウント生成時はユーザー仮登録としたいのでis_activeをFalseにします

models.py

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

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


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

有効期限付きアクティベーション用トークン保持テーブルを実装

Email認証には特定のトークンをくっつけたURLをクリックすることでアクティベーションを行える実装としたいので、アクティベーショントークンを保持するテーブルを作ります

models.py

class UserActivateTokens(models.Model):

    token_id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    activate_token = models.UUIDField(default=uuid.uuid4)
    expired_at = models.DateTimeField()

    objects = UserActivateTokensManager()


class UserActivateTokensManager(models.Manager):

    def activate_user_by_token(self, activate_token):
        user_activate_token = self.filter(
            activate_token=activate_token,
            expired_at__gte=datetime.now() # __gte = greater than equal
        ).first()
        if hasattr(user_activate_token, 'user'):
            user = user_activate_token.user
            user.is_active = True
            user.save()
            return user

ユーザー生成時にトークンを発行しメール送信をする処理の実装

仮登録が完了すると同時にEmailが飛んでURLをクリックするとアクティベーションを行える処理を実装します
@receiverをつかったシグナリングの実装を行っています
今回ではsenderに設定したユーザーがPOSTメソッドで保存が実行されたときにアノテーション以下の処理が実行されます
Subjectは日本語で書くとなぜか文字化けします

from django.conf import settings
from datetime import datetime, timedelta
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def publish_activate_token(sender, instance, **kwargs):
    if not instance.is_active:
        user_activate_token = UserActivateTokens.objects.create(
            user=instance,
            expired_at=datetime.now()+timedelta(days=settings.ACTIVATION_EXPIRED_DAYS),
        )
        subject = 'Please Activate Your Account'
        message = f'URLにアクセスしてユーザーアクティベーション。\n http://127.0.0.1:8000/users/{user_activate_token.activate_token}/activation/'
    if instance.is_active:
        subject = 'Activated! Your Account!'
        message = 'ユーザーが使用できるようになりました'
    from_email = settings.DEFAULT_FROM_EMAIL
    recipient_list = [
        instance.email,
    ]
    send_mail(subject, message, from_email, recipient_list)

トークンの有効期限もsettingに設定します
3日間有効としています
settings.py

ACTIVATION_EXPIRED_DAYS = 3

View.pyの実装

アクティベーション用のURLが叩かれたらHTTPResponseを返す単純なViewを作ります
views.py

from .models import UserActivateTokens
from django.http import HttpResponse


def activate_user(request, activate_token):
    activated_user = UserActivateTokens.objects.activate_user_by_token(
        activate_token)
    if hasattr(activated_user, 'is_active'):
        if activated_user.is_active:
            message = 'ユーザーのアクティベーションが完了しました'
        if not activated_user.is_active:
            message = 'アクティベーションが失敗しています。管理者に問い合わせてください'
    if not hasattr(activated_user, 'is_active'):
        message = 'エラーが発生しました'
    return HttpResponse(message)

ulrsに追記

urls.py

from .views import activate_user

urlpatterns = [
    path('users/<uuid:activate_token>/activation/', activate_user, name='users-activation'),
]

Admin画面の設定

adminpy

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


class UserAdmin(BaseUserAdmin):
    ordering = ('id',)
    list_display = ('email', 'id', 'is_active', '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'),
        }),
    )


class UserActivateTokensAdmin(admin.ModelAdmin):
    list_display = ('token_id', 'user', 'activate_token', 'expired_at')



admin.site.register(User, UserAdmin)
admin.site.register(UserActivateTokens, UserActivateTokensAdmin)

動作確認

それではマイグレーションをまずはします

py manage.py makemigrations
py manage.py migrate
py manage.py runserver
py manage.py createsuperuser

その後、Superユーザー作成をした時にコンソールにEmailの内容が表示されるはずなので、URLをクリックしてアクティベーションされるか確認しましょう
URLをクリックする前はAdmin画面にログインできなかったと思いますが、クリック後は出来るようになっていればOKです

また、管理者画面でユーザーを追加しても同様にメールが飛ぶはずなので、URLをクリックしてis_actioveになるかを確認しましょう

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Subject: Please Activate Your Account
From: hoge@hoge.com
To: user1@user.com
Date: 
Message-ID: <>

URLにアクセスしてユーザーアクティベーション。
 http://127.0.0.1:8000/users/................/activation/
-------------------------------------------------------------------------------

確認出来たら成功です

より実用的なアプリにおける実装方法

より実用的なアプリにおけるEmailアクティベーションの実装方法を知りたい方はこちらの記事をご覧ください
マッチングを一から作りながら諸々の実装を解説しています

6
7
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
6
7