Closed1

【Django】User作成時にProfileを自動生成する3パターン+1 まとめ(signals)

HiroHiro

User作成時に1対1リレーションのProfileを自動生成する方法について簡潔にまとめました。

1. accounts/models.pyに関数を設定して、connectで繋ぐ方法

from django.db.models.signals import post_save
from user_profile.models import Profile

(略)

# 最下部にこのように記述
def post_user_created(sender, instance, created, **kwargs):
    if created:
        profile_obj = Profile(user=instance)
        profile_obj.save()
        # 上の2行は以下のコメントアウトしたコードとしてもOK
        # Profile.objects.create(user=instance)


post_save.connect(post_user_created, sender=CustomUser)

オブジェクトの保存方法はどちらでもOK
何かインスタンスに対して処理を施すなら、インスタンス化して保存。
特に処理がないなら、ダイレクトにcreateコマンドで保存するほうが見通しがいいと思う。

参考:https://itc.tokyo/django/save-with-post-save-signal/

2. accounts/signals.pyを作成してaccounts/apps.pyで実行

上記方法では、models.pyに記述するので、読みにくくなる可能性もあり、責務分割の観点から以下のようにsignals.pyを別途作成して記述する方法が個人的には好み。

# accoounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver

from accounts.models import CustomUser
from user_profile.models import Profile


@receiver(post_save, sender=CustomUser)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)


# accounts/apps.py
from django.apps import AppConfig


class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'accounts'

    # 以下の2行を追加(インデント注意。class内に定義する)
    def ready(self):
        import accounts.signals  # noqa: F401

noqa: F401は、importしたものを使っていないたFlake8からF401で指摘されるので、それを無視するもの

参考:【Django入門21】シグナルとイベントトリガーの使い方|自動処理を実現する方法

3. django-allauthのsignalsを使用する方法

# accounts/signals.py
from allauth.account.signals import user_signed_up
from django.dispatch import receiver

from user_profile.models import Profile


@receiver(user_signed_up)
def create_user_profile(request, user, **kwargs):
    Profile.objects.create(user=user)


# accounts/apps.py(2.と同じ内容)
from django.apps import AppConfig


class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'accounts'

    # 以下の2行を追加(インデント注意。class内に定義する)
    def ready(self):
        import accounts.signals  # noqa: F401

django-allauthにかかわるものであれば、こちらがベターかもしれない
この方法のメリットは、ソーシャルアカウントに紐づいた情報も取得して反映することも可能なこと
ただし、django-allauthの仕組み上、管理者サイトからの登録やcreatesuperuser実行時は自動でプロフィールを作成することはできない

参考:https://dev.to/gabe_ss/django-allauth-add-action-after-authentication-test-behaviour-2b9a

4. django-allauth signalsを使用しつつ、管理者サイトやcreatesuperuserコマンドにも対応させる方法

django-allauthのsignalsを使いつつ、管理者サイトやcreatesuperuserに対応させるならば、このようなコードになります。
管理者サイト等での登録は運用者向けであり、両方に対応させることで、思わぬバグが発生することも想定される。

# accounts/models.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from allauth.account.signals import user_signed_up

from user_profile.models import Profile


# 通常の allauth サインアップ(ソーシャルも含む)に対応
@receiver(user_signed_up)
def create_profile_allauth(request, user, **kwargs):
    if not hasattr(user, 'profile'):
        Profile.objects.create(user=user)

# 管理画面や createsuperuser など allauth を通らない場合に対応
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_post_save(sender, instance, created, **kwargs):
    if created and not hasattr(instance, 'profile'):
        Profile.objects.create(user=instance)


# accounts/apps.py(2.と同じ内容)
from django.apps import AppConfig


class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'accounts'

    # 以下の2行を追加(インデント注意。class内に定義する)
    def ready(self):
        import accounts.signals  # noqa: F401
このスクラップは6ヶ月前にクローズされました