Edited at

[Django]User作成時にUserProfileモデルに空レコードを作成する(Signalの使用)

※備忘録


やりたいこと

作成中のアプリにて、サインアップした際にUserモデルにOneToOneで紐付いているUserProfileモデルにレコードを追加する。


環境


  • Python==3.7.0

  • Django==2.1.7


実装


models.py

※Userモデルはemailを使用するためカスタムしています。


models.py

from django.db.models.signals import post_save

from django.dispatch import receiver
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.base_user import BaseUserManager

class UserProfile(models.Model):
  """ユーザープロフィールモデル"""
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name='profile')
picture = models.ImageField(upload_to='profile_pictures', blank=True, null=True,)
bio = models.TextField(blank=True)

def __str__(self):
return self.user.username

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

use_in_migrations = True

def _create_user(self, email, password, **extra_fields):
"""メールアドレスでの登録を必須にする"""
if not email:
raise ValueError('メールアドレスを入力してください')
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)
nick_name = models.CharField(_('nick name'), max_length=50, blank=False, unique=True, db_column="ユーザー名")

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_short_name(self):
return self.nick_name

@property
def username(self):
return self.email

@receiver(post_save, sender=User)
def create_profile(sender, **kwargs):
""" 新ユーザー作成時に空のprofileも作成する """
if kwargs['created']:
user_profile = UserProfile.objects.get_or_create(user=kwargs['instance'])



views.py


views.py

class UserCreate(CreateView):

"""ユーザー登録"""
model = User
form_class = UserCreateForm

def form_valid(self, form):
"""ユーザー登録"""
# formをテーブルに保存するかを指定するオプション(デフォルト=True)
user = form.save(commit=True)
# is_active<-ユーザーアカウントをアクティブにするかどうかを指定,
# 退会処理も、is_activeをFalseにするという処理がベター。
user.is_active = True
user.save()

return redirect('user_create_done')



実装のポイント

DjangoにはSignalという機能が搭載されていて、なにかアクションが発生したときにそれをフックに特定の処理をキックすることができる。

※該当箇所抜粋


models.py

@receiver(post_save, sender=User)

def create_profile(sender, **kwargs):
""" 新ユーザー作成時に空のprofileも作成する """
if kwargs['created']:
user_profile = UserProfile.objects.get_or_create(user=kwargs['instance'])

@receiverデコレータでpost_saveシグナルを指定し、senderにUserモデルを指定する。


  • post_saveはモデルに対するレコードの追加 or 変更をした直後に動作する。(他にもpre_save、request_startedなどが用意されている)

  • senderで、アクションを検知する対象を指定する。

  • get_or_createメソッドは、引数をキーにDBのレコードを探索し、すでにレコードが存在しない場合、作成し、存在する場合はそのレコードを返す。

create_profile関数では、Userモデルの追加、変更をフックに**kwargsがcreatedであれば、UserProfileモデルにレコードを作成する。


参考にさせていただいた記事

公式ドキュメント

[Django] Signal の使い方まとめ

Djangoシグナル登録の簡単なまとめ

get_or_createの更新時のデフォルト値の扱いなど