投稿日: | 最終更新日:
DjangoのUserモデルカスタマイズ(UUIDを使う)
Djangoに標準で備わるUserモデルをカスタマイズします。今回は、UUIDを利用します。
UserモデルでUUIDを使う
前回、Django標準のUserモデルをカスタマイズして、独自のユーザ認証テーブルを作成しました。今回は、UUIDを使ってidキーを改造してみます。
UUIDとは?
UUID(universally unique identifier)とは、世界で重複しないIDです。Djangoでは、1.8から追加されました。UUIDFieldクラスを使うと、UUIDを扱えるフィールドをモデルに追加できます。
Djangoモデルのデフォルトのプライマリーキー
Djangoではモデルで特に何も指定しないと、自動でidフィールドが作成されます。このidはプライマリキーですが、型は整数(integer)で1からの連番で生成されます。
こんな時にUUIDを使う
WEBアプリケーションのような分散環境では、UUIDは便利です。idが数値型のままでは重複してしまう可能性がありますが、uuidであれば唯一のidが生成されますので衝突しません。また、IDを公開したくない場合に、推測されにくいIDを生成できます。よって、ユーザ認証情報にこれを利用してみたいと思います。
DjangoのUUIDFieldの用途
djangoでは、プライマリキーにするAutoField(自動的にインクリメントする IntegerField)の代わりになります。例えば、ランキングテーブルのような、大量のデータを高頻度に削除し追加する場合など、AutoFieldを使いたくない場合にUUIDFieldを使うと便利です。
欠点もあります
数値型のidと比べて、容量が大きくなります。DBのインデクシングやプログラミング言語で扱うときに不利な場合があります。また、IDの作成順がわからなくなります。例えば、2つのIDを比べてどちらが古い投稿か判断できなくなります。(これについては、created_atなどの作成日を作って対応します。)
カスタマイズ手順
以下の作業手順ですすめます。前回とほぼ同じで、models.pyにuuidフィールドを追加しました。
- usersという名前のアプリケーションを作成
- usersアプリのmodels.pyにAbstractBaseUserを継承したUserクラスを作成
- usersアプリのadmin.pyに UserAdmin を継承したクラスを作成
- プロジェクトのsettings.pyに「AUTH_USER_MODEL = ‘users.User’」を定義
- django.contrib.admin、django.contrib.authへの影響範囲を調べ、必要であれば他のクラスも継承して独自に修正
今回の変更
- idの代わりにuuidを使います。
- usernameの代わりにemailを使います。
- nick_nameを追加します。
- AbstractUserからusername の必須を外します。emailを必須かつユニークに変更します。
- usernameは消します。
- first_nameとlast_nameを消しします。
- 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): """カスタムユーザーモデル.""" uuid = models.UUIDField(default=uuid_lib.uuid4, primary_key=True, editable=False) email = models.EmailField(_('email address'), unique=True) nick_name = models.CharField(_('ニックネーム'), 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): full_name = '%s' % (self.nick_name) return full_name.strip() def get_short_name(self): """Return the short name for the user.""" return self.nick_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を継承します。
uuid = models.UUIDField(default=uuid_lib.uuid4, primary_key=True, editable=False)
「default=uuid.uuid4」は、モデルのオブジェクトが生成されたタイミングでuuidを自動的に割り当てます。また、「primary_key=True」でこのテーブルの主キーであることを示します。「editable=False」で、変更不可にします。
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': ('nick_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', 'nick_name', 'is_staff') list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') search_fields = ('email', 'nick_name') ordering = ('email',) admin.site.register(User, MyUserAdmin)
以下のコードを参考にしました。
django/django/contrib/auth/admin.py
admin.ModelAdmin(UserAdminのスーパークラス)のカスタマイズについては、以下の記事が参考になります。
Django 管理画面逆引きメモ
https://qiita.com/zenwerk/items/044c149d93db097cdaf8
マイグレート
①マイグレーションファイルを作成します。
python manage.py makemigrations
②マイグレートします。
python manage.py migrate
usersテーブルができたか確認します。
python manage.py dbshell
以下のような結果がでました。
show fields from users_user ; +--------------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------+--------------+------+-----+---------+-------+ | password | varchar(128) | NO | | NULL | | | last_login | datetime | YES | | NULL | | | is_superuser | tinyint(1) | NO | | NULL | | | uuid | char(32) | NO | PRI | NULL | | | email | varchar(254) | NO | UNI | NULL | | | nick_name | varchar(150) | NO | | NULL | | | is_staff | tinyint(1) | NO | | NULL | | | is_active | tinyint(1) | NO | | NULL | | | date_joined | datetime | NO | | NULL | | +--------------+--------------+------+-----+---------+-------+
③スーパーユーザを作ります。
python manage.py createsuperuser
以下のようなプロンプトが表示されます。email、パスワード、パスワード確認を入力します。デフォルトではUsernameの入力が求められましたが、「Email address」が最初の入力になっています。(※uuidは、自動で生成されます。)
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