はじめに
巷に出回っているアプリなどではユーザー登録されるときに、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アクティベーションの実装方法を知りたい方はこちらの記事をご覧ください
マッチングを一から作りながら諸々の実装を解説しています