ホーム >  Python > Django >  DjangoのUserモデルを継承してカスタマイズ

投稿日:   |  最終更新日:

DjangoのUserモデルを継承してカスタマイズ

DjangoPython

Djangoが標準で持つUserモデルを継承してカスタマイズします。

Userモデルの継承

前回、Userモデルに追加情報(gender、tel)を与えました。今回は、見栄えの良い方法として、継承を利用してUserモデルをカスタマイズします。

わざわざ継承を利用する理由

DjangoのデフォルトのUserモデルには、usernameというフィールドがあります。しかし、Webアプリによってはusernameとしてemailを利用したい場合があります。今回は、邪魔なusernameフィールドを消してemailをメインに扱うモデルを作成します。

どのモジュールを継承するか?

Djangoでは、Userモデルの挙動を継承して変更する際、以下の2つのモジュールを使用する方法があります。

  • django.contrib.auth.base_user.AbstractBaseUser
  • django.contrib.auth.models.AbstractUser

簡単に継承する場合は、AbstractUserを使いますが、実際開発で使う場合はAbastractBaseUserを継承してUserモデルを作ります。AbstractBaseUserの継承は色々と面倒ですが、挙動を柔軟にカスタマイズできます。

AbstractBaseUserを継承してUserモデルを作るときの条件

カスタムUserモデルが標準のUser Modelの機能を満たすためには下記の事項を満たす必要があります。

  • Modelがユニークなキーを持っていること。(usernameあるいはemailなど)
  • Modelがユニークなフィールド(username,email,,,etc)を持っていること。
  • get_full_name(),get_short_name()を実装していること。

カスタマイズ手順

以下の作業手順ですすめます。

  1. usersという名前のアプリケーションを作成
  2. usersアプリのmodels.pyにAbstractBaseUserを継承したUserクラスを作成
  3. usersアプリのadmin.pyに UserAdmin を継承したクラスを作成
  4. プロジェクトのsettings.pyに「AUTH_USER_MODEL = ‘users.User’」を定義
  5. django.contrib.admin、django.contrib.authへの影響範囲を調べ、必要であれば他のクラスも継承して独自に修正

usersという名前について

「users」はcookiecutter-djangoでも使われている名前です。こちらで統一します。また、userアプリケーションに独立させることにより、他プロジェクトでの再利用したり、アプリケーション単位でダンプファイル操作が可能になるなどのメリットがあります。

カスタムUserを使う場合の注意点

カスタムUserを使うには、Djangoプロジェクト開始時に設定しましょう。migrationsのinit(0001)以降でカスタムUserを設定すると、DBの整合性が取れなくなりmigirateが失敗します。特に他のmodelからUserを参照している場合などには、途中から参照を変える事ができません。

※すでにmigrateしている場合は、DBを初期化してmakemigrationsし直します。

公式チュートリアルにカスタムUserについての説明があります。

Django の認証方法のカスタマイズ

最初からカスタムUserを使用すべき

プロジェクトの開始時にはデフォルトUserで問題なくても、途中からUserに新しい情報を追加したくなる場合があります。こういう場合に備えて、プロジェクトの最初からカスタムUserを使用しておくことをおすすめします。

今回の変更

  • usernameの代わりにemailを使います。
  • AbstractUserからusername の必須を外します。emailを必須かつユニークに変更します。
  • usernameは消します。
  • Djangoの管理画面へのログインも、usernameではなくemailとパスワードで行います。

環境

OS CentOS 7.1.1503
pyenv 1.1.3-5-g7dae197
Anaconda 3-4.3.0
MariaDB 5.5.52-1.el7
Apache 2.4.6
mod_wsgi 4.5.14
Django 1.11.3

models.py

Userモデルの継承してカスタマイズします。usernameの代わりにemailを使うようにしています。

①以下のようなDjangoアプリ構成を作ります。プロジェクト名が「pj1」で、アプリケーション名が「users」です。プロジェクトの設定は済ませたものとします。赤字のファイルは、今回変更する部分です。

  • pj1/
  • pj1/
  • __init__.py
  • settings.py
  • urls.py
  • wsgi.py
  • users/
  • __init__.py
  • admin.py
  • apps.py
  • urls.py
  • views.py
  • models.py
  • manage.py

②usersディレクトリ配下のmodels.pyを編集します。

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):
        """Create and save a user with the given username, email, and
        password."""
        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):
        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):
        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)

