DjangoでUserモデルのカスタマイズ

2018-10-21 / PythonDjango

概要

Djangoで、会員登録機能を自作するシリーズの1つです。DjangoのデフォルトのUserモデルでは、ユーザー名を表すusernameというフィールドがあります。しかし、Webアプリによってはユーザー名としてメールアドレスを利用することも多く、usernameというフィールドが邪魔になることもあります。今回はusernameフィールドをなくし、emailフィールドをメインに扱うモデルを作成します

継承を使う際、大まかに2つの方法があります。

AbstractUserはデフォルトのUserモデルが持っている機能を全て持っており、デフォルトUserに少し手を入れたい場合に向いています。よくやるのは、emailフィールドを必須にしてみたり、電話番号や住所といったフィールドの追加です。

AbstractBaseUserは最低限な機能だけを持っています。AbstractUserが持っているフィールドの幾つかを消したり、大きな変更が必要な場合は、こちらの継承がオススメです。今回使うのもこちらです。

カスタムユーザーモデルの作成

models.pyを、以下のように変更します。 AbstractUserのソースコードを殆ど借用しつつ、usernameの代わりにemailを使うようにしています。

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


class UserManager(BaseUserManager):
    """ユーザーマネージャー."""

    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """メールアドレスでの登録を必須にする"""
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)

        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        """is_staff(管理サイトにログインできるか)と、is_superuer(全ての権限)をFalseに"""
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        """スーパーユーザーは、is_staffとis_superuserをTrueに"""
        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(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    """カスタムユーザーモデル."""

    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, 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 = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    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)

    @property
    def username(self):
        """username属性のゲッター

        他アプリケーションが、username属性にアクセスした場合に備えて定義
        メールアドレスを返す
        """
        return self.email

カスタムユーザーモデルをデフォルトに設定

自分で作ったUserモデルをデフォルトで使用するように宣言する必要があります。 settings.pyに、以下を付け加えましょう。INSTALLED_APPSにモデルを定義したアプリケーションを追加するのも忘れないでください。

# registerというアプリケーションです
AUTH_USER_MODEL = 'register.User'

管理画面用に設定する

自作のログイン画面等を使う場合は問題ないのですが、デフォルトのadmin管理サイトでこのカスタムユーザーを使う場合はadmin.pyで少し処理を加える必要があります。複雑に見えるかもしれませんが、これも 元々のソースコードとほぼ同じです。元々のUserモデルが使っていたusernameをemailに置き換え、使用しているFormもカスタムユーザーに合わせたものにしているだけです。

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'


class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)


class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'first_name', 'last_name')
    ordering = ('email',)


admin.site.register(User, MyUserAdmin)

この記事の関連記事

Djangoで会員登録機能を自作するシリーズ

2018-10-19 / PythonDjangoシリーズ・まとめ

- Djangoで会員登録機能を自作していきます。メールアドレスをユーザー名として使うようにし、ログイン画面、仮登録、メールクリックで本登録、ユーザー情報変更ページ、パスワード変更ページ、パスワードを忘れた際の再設定...などなど、よくある一連の機能を実装します。

Django、Userモデルのカスタマイズ(OneToOne)

2018-10-22 / PythonDjango

- Djangoで、会員登録機能を自作するシリーズの1つです。Djangoのユーザーモデルを拡張します。今回はOneToOneでUserにProfile等のモデルを紐づけ、機能を追加していきます。

コメント欄

記事にコメントする

名無し

こんばんは、webアプリ初心者・django初心者ですが、いつも参考にさせていただいています。

この記事を見て私もAbstractBaseUserを継承する方法でカスタムユーザーを定義したのですが、多言語対応させた場合、メールアドレスやユーザーネームなどのフィールドのラベルは多言語対応できる一方、自分で定義していない「パスワード」フィールドは多言語対応できません。

ログインフォームのラベルなどの「パスワード」の文字を多言語対応させるにはどうすればよいのでしょうか?

コメントに返信する

なりと

結論から言うと、自分で定義していないフィールドの翻訳も上書きすることができます。

翻訳ファイルは大雑把に言うと、次の3つの場所に置くことができますし、置かれています。

  1. settings.pyLOCALE_PATHSが指している場所

    この記事で言うところのプロジェクト直下のlocale

  2. 各アプリケーション内にあるlocaleディレクトリ

    authなどの組み込みのDjangoアプリケーション内にもlocaleがあります。

  3. django/conf/locale

    Django全体で使われる基本翻訳。例えば曜日の翻訳文章などはここで。

上から順番に翻訳が探されます。

今回はパスワードフィールドを上書きしませんでしたので、パスワード文字列はauthアプリケーション内の翻訳が使われています。なので、↑の例ならばプロジェクト直下のlocale内でパスワードの翻訳を上書きすれば解決します。

authアプリケーション内では、パスワードは基本的に_('Password')というmsgidが降られていますので、プロジェクト直下などのlocaleにて次のような定義を足し、compilemessagesをしてください。

msgid "Password"
msgstr "ぱっすわーど"

また、settings.py'django.middleware.locale.LocaleMiddleware'を設定することも忘れないようにしてください。

名無し

返信遅くなりました。ありがとうございます。 ちなみに、組み込みのauthアプリはどこに存在するのでしょうか?見つけられなかったので質問させて頂きたいです。

なりと

django/contrib/authにあります。djangoをpipでインストールしていると思いますので、django自体はsite-packagesに格納されています。そのsite-packagesの場所は環境によって変わります。

authアプリケーションの中身を見たいというだけであれば、Githubのソースコードから探したほうが楽です。

名無し

ありがとうございます。非常にわかりやすく、参考になりました。

名無し

何度もすみません。

教えていただいた通りプロジェクトのlocaleディレクトリでauthのlocaleを上書きしてコンパイルしてみたところ、コンパイル後にすべての文章がコメントアウトされてしまい、うまくいきませんでした。(下記のようになりました)

#~ msgid "nov"
#~ msgstr "十一月"

#~ msgid "dec"
#~ msgstr "十二月"

#~ msgctxt "abbrev. month"
#~ msgid "Jan."
#~ msgstr "一月"

これは何か書き方が間違っているのでしょうか?

名無し

原因がわかったので報告しておきます。わたしはプロジェクト直下のlocaleでauth等の組み込みのlocaleを上書きした後、makemessagesコマンドを走らせていたのですが、その際にdjango.poに勝手に自分で追加した分がコメントアウトされてしまっていたようです。
djangoには--clear-obsoleteオプションがなかったので、makemessagesを走らせた後に自分でコメントアウトを外し、コンパイルすることで解決しました。
私がこれほど面倒なことをしなければならなかったのは、urlの簡略化のため自分で言語コードを定義しなおしたためです。
なので普通の人ならこのような問題に直面することなく、自動的にdjangoの用意したlocaleの中の翻訳文が適用されるのだろうと思いました。
この情報がこのサイトを見る人の助けになれば幸いです。

名無し

参考になりました

コメントに返信する

名無し

参考になりました

コメントに返信する