はじめに
Djangoでは、ユーザー登録後にそのユーザー専用のプロフィール情報を管理したいケースが多くあります。
例えば、CustomUser
モデルとProfile
モデルが1対1の関係にあり、ユーザーログイン後にプロフィール情報を含むトップページにアクセスしたとしましょう。
このとき、CustomUser
に紐づくProfile
オブジェクトが存在しないため、RelatedObjectDoesNotExist
エラーが発生してしまいます。
管理画面でユーザーごとにプロフィールを手動で登録することもできますが、運用を考えると現実的ではありません。
そこで本記事では、アダプタを使わずに CustomUser
モデルと1対1で紐づいたProfile
モデルを、ユーザー登録時に自動生成する方法を3つ紹介します。
対象読者と実行環境
🎯 対象読者
- ユーザー登録時にプロフィール情報を自動生成したい方
- django-allauthを使ってログイン・サインアップを実装している方
🧪 実行環境
- Python: 3.13.2
- Django: 4.2.19
- django-allauth: 65.4.1
🔧 前提条件
本記事は以下のような構成を前提としています。
-
Djangoの
CustomUser
モデルを使用している
(AbstractUser
やAbstractBaseUser
を継承して独自に定義している) -
CustomUser
モデルとProfile
モデルが1対1のリレーション(OneToOneField
)で結びついている -
プロフィール情報(アカウント名や自己紹介文など)は別モデル(
Profile
)で管理したい -
django-allauth
を導入している(任意)
🧾 この記事の内容をざっくり読みたい方へ
実装の要点だけを知りたい方は、Zennのスクラップ を御覧ください。
1️⃣ models.pyに直接記述する方法
django標準のシグナル post_save
を使うことで、CustomUser
登録後に 自動的に Profile
が生成されます。
実装コードは以下のとおりです。
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.objects.create(user=instance)
post_save.connect(post_user_created, sender=CustomUser)
これによって、CustomUserの保存後にpost_user_created()
関数が呼ばれ、対応する Profile
オブジェクトが自動生成されます。
個人的には、models.py内で記述が完結するため、シンプルに実装できる方法だと思います。
しかしながら、モデルが複雑になってきた場合に、ファイルが肥大化して、コードの見通しが悪くなるおそれがあります。
責務を分けて保守性を高める方法として、次の signals.py
を作成する方法があります。
2️⃣ signals.pyを使って責務分割する方法
まず、 accounts
アプリ内に 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)
1️⃣の実装との主な違いは、@receiver
デコレータを使っている点です。
このcreate_user_profile
関数を有効にするためには、 accounts/apps.py
内に次のようにコードを追記する必要があります。
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'
# 以下のコードを追記
def ready(self): # <- インデント注意
import accounts.signals # noqa: F401
def ready
のインデントに注意してください(classの中に記述しないと正常に動作しません)。
また、最終行の # noqa: F401
は、Flake8を導入している場合に未使用のimportに対して出される警告(F401)を無視するためのものです。
これで、1️⃣と同様に CustomUser
の保存後に、Profile
オブジェクトが自動生成されるようになります。
さて、ソーシャルアカウントとの連携のため、django-allauth
を導入している方も少なくないと思います。
実は、django-allauth
にもシグナルが用意されていて、次に紹介する方法で実装可能です。
3️⃣ django-allauth の signalを活用する方法
前述のとおり、django-allauthにもシグナルが用意されています(ただし、公式を見ても、どのように実装するかわかりにくい素っ気ない内容です)。
今回のようなケースで使用するシグナルは、user_signed_up
です。
先程の2️⃣で作成した 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)
django-allauth
のシグナルを使うとコードがスッキリします。
加えて、ソーシャルアカウントと連携している場合、ソーシャルアカウントに紐づいた情報も取得・活用できるというメリットがあります。
ただし、注意することが一つあります。
⚠️ 注意:この方法は createsuperuser
や管理画面からのユーザー作成には反応しません
これは django-allauth
の仕組みによるもので、残念ながら回避が難しい部分です。
番外編;2️⃣ + 3️⃣のハイブリッド
django-allauth
のシグナルを使いつつ、createsuperuser
や管理画面からのユーザー作成時にもProfile
を自動生成したい場合、若干力技ですが、以下の実装コードを試すのも一つの手だと思います。
from allauth.account.signals import user_signed_up
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.conf import settings
from user_profile.models import Profile
@receiver(user_signed_up)
def create_profile_allauth(request, user, **kwargs):
if not hasattr(user, 'profile'):
Profile.objects.create(user=user)
@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)
これにより、通常のallauth経由のサインアップ、管理画面や createsuperuser
のどちらでもプロフィールが自動生成されるようになります。
しかしながら、管理者サイト等での登録は運用者向けであり、django-allauthによるサインアップと双方に対応させることで、思わぬバグが発生することも考えられます。したがって、実装にあたっては十分検討する必要があると考えます。
さいごに
シグナルは、特定のイベントをきっかけに処理を自動化できる便利な仕組みですが、使いすぎるとコードの挙動が見えづらくなり、保守が難しくなることもあります。
そのため、限定的な用途でうまく活用するのがおすすめです。
まとめ
実装の簡便さ:1️⃣の方法
責務分割と保守性:2️⃣の方法
django-allauth利用:3️⃣の方法
状況に応じて適切な方法を選んでみてください。
参考記事
Django公式ドキュメント - シグナル
https://itc.tokyo/django/save-with-post-save-signal/
https://djangostart.com/158/
allauth - signals
https://itc.tokyo/django/use-allauth-signals/
気になる点などございましたら、コメント欄でお気軽にお知らせください。