解説

AbstractUserのソースコードを元に作成しました。

models.py

class UserManager(BaseUserManager):

UserManagerクラスは、ユーザ名、メールアドレス、パスワードに関するメソッドを提供するBaseUserManagerを継承します。

use_in_migrations = True

クラスをRunPython操作で利用できるようにします。

def _create_user(self, email, password, **extra_fields):

与えられたユーザ名、電子メール、およびパスワードでユーザーを作成して保存する関数です。(※今回はユーザ名を省くためにオーバーライドします。)

_create_user関数とは?

_create_user関数は、内部関数です。UserManagerクラスには、3つの関数(_create_user、reate_user、create_superuser)があります。また、create_userおよびcreate_super_userメソッドが_create_user関数を呼び出していることがわかります。_create_user関数の先頭にあるアンダースコアは、”内部使用のみ”を意味します。つまり、UserManagerクラス内でしか使用しません。

※通常は、パブリック関数をオーバーライドするのが設計上優れています。

        if not email:
            raise ValueError('The given email must be set')

emailに値がない場合は、例外メッセージを表示します。raiseは、図的に例外を発生させます。自作した関数などで例外を発生させたい場合によくraiseを使います。

raiseの記述方法は以下の通りです。

raise 例外クラス(メッセージ):

    def create_user(self, email, password=None, **extra_fields):

通常ユーザを作る関数です。

    def create_superuser(self, email, password, **extra_fields):

スーパユーザを作る関数です。

class User(AbstractBaseUser, PermissionsMixin):

Userは、AbstractBaseUserを継承するクラスです。

email = self.normalize_email(email)

電子メールを正規化します。

email = models.EmailField(_('email address'), unique=True)

emailフィールドは、テーブル上で一意となる制約を受けます。

objects = UserManager()

標準のBaseUserManagerを使う代わりに、UserManagerを使うということをDjangoに知らせています。 これにより、今後「create_user」、「create_superuser」のメソッドを呼ぶときにUserManagerクラスの「create_user」、「create_superuser」のメソッドが呼ばれるようになります。

USERNAME_FIELD = 'email'

そのユーザーのユニークなキーを記述します。ここではemailがユニークなフィールドとします。

REQUIRED_FIELDS = []

ユーザーを作成するために必要なキーを記述します。「createsuperuser management」コマンドを使用してユーザーを作成するとき、プロンプ​​トに表示されるフィールド名のリストです。デフォルトは「REQUIRED_FIELDS = [‘username’]」です。

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

内部クラスを指定します。class文の中に入れ子でMetaという名前のclass文を定義しておくと、そこから情報を読み取って定義しているクラス(ここでいうUser)に追加情報や機能を差し挟んでくれます。

verbose_name

人間が判読可能なフィールド名です。 冗長な名前が与えられていない場合、Djangoはそのフィールドの属性名を使って自動的に作成し、アンダースコアをスペースに変換します。

settings.py

自分で作ったUserモデルをデフォルトで使用するため宣言します。settings.pyに、以下を付け加えます。

AUTH_USER_MODEL = 'users.User'

admin.py

自作のログイン画面等を使う場合はこれだけで問題ありません。しかし、デフォルトの管理画面(/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/django/contrib/auth/admin.py

マイグレート

①マイグレーションファイルを作成します。

python manage.py makemigrations

以下のような結果が表示されます。

Migrations for 'users':
  users/migrations/0001_initial.py
    - Create model User

②マイグレートします。

python manage.py migrate

③スーパーユーザを作ります。

python manage.py createsuperuser

以下のようなプロンプトが表示されます。email、パスワード、パスワード確認を入力します。デフォルトではUsernameの入力が求められましたが、「Email address」が最初の入力になっています。

Email address: 
Password: 
Password (again): 

Superuser created successfully.

④ホスト環境のブラウザから、以下のURLを入力します。

http://ローカルホストのipドレス:8000/admin/

以下のように、usernameを入力するフォームがメールアドレスに変わっています。

トラックバック用のURL
プロフィール

名前:イワサキ ユウタ 職業:システムエンジニア、ウェブマスター、フロントエンドエンジニア 誕生:1986年生まれ 出身:静岡県 特技:ウッドベース 略歴 20

最近の投稿
人気記事
カテゴリー
広告