見出し画像

[Lightsail Django]No8 Userモデル

django-allauthを使用すると、簡単な設定でログイン画面、サインアップ画面、パスワード変更画面などの機能が利用できるようになりますが、DjangoのUserモデル周りの構成を理解しておかないと、django-allauthの設定で度々躓くことがありましたので、Userモデル周りについて整理しました。


1.Userモデル系

Userモデルは、passwordやemail、名前などのユーザー情報を保持するモデル(テーブル)です。
djangoではUserモデルをそのまま使用せずに、拡張しやすいようにカスタマイズされたUserモデル(別途自作アプリのModels.pyにカスタマイズ用としてCustomUserモデルを作るイメージ)の使用が推奨されており、django-allauthの設定手順でも、CustomUserを用意する手順がありますので、カスタマイズする際の補足についても合わせて説明して行きます。

1-1.User

UserはDjangoで標準で用意されているUserモデルで、UserはAbstractUserを継承しています。

自前で作るCustomUserには、AbstractUserまたは、AbstractBaseUserとPermissionsMixinを継承していくイメージとなります。

class User(AbstractUser):
    """
    Users within the Django authentication system are represented by this
    model.

    Username and password are required. Other fields are optional.
    """

    class Meta(AbstractUser.Meta):
        swappable = "AUTH_USER_MODEL"

1-2.AbstractUser

AbstractUserはAbstractBaseUser, PermissionsMixinを継承しています。

AbstractUserにはプロフィール項目が多く用意されており、上記Userモデルから継承されています。(Userモデルと同じラインナップ)
ここから更に項目を増やしたい場合は、自前で作るCustomUserに、AbstractUserを継承させます。

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """

    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _("username"),
        max_length=150,
        unique=True,
        help_text=_(
            "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
        ),
        validators=[username_validator],
        error_messages={
            "unique": _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_("first name"), max_length=150, blank=True)
    last_name = models.CharField(_("last name"), max_length=150, blank=True)
    email = models.EmailField(_("email address"), blank=True)
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_("Designates whether the user can log into this admin site."),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."
        ),
    )
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email"]
 
# ...

1-3.AbstractBaseUser

AbstractBaseUserは大元のModelで、AbstractUserと比べるとプロフィール項目は少なく、項目が少ない状態からカスタイズしたい場合、CustomUserにはAbstractBaseUserを継承させます。

class AbstractBaseUser(models.Model):
    password = models.CharField(_("password"), max_length=128)
    last_login = models.DateTimeField(_("last login"), blank=True, null=True)

    is_active = True

    REQUIRED_FIELDS = []

    # Stores the raw password if set_password() is called so that it can
    # be passed to password_changed() after the model is saved.
    _password = None

    class Meta:
        abstract = True

    def __str__(self):
        return self.get_username()

 # ...

1-4.PermissionsMixin

PermissionsMixinも大元のModeで、AbstractBaseUserと共に継承されて使用される形となります。
こちらのモデルにはis_superuserフィールドを保持しており、管理画面にログインできるユーザーの条件として、is_staff=True、is_superuser= Trueとあるため、このモデルは継承が必要となります。

class PermissionsMixin(models.Model):
    """
    Add the fields and methods necessary to support the Group and Permission
    models using the ModelBackend.
    """

    is_superuser = models.BooleanField(
        _("superuser status"),
        default=False,
        help_text=_(
            "Designates that this user has all permissions without "
            "explicitly assigning them."
        ),
    )
    groups = models.ManyToManyField(
        Group,
        verbose_name=_("groups"),
        blank=True,
        help_text=_(
            "The groups this user belongs to. A user will get all permissions "
            "granted to each of their groups."
        ),
        related_name="user_set",
        related_query_name="user",
    )
    user_permissions = models.ManyToManyField(
        Permission,
        verbose_name=_("user permissions"),
        blank=True,
        help_text=_("Specific permissions for this user."),
        related_name="user_set",
        related_query_name="user",
    )
 # ...

2.UserManagerモデル系

2-1.UserManager

UserManagerは、上記AbstractUserからコールされます。

create_userメソッドは、ユーザーを新規作成した時にコールされるイメージで、
is_staff=False、is_superuser=Falseで設定されます。

create_superuser:createsuperuser時にコールされるイメージで、
is_staff=True、is_superuser=Trueで設定されます。

管理画面にログインできるユーザーの条件として、is_staff=True、is_superuser= Trueとあるため、このようなメソッドで区分けされているかと思われます。

その他の処理は各種項目の加工処理なので、自前のCustomUserに独自項目を追加した際に、項目の加工処理などが必要であればUserManagerやBaseUserManagerを自作のCustomUserManagerから継承しつつ、独自項目の加工処理を追加していくイメージになるかと思います。

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        """
        Create and save a user with the given username, email, and password.
        """
        if not username:
            raise ValueError("The given username must be set")
        email = self.normalize_email(email)
        # Lookup the real model class from the global app registry so this
        # manager method can be used in migrations. This is fine because
        # managers are by definition working on the real model.
        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)

2-2.BaseUserManager

class BaseUserManager(models.Manager):
    @classmethod
    def normalize_email(cls, email):
        """
        Normalize the email address by lowercasing the domain part of it.
        """
        email = email or ""
        try:
            email_name, domain_part = email.strip().rsplit("@", 1)
        except ValueError:
            pass
        else:
            email = email_name + "@" + domain_part.lower()
        return email

    def get_by_natural_key(self, username):
        return self.get(**{self.model.USERNAME_FIELD: username})

その他

その他、amazon LightsailにてDjangoを使用したWEBアプリ構築については以下となります。(こちらの記事は以下記事の続きとなります。)



この記事が気に入ったらサポートをしてみませんか?