概要
ユーザのパスワードなどDjango内で独自のバリデーションを作成する方法について解説したいと思います
ファイル構成
今回作成するファイルの構成は以下の通りです
tree
・
└── application
├── models.py
├── utils
│ ├── __init__.py
│ └── password_validator.py
└── project
└── settings.py
- バリデーションを記載するファイル(今回はpassword_validator.py)
- settings.py
- models.py(カスタムユーザの作成)
に設定を記載していきます
password_validator.py
re.fullmatch()関数を使って指定した正規表現と完全に一致するかどうかを調べます
application/password_validator.py
import re
from rest_framework.exceptions import ValidationError
class PasswordValidator:
def validate(self, password, user=None):
REX = r"(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|(){}\[\]:;\"\'<>,.?/]).{8,64}"
result = re.fullmatch(REX, password)
if not result:
raise ValidationError("英大小文字、数字、または特殊文字が含まれていません")
正規表現の書き方
正規表現の書き方は以下の通りです
正規表現 | 説明 |
---|---|
?=.* | 一致するかどうか |
(\d) | 少なくとも1つの数字 |
([a-z]) | 少なくとも1つの小文字のアルファベット |
([A-Z]) | 少なくとも1つの大文字のアルファベット |
*[~!@#$%^&*_-+=`|(){}[]:;"'<>,.?/] | 少なくとも1つの特殊文字 |
{8,64} | 全体で8文字以上、64文字以下(.{8,64}) |
settings.py
AUTH_PASSWORD_VALIDATORSに該当するクラス名もしくはファイル名を記載します
project/settings.py
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
# 以下にカスタムバリデーションのクラス名を記載
{
"NAME": "application.utils.password_validator.PasswordValidator",
},
]
models.py
システムユーザ用のModelを作成します
詳細については以下の記事を参照してください
application/models.py
import uuid
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.validators import RegexValidator
from django.db import models
class User(AbstractUser):
"""システムユーザ"""
username_validator = UnicodeUsernameValidator()
class Role(models.IntegerChoices):
"""システムユーザのロール
Args:
MANAGEMENT(0): 管理者
GENERAL(1): 一般
PART_TIME(2): アルバイト
"""
MANAGEMENT = 0
GENERAL = 1
PART_TIME = 2
# 不要なフィールドはNoneにすることができる
first_name = None
last_name = None
date_joined = None
groups = None
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
db_comment="システムユーザID",
)
employee_number = models.CharField(
unique=True,
validators=[RegexValidator(r"^[0-9]{8}$")],
max_length=8,
# 管理者のログイン画面で社員番号と表示される
verbose_name="社員番号",
db_comment="社員番号",
)
username = models.CharField(
max_length=150,
unique=True,
validators=[username_validator],
db_comment="ユーザ名",
)
email = models.EmailField(
max_length=254,
unique=True,
db_comment="メールアドレス",
)
role = models.PositiveIntegerField(
choices=Role.choices,
default=Role.PART_TIME,
db_comment="システムユーザのロール",
)
created_at = models.DateTimeField(
auto_now_add=True,
db_comment="作成日",
)
updated_at = models.DateTimeField(
auto_now=True,
db_comment="更新日",
)
USERNAME_FIELD = "employee_number"
REQUIRED_FIELDS = ["email", "username"]
class Meta:
ordering = ["employee_number"]
db_table = "User"
db_table_comment = "システムユーザ"
def __str__(self):
return self.username
実際に検証してみよう!
python manage.py createsuperuser
を実行します
以下のように入力していきます
今回は数字だけのパスワードを入力します
社員番号: 00000001
Email: test@gmail.com
Password: 11111111
Password (again): 11111111
パスワード内に英大小文字、特殊文字が含まれていないため、以下のようにValidationErrorが表示されたら成功です
File "/code/application/utils/password_validator.py", line 51, in validate
raise ValidationError("英大小文字、数字、または特殊文字が含まれていません")
rest_framework.exceptions.ValidationError: [ErrorDetail(string='英大小文字、数字、または特殊文字が含まれていません', code='invalid')]
他のパターンも気になる方は以下のようにテストコードで検証してみましょう
application/tests/test_password_validation.py
import pytest
from application.utils.validator import PasswordValidator
from rest_framework.exceptions import ValidationError
@pytest.mark.django_db()
class TestPasswordValidator:
def test_user_password_does_not_contain_non_alphanumeric_characters(self):
"""異常/パスワードに不英数文字が含まれていない"""
with pytest.raises(ValidationError):
PasswordValidator.validate(self, password="TestingUsr01")
def test_user_password_does_not_contain_numbers(self):
"""異常/パスワードに数字が含まれていない"""
with pytest.raises(ValidationError):
PasswordValidator.validate(self, password="T@stingUsr")
def test_user_password_does_not_contain_uppercase_letters(self):
"""異常/パスワードに大文字が含まれていない"""
with pytest.raises(ValidationError):
PasswordValidator.validate(self, password="t@stingusr01")
def test_user_password_is_less_than_7_characters(self):
"""異常/パスワードが7文字以下"""
with pytest.raises(ValidationError):
PasswordValidator.validate(self, password="T@stus1")
def test_user_password_is_65_characters_or_longer(self):
"""異常/パスワードが65文字以上"""
with pytest.raises(ValidationError):
PasswordValidator.validate(self, password="a" * 65)
参考