【django-allauth対応】CustomUser登録時にProfileを自動生成する方法3選

## はじめに

Djangoでは、ユーザー登録後にそのユーザー専用のプロフィール情報を管理したいケースが多くあります。

例えば、`CustomUser`モデルと`Profile`モデルが1対1の関係にあり、ユーザーログイン後にプロフィール情報を含むトップページにアクセスしたとしましょう。
このとき、`CustomUser`に紐づく`Profile`オブジェクトが存在しないため、`RelatedObjectDoesNotExist`エラーが発生してしまいます。
管理画面でユーザーごとにプロフィールを手動で登録することもできますが、運用を考えると現実的ではありません。
そこで本記事では、**アダプタを使わずに** `CustomUser`モデルと1対1で紐づいた`Profile`モデルを、ユーザー登録時に自動生成する方法を３つ紹介します。

## 対象読者と実行環境
### 🎯 対象読者
- ユーザー登録時にプロフィール情報を自動生成したい方
- 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のスクラップ](https://zenn.dev/oretomerge49/scraps/066e8021e0610f) を御覧ください。

## 1️⃣ models.pyに直接記述する方法
django標準のシグナル `post_save`を使うことで、`CustomUser` 登録後に 自動的に `Profile`が生成されます。
実装コードは以下のとおりです。

```python:accounts/models.py
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` ファイルを新規作成します。
実装コードは以下のとおりです。

```python: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`内に次のようにコードを追記する必要があります。

```python: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にもシグナル](https://docs.allauth.org/en/dev/account/signals.html)が用意されています（ただし、公式を見ても、どのように実装するかわかりにくい素っ気ない内容です）。

今回のようなケースで使用するシグナルは、`user_signed_up` です。
先程の2️⃣で作成した `accounts/signals.py` の内容を次のように書き換えます。

```python: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`を自動生成したい場合、若干力技ですが、以下の実装コードを試すのも一つの手だと思います。

```python:accounts/signals.py
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://docs.djangoproject.com/ja/5.2/topics/signals/#receiver-functions)
https://itc.tokyo/django/save-with-post-save-signal/
https://djangostart.com/158/
[allauth - signals](https://docs.allauth.org/en/dev/account/signals.html)
https://itc.tokyo/django/use-allauth-signals/


<hr><br>
気になる点などございましたら、コメント欄でお気軽にお知らせください。