投稿日: | 最終更新日:
DjangoのUserモデルを継承してカスタマイズ
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()を実装していること。
カスタマイズ手順
以下の作業手順ですすめます。
- usersという名前のアプリケーションを作成
- usersアプリのmodels.pyにAbstractBaseUserを継承したUserクラスを作成
- usersアプリのadmin.pyに UserAdmin を継承したクラスを作成
- プロジェクトのsettings.pyに「AUTH_USER_MODEL = ‘users.User’」を定義
- 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についての説明があります。
最初からカスタム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のソースコードを元に作成しました。
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を入力するフォームがメールアドレスに変わっています。
- Python 114
- 制作 54
- RaspberryPi 41
- Django 40
- WordPress 40
- Linux 27
- VPS 22
- JavaScript 21
- PHP 20
- HTML・CSS 19
- AWS 16
- 仮想環境 15
- レスポンシブデザイン 13
- マイコン 11
- WEB全般 11
- 動画製作 9
- Webサービス 8
- 統合開発環境 8
- 機械学習 8
- PyCharm 7
- jQuery 7
- AfterEffects 7
- 起業・設立 7
- Django REST framework 6
- C# 6
- デザイン 6
- SEO 6
- pydata 6
- Visual Studio 5
- 数学 5
- 携帯サイト 5
- heroku 5
- Mac 5
- illustrator 5
- node.js 5
- Anaconda 5
- Nginx 4
- Jupyter Notebook 4
- インフラ 4
- Google Colaboratory 4
- symfony 4
- Webスクレイピング 3
- photoshop 3
- Go言語 3
- PC 3
- ツール 3
- Docker 3
- facebook 3
- 作業効率化 3
- データベース 3
- Cloud9 3
- コマンド 2
- micro:bit 2
- Kali Linux 2
- Webサーバー 2
- MariaDB 2
- ドローン 2
- コンテナ 2
- DaVinci Resolve 2
- ネットワーク 2
- Java 2
- movie 2
- PCDJ 2
- 音楽 2
- XSERVER 2
- Ansible 1
- Vue.js 1
- JSON 1
- Bootstrap 1
- バージョン管理システム 1
- SSL 1
- S3 1
- ムームードメイン 1
- ネットワーク 1
- アニメーション 1
- D3.js 1
- Rhino 1
- アニメ 1
- git 1
- windows 1
- アクセス解析 1
- スマートフォン 1
- アフィリエイトノウハウ 1
- 知識 1
- TypeScript 1
- 役立つ本・書籍 1
- データサイエンス 1
- ESP32 1
- AI 1
- ownCloud 1
- API 1