Djangoシグナル入門
モデルのイベントに自動で処理を実行する
Djangoのシグナルシステムを解説。pre_save/post_save、@receiver、カスタムシグナル、実践的な活用パターンまで学べます。
こんな人向けの記事です
- Djangoのシグナルの仕組みを理解したい
- モデル保存時に自動処理を実行したい
- カスタムシグナルを作成したい
Step 1シグナルとは ― イベント駆動のフック
Djangoのシグナル(Signal)は、アプリケーション内で特定のイベントが発生したとき、別の処理を自動的に実行する仕組みです。いわゆるオブザーバーパターン(Observer Pattern)の実装であり、「送信者(sender)」と「受信者(receiver)」を疎結合に保ちながら連携できます。
| 用語 | 説明 |
|---|---|
| シグナル(Signal) | イベントの種類を表すオブジェクト |
| 送信者(Sender) | シグナルを発行する側(例: モデルクラス) |
| 受信者(Receiver) | シグナルを受け取って処理を実行する関数 |
| 接続(Connect) | シグナルと受信者を紐づける操作 |
例えば「ユーザーが作成されたらプロフィールも自動で作る」「記事が削除されたらキャッシュをクリアする」など、モデルの保存・削除に連動した処理を、モデル自体のコードを変更せずに追加できます。
なぜシグナルを使うのか?
モデルの save() メソッドをオーバーライドしても同じことは実現できます。しかしシグナルなら、他のアプリから後付けで処理を差し込めるのが最大のメリットです。再利用可能なアプリを作る際に特に有効です。
Step 2組み込みシグナル(pre_save / post_save / pre_delete / post_delete)
Djangoにはモデル操作に関する主要なシグナルが用意されています。最もよく使われる4つを確認しましょう。
| シグナル | 発火タイミング | 主な用途 |
|---|---|---|
pre_save | モデルの save() 実行前 | バリデーション、フィールドの自動補完 |
post_save | モデルの save() 実行後 | 関連レコードの作成、通知送信 |
pre_delete | モデルの delete() 実行前 | 関連データのバックアップ、依存チェック |
post_delete | モデルの delete() 実行後 | ファイル削除、キャッシュクリア |
その他の組み込みシグナル
| シグナル | 説明 |
|---|---|
m2m_changed | ManyToManyField の関連が変更されたとき |
request_started | HTTPリクエスト処理の開始時 |
request_finished | HTTPリクエスト処理の終了時 |
got_request_exception | リクエスト処理中に例外が発生したとき |
シグナルのインポート
from django.db.models.signals import (
pre_save,
post_save,
pre_delete,
post_delete,
m2m_changed,
)
from django.core.signals import request_started, request_finished
各シグナルに渡される引数
post_save の受信関数には以下の引数が渡されます。
| 引数 | 型 | 説明 |
|---|---|---|
sender | モデルクラス | シグナルを発行したモデル |
instance | モデルインスタンス | 保存されたオブジェクト |
created | bool | True なら新規作成、False なら更新 |
**kwargs | dict | その他のキーワード引数 |
pre_save には created 引数がない
pre_save の時点ではまだDBに保存されていないため、created 引数は渡されません。新規作成かどうかを判定するには instance.pk is None をチェックします。
Step 3シグナルの接続方法(@receiver デコレータ)
シグナルに処理を接続する方法は2つあります。推奨されるのは @receiver デコレータです。
方法1: @receiver デコレータ(推奨)
myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
@receiver(post_save, sender=User)
def user_saved(sender, instance, created, **kwargs):
if created:
print(f"新しいユーザーが作成されました: {instance.username}")
else:
print(f"ユーザーが更新されました: {instance.username}")
方法2: connect() メソッド
myapp/signals.py
from django.db.models.signals import post_save
from django.contrib.auth.models import User
def user_saved(sender, instance, created, **kwargs):
if created:
print(f"新しいユーザーが作成されました: {instance.username}")
# connect() で手動接続
post_save.connect(user_saved, sender=User)
シグナルファイルの読み込み設定
シグナルを定義しただけでは動きません。AppConfig の ready() メソッドで読み込む必要があります。
myapp/apps.py
from django.apps import AppConfig
class MyappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
import myapp.signals # ここでシグナルを読み込む
ready() を忘れるとシグナルが動かない
最も多いミスが ready() でのインポート忘れです。シグナルを定義したのに動かないときは、まず apps.py の ready() を確認してください。また __init__.py の default_app_config が正しく設定されていることも確認しましょう。
推奨のファイル構成
ディレクトリ構成
myapp/
├── __init__.py
├── apps.py # ready() でシグナルを読み込む
├── models.py # モデル定義
├── signals.py # シグナルハンドラをまとめる
├── views.py
└── admin.py
sender を省略すると?
@receiver(post_save) のように sender を省略すると、すべてのモデルの保存時にシグナルが発火します。パフォーマンスに影響するため、通常は sender を指定しましょう。
Step 4カスタムシグナルの作成
Django組み込みのシグナルだけでなく、独自のシグナルを定義してアプリケーション固有のイベントを通知できます。
カスタムシグナルの定義
myapp/signals.py
import django.dispatch
# カスタムシグナルの定義
order_completed = django.dispatch.Signal() # 注文完了シグナル
payment_received = django.dispatch.Signal() # 支払い受領シグナル
シグナルの送信
myapp/views.py
from myapp.signals import order_completed
def complete_order(request, order_id):
order = Order.objects.get(id=order_id)
order.status = 'completed'
order.save()
# カスタムシグナルを送信
order_completed.send(
sender=Order,
order=order,
user=request.user,
)
return redirect('order_detail', order_id=order.id)
シグナルの受信
notifications/signals.py
from django.dispatch import receiver
from myapp.signals import order_completed
@receiver(order_completed)
def send_order_confirmation(sender, order, user, **kwargs):
"""注文完了メールを送信"""
send_mail(
subject=f'注文 #{order.id} が完了しました',
message=f'{user.username} 様、ご注文ありがとうございます。',
from_email='shop@example.com',
recipient_list=[user.email],
)
@receiver(order_completed)
def update_inventory(sender, order, **kwargs):
"""在庫を更新"""
for item in order.items.all():
item.product.stock -= item.quantity
item.product.save()
send() と send_robust() の違い
| メソッド | 例外発生時の挙動 | 用途 |
|---|---|---|
send() | 例外がそのまま伝播する | 開発環境、エラーを即座に検知したい場合 |
send_robust() | 例外をキャッチし、戻り値として返す | 本番環境、1つの受信者の失敗で他を止めたくない場合 |
send_robust() の使用例
# send_robust() は例外が起きてもすべての受信者を実行する
results = order_completed.send_robust(
sender=Order,
order=order,
user=request.user,
)
# 各受信者の結果を確認
for receiver_func, response in results:
if isinstance(response, Exception):
print(f"エラー: {receiver_func.__name__} - {response}")
else:
print(f"成功: {receiver_func.__name__}")
providing_args は廃止済み
Django 3.1 以前では Signal(providing_args=['order', 'user']) のように引数を宣言していましたが、Django 4.0 で削除されました。現在は Signal() のみで定義し、引数はドキュメントやコメントで示します。
Step 5シグナルの実行順序と注意点
シグナルは便利ですが、使い方を誤るとバグやパフォーマンス低下の原因になります。重要な注意点を押さえておきましょう。
実行順序
同じシグナルに複数の受信者が接続されている場合、実行順序は保証されません。接続された順に実行される傾向はありますが、仕様として保証はされていません。
実行順序に依存してはいけない
受信者Aの結果を受信者Bが前提とするような設計は避けてください。各受信者は独立して動作するように実装しましょう。順序が重要な場合は、1つの受信者内で順番に処理を呼び出します。
トランザクションとの関係
トランザクション完了後に実行する
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Order)
def notify_order_saved(sender, instance, created, **kwargs):
# post_save はトランザクションのコミット前に発火する可能性がある
# 外部APIの呼び出しなどはトランザクション完了後に行う
transaction.on_commit(
lambda: send_notification(instance)
)
post_save はコミット前に発火する
post_save はモデルの save() 完了後に発火しますが、トランザクションのコミット前です。外部APIの呼び出しやメール送信など、ロールバックされては困る処理は transaction.on_commit() で囲みましょう。
よくある落とし穴
| 問題 | 原因 | 対策 |
|---|---|---|
| シグナルが動かない | ready() で import していない | apps.py の ready() を確認 |
| 無限ループ | シグナル内で save() を呼んでいる | update() を使うか、フラグで制御 |
| 2回発火する | シグナルが2回接続されている | dispatch_uid を指定する |
| テストで予期しない動作 | シグナルが意図せず発火 | テスト時にシグナルを一時無効化 |
| パフォーマンス低下 | シグナル内で重い処理をしている | 非同期タスク(Celery等)に委譲 |
無限ループの防止
無限ループを防ぐパターン
@receiver(post_save, sender=Article)
def update_word_count(sender, instance, **kwargs):
# NG: save() を呼ぶと post_save が再発火 → 無限ループ
# instance.word_count = len(instance.content.split())
# instance.save()
# OK: update() はシグナルを発火しない
Article.objects.filter(pk=instance.pk).update(
word_count=len(instance.content.split())
)
dispatch_uid で重複接続を防ぐ
dispatch_uid の指定
# dispatch_uid を指定すると、同じIDで2回以上接続されない
@receiver(post_save, sender=User, dispatch_uid="create_user_profile")
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
Step 6実践例 ― プロフィール自動作成・ログ記録
ここまでの知識を使って、実際のプロジェクトでよく使われるパターンを実装してみましょう。
実践例1: ユーザー作成時にプロフィールを自動作成
accounts/models.py
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True, default='')
avatar = models.ImageField(upload_to='avatars/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.user.username} のプロフィール'
accounts/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Profile
@receiver(post_save, sender=User, dispatch_uid="create_user_profile")
def create_profile(sender, instance, created, **kwargs):
"""ユーザー作成時にプロフィールを自動作成"""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User, dispatch_uid="save_user_profile")
def save_profile(sender, instance, **kwargs):
"""ユーザー保存時にプロフィールも保存"""
if hasattr(instance, 'profile'):
instance.profile.save()
accounts/apps.py
from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'
def ready(self):
import accounts.signals
実践例2: モデル変更のログ記録
audit/models.py
from django.db import models
class AuditLog(models.Model):
ACTION_CHOICES = [
('create', '作成'),
('update', '更新'),
('delete', '削除'),
]
model_name = models.CharField(max_length=100)
object_id = models.IntegerField()
action = models.CharField(max_length=10, choices=ACTION_CHOICES)
changes = models.JSONField(default=dict, blank=True)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-timestamp']
def __str__(self):
return f'{self.model_name} #{self.object_id} {self.action}'
audit/signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import AuditLog
# 監査対象のモデル一覧
AUDITED_MODELS = []
def audit_model(model_class):
"""デコレータ: モデルを監査対象に登録"""
AUDITED_MODELS.append(model_class)
return model_class
@receiver(post_save)
def log_save(sender, instance, created, **kwargs):
if sender not in AUDITED_MODELS:
return
AuditLog.objects.create(
model_name=sender.__name__,
object_id=instance.pk,
action='create' if created else 'update',
)
@receiver(post_delete)
def log_delete(sender, instance, **kwargs):
if sender not in AUDITED_MODELS:
return
AuditLog.objects.create(
model_name=sender.__name__,
object_id=instance.pk,
action='delete',
)
myapp/models.py(監査対象のモデル)
from django.db import models
from audit.signals import audit_model
@audit_model
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
# ... このモデルの作成・更新・削除が自動でログに記録される
実践例3: テスト時にシグナルを無効化
tests/test_user.py
from django.test import TestCase
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from accounts.signals import create_profile
class UserTestCase(TestCase):
def setUp(self):
# テスト中はプロフィール自動作成を無効化
post_save.disconnect(create_profile, sender=User)
def tearDown(self):
# テスト後に再接続
post_save.connect(create_profile, sender=User)
def test_user_creation_without_profile(self):
user = User.objects.create_user('testuser', 'test@example.com', 'pass')
self.assertFalse(hasattr(user, 'profile'))
シグナル活用のチェックリスト
signals.pyにシグナルハンドラをまとめているapps.pyのready()でインポートしているdispatch_uidを指定して重複接続を防いでいる- シグナル内で
save()を呼ばず無限ループを避けている - 外部API呼び出しは
transaction.on_commit()で囲んでいる - テスト時はシグナルの無効化を検討している
まとめ
- シグナルはDjangoのイベント駆動フックで、モデルの保存・削除に連動した処理を疎結合に実装できる
- 組み込みシグナル:
pre_save/post_save/pre_delete/post_deleteが主要な4つ - @receiver デコレータで接続し、
apps.pyのready()で読み込む - カスタムシグナルは
django.dispatch.Signal()で定義し、send()/send_robust()で発火する - 注意点: 実行順序は保証されない、無限ループに注意、
transaction.on_commit()を活用する - 実践: プロフィール自動作成、監査ログ、テスト時の無効化が代表的なパターン