Django

【Django】カスタムユーザとカスタムコマンドを作成し、ワンライナーでスーパーユーザを作成する

さて、今回はDjangoのカスタムユーザを作成したあと、カスタムコマンドでワンライナーでスーパーユーザを作成するまでの備忘録です。

以下のリポジトリに作成した環境は残しておきます。

mocrat-setup-sample

大まかなディレクトリツリーは以下のとおりです。(一部省略)

  ubuntu@ThinkPad:~/mocrat$ tree
  .
  ├── Makefile
  ├── README.md
  ├── docker-compose.yml
  └── main
      ├── Dockerfile
      ├── entrypoint.sh
      ├── mocrat
      │   ├── manage.py
      │   ├── mocrat_config
      │   │   ├── __init__.py
      │   │   ├── asgi.py
      │   │   ├── local_settings.py
      │   │   ├── production_settings.py
      │   │   ├── settings.py
      │   │   ├── urls.py
      │   │   └── wsgi.py
      │   ├── mocrat_user
      │   │   ├── __init__.py
      │   │   ├── admin.py
      │   │   ├── apps.py
      │   │   ├── management
      │   │   │   └── commands
      │   │   │       └── custom_create_superuser.py
      │   │   ├── migrations
      │   │   │   └── __init__.py
      │   │   ├── models.py
      │   │   ├── tests.py
      │   │   └── views.py
      └── requirements.txt

Djangoのカスタムユーザーをつくる

Djangoのベストプラクティスとして、デフォルトのユーザーではなくカスタムユーザーを利用するのが良いそうです。
これは、デフォルトのUserモデルでは、あとでUserの内容をカスタマイズする必要が発生したときに柔軟に対応できないためだそう。

詳しくは下記を参照すると書かれています。

Djangoでは常にカスタムUserを使用すべき

あとは、公式ドキュメントにもカスタムユーザーについて詳しく書かれています。

とはいえ、面倒な設定は不要です。
デフォルトのUserモデルの設定を、そのままカスタムユーザーとしてコピペしてしまいましょう。

まずは、カスタムユーザー用のアプリをmanage.pyのstartappコマンドで作成します。
もちろん、SettingsのINSTALL_APPに追記するのも忘れずに。

作成したアプリケーションのmodels.pyに以下のコードを貼り付け。

#mocrat_user/models.py

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.core.mail import send_mail
from django.contrib.auth.base_user import BaseUserManager

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)
        username = self.model.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_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, password, **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 User(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=30, 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']

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        #abstract = True # ここを削除しないといけないことを忘れない!!!!!!!!!!

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def get_full_name(self):
        """
        Return the first_name plus the last_name, with a space in between.
        """
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.first_name

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)

次に、admin.pyに以下のコードを貼り付け。

#mocrat_user/admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)

これでカスタムユーザーが作成できるようになりました。

あとは、manage.pyのmakemigrationsとmigrateでデータベースを反映させましょう。

カスタムコマンドを使う

さて、これでユーザー用のモデルが作成できたわけですが、正直スーパーユーザを作成するときに毎回情報を入力するのが面倒です。
そこで、create_superuserコマンドもカスタムしてしまいましょう。

実は、Djangoはアプリケーションディレクトリにmanagement/commandsというディレクトリを配置すると、その配下のPythonファイルをカスタムコマンドとして実行できるようになります。
カスタムコマンドは、python3 manage.py [カスタムコマンド]といった形で使用可能になります。

作成するには、以下のmanagement以下の構成を作成します。

mocrat_user
├── __init__.py
├── admin.py
├── apps.py
├── management
    └── commands
        └── custom_create_superuser.py

このcustom_create_superuser.pyは、以下のスクリプトを書き込みます。
こちらのサイトで紹介されていたスクリプトのタイポを直したものになります。

from django.contrib.auth.management.commands import createsuperuser
from django.core.management import CommandError


class Command(createsuperuser.Command):
    help = 'Create a superuser with a password non-interactively'

    def add_arguments(self, parser):
        super(Command, self).add_arguments(parser)
        parser.add_argument(
            '--password', dest='password', default=None,
            help='Specifies the password for the superuser.',
        )

    def handle(self, *args, **options):
        options.setdefault('interactive', False)
        username = options.get('username')
        email = options.get('email')
        password = options.get('password')
        database = options.get('database')

        if not (username and email and password):
            raise CommandError('--username, --email and --password are required options')

        user_data = {
            'username': username,
            'email': email,
            'password': password,
        }

        exists = self.UserModel._default_manager.db_manager(database).filter(username=username).exists()
        if not exists:
            self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)

ちなみに、カスタムユーザを作成した場合は、Settingsに以下のようなAUTH_USERモデルを追記しないと、SuperUserが正しく動いてくれないので注意です。

AUTH_USER_MODEL = 'mocrat_user.User'

最後に、ワンライナーでスーパーユーザを作成できるように、Makefileのコマンドを作成します。
各変数の部分は適当に変更してください。

username=admin
email=sample@testmail.org
passwd=Passw0rd
create_custom_superuser: ## This is create super user for 1line, need name,email,passwd(default=admin,sample@testmail.org,Passw0rd)
	docker-compose ${run} ${name} sh -c "python3 ./mocrat/manage.py custom_create_superuser --username ${username} --email ${email} --password ${passwd}"

まとめ

以上で、Django環境でカスタムユーザを作成し、カスタムコマンドでスーパーユーザを作成するところまで実装できました。

COMMENT

メールアドレスが公開されることはありません。