User
名前はCustomUserとし、単純のためBaseUserManagerを継承することとします。
だだ含まれるフィールドが受け継がれてしまうので、すべてカスタムしたい場合はAbstractBaseUserを継承しましょう。
settings.pyにUserモデルの指定を追加する
AUTH_USER_MODEL = 'accounts.CustomUser'
Manager
これはデータベースにアクセスするためのものです。Userのobjectsにオーバーライドします。
ManagerもデフォルトのUserManagerでなくCustomUserManagerに変更します。
CustomUserManagerはBaseUserManagerを継承します。
ベースはUserManagerからcreate_superuser, create_user, _create_userをコピペして修正しましょう。
_create_userの修正点として、外部参照であるDepartment, UserPowerにデフォルト値を設定します。
上記にはあらかじめデータを作成しておくこと。
必須項目であるemployee_nameも入力チェックをしたいのでextra_fieldsの中をチェックしてエラーを出します。
またpasswordの入力がない場合も、エラーを出すようにします。
なぜかデフォルトのmake_password関数ではパスワード無しでもエラーにならず、
Noneが渡されることでログインできないパスワードが作成されてしまう可能性があります。
なぜこの仕様になっているのかよくわかりません。
Noneが渡されると!SamgQKLYDaqs87093・・のようなパスワードが生成され、
デフォルトハッシュアルゴリズムのpbkdf2_sha256のハッシュ、ストレッチ回数、ソルトを含むパスワードにならないです。
class Department(models.Model):
code = models.IntegerField("コード", unique=True)
name = models.CharField("名称", max_length=50)
def __str__(self) -> str:
return f"{self.code} - {self.name}"
class UserPower(models.Model):
code = models.IntegerField("コード", unique=True)
name = models.CharField("名称", max_length=50)
def __str__(self) -> str:
return f"{self.code} - {self.name}"
class CustomUserManager(BaseUserManager):
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if extra_fields.get('employee_name') is None:
raise ValueError("employee_name must be set")
if not username:
raise ValueError("The given username must be set")
if not password:
raise ValueError("The given password must be set")
extra_fields.setdefault("department", Department.objects.get(code=0))
extra_fields.setdefault("power", UserPower.objects.get(code=0))
email = self.normalize_email(email)
GlobalUserModel = apps.get_model(
self.model._meta.app_label, self.model._meta.object_name
)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.password = make_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False)
return self._create_user(username, email, password, **extra_fields)
def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
if extra_fields.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if extra_fields.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
return self._create_user(username, email, password, **extra_fields)
class CustomUser(AbstractUser):
employee_name = models.CharField("社員名", max_length=50)
department = models.ForeignKey(
"Department", verbose_name="部署", on_delete=models.PROTECT
)
power = models.ForeignKey(
"UserPower", verbose_name="権限", on_delete=models.PROTECT
)
entry_date = models.DateField("入社日", null=True)
REQUIRED_FIELDS = ["employee_name"]
objects = CustomUserManager()
管理画面の編集
管理画面では必須項目や外部参照キーが入力できないので登録するとエラーになります。
ユーザー追加画面項目を定義しているadd_fieldsetsに追加します。
class CustomUserAdmin(UserAdmin):
list_display = ["id", "username", "employee_name", "department", "power", "is_superuser", "is_staff"]
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": (
"username",
"password1",
"password2",
"employee_name",
"department",
"power",
),
},
),
)
admin.site.register(CustomUser, CustomUserAdmin)
いろいろハマったけど、authenticateやパスワードハッシュの仕様について勉強になった。
複数バックエンドを用意して順番に認証していくこともできるし、パスワードハッシュの変更の仕組みもあるようだ。