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やパスワードハッシュの仕様について勉強になった。
複数バックエンドを用意して順番に認証していくこともできるし、パスワードハッシュの変更の仕組みもあるようだ。