ホーム >  Python > Django >  DjangoのUserモデルカスタマイズ(UUIDを使う)

投稿日:   |  最終更新日:

DjangoのUserモデルカスタマイズ(UUIDを使う)

DjangoPython

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フィールドを追加しました。

  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への影響範囲を調べ、必要であれば他のクラスも継承して独自に修正

今回の変更

  • 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のソースコードを元に作成しました。

models.py

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を入力するフォームがメールアドレスに変わっています。

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

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

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