Djangoのシグナル: 効果的なイベントハンドリング

こんにちは！
[AXLBIT株式会社](https://axlbit.com/)の@yangです。

今回はDjangoのシグナルについてお話します。

Djangoは、信頼性の高いウェブアプリケーションを構築するための強力なフレームワークです。その中でも、シグナルは非常に重要であり、イベント駆動型のプログラミングにおいて非常に有用です。

# Djangoのシグナル

Djangoには「シグナルディスパッチャー」と呼ばれる仕組みが組み込まれており、これは切り離されたアプリケーションがフレームワークの他の場所でアクションが発生した際に通知を受けるのに役立ちます。要するに、シグナルは特定の送信者が一連のレシーバに対して、あるアクションが発生したことを通知することを可能にします。これは同じイベントに関心を持つ多くのコードが存在する場合に特に役立ちます。

Djangoの組み込みシグナルは、ユーザーコードに特定のアクションが発生したことを通知する機能を提供しています。

また、独自のカスタムシグナルを定義して送信することもできます。

# シグナルの主な用途

Djangoのシグナルは以下のような用途で活用されます。

### モデルの変更の検知

モデルのインスタンスが保存、更新、削除されたときにSignalを使用してそれを検知し、必要な処理を実行します。

### ユーザー認証の拡張

ユーザーがログインまたはログアウトしたときにSignalを使用して、特定のアクションを実行する。

### アクションの起動装置

アプリケーション内の異なるコンポーネント間での特定のアクションの起動装置としてSignalを使用する。

# シグナルの使い方

シグナルを受信するためには、Signal.connect() メソッドを使用してレシーバー関数を登録します。レシーバー関数はシグナルが送信されたときに呼び出されます。シグナルのすべてのレシーバー関数は、登録された順序で一度に1つずつ呼び出されます。

### レシーバー関数を定義

レシーバー関数は以下のように定義されます。

```python
def my_callback(sender, **kwargs):
    処理
```

レシーバー関数も非同期処理として定義できます、その場合はdefの前にasyncをつければ良いです。

### レシーバー関数を接続

レシーバー関数を接続する際二つの方法があります。

その一つは以下のように手動で接続させます。

```python
from django.core.signals import シグナル名
シグナル名.connect(レシーバー関数)
```

もしくは、receiverデコレーターを使用することもできます。

```python
from django.core.signals import シグナル名
from django.dispatch import receiver


@receiver(シグナル名)
def レシーバー関数名(sender, **kwargs):
    処理
```

これでこのレシーバー関数は指定のシグナルが発生するたびに実行されます。

### 特定の送信者から送られたシグナルに接続

receiverにさらにsenderを指定することで、特定の送信者からのシグナルのみに反応するようにもできます。

```python
from django.db.models.signals import シグナル名
from django.dispatch import receiver
from アプリ名.models import モデル名


@receiver(シグナル名, sender=モデル名)
def レシーバー関数名(sender, **kwargs):
    処理
```

使い方について、[こちら](https://docs.djangoproject.com/ja/5.0/topics/signals/)のページでご確認ください。

# よく使われるシグナル

Djangoでは多くのビルトインシグナルが提供されています。

以下によく使われるシグナルの実行タイミングとそれを使う際のレシーバー関数の引数の**kwargsとして使えるものをまとめています。

| シグナル名           | 実行タイミング         | レシーバー関数引数                                                  |
|----------------------|------------------------|------------------------------------------------------------------|
| pre_init             | インスタンス初期化前    | args：クラスのコンストラクタに渡される引数<br>kwargs：クラスのコンストラクタに渡されるキーワード引数        |
| post_init            | インスタンス初期化後    | instance：作成されたインスタンス        |
| pre_save             | モデル保存前            | instance：保存されるモデルのインスタンス<br>raw：`loaddata`や`defer`を使用している場合はTrue<br>using：データベースエイリアスの名前                             |
| post_save            | モデル保存後            | instance：保存されたモデルのインスタンス<br>created：新しいレコードが作成された場合はTrue<br>raw：`loaddata`や`defer`を使用している場合はTrue<br>using：データベースエイリアスの名前                             |
| pre_delete           | モデル削除前            | instance：削除されるモデルのインスタンス<br>using：データベースエイリアスの名前                             |
| post_delete          | モデル削除後            | instance：削除されたモデルのインスタンス<br>using：データベースエイリアスの名前                             |
| m2m_changed          | Many-to-Many 変更時     | instance：関連するモデルのインスタンス<br>action：追加、削除、クリアのいずれか<br>reverse：逆の関係フィールドを通している場合はTrue<br>model：対象のMany-to-Manyフィールドのクラス<br>pk_set：変更後の関連付けられた主キーのセット<br>using：データベースエイリアスの名前                             |
| class_prepared       | クラスが初めて読み込まれる |                                              |
| pre_migrate          | マイグレーション前        | using：データベースエイリアスの名前<br>plan：マイグレーションの実行計画                                             |
| post_migrate         | マイグレーション後        | using：データベースエイリアスの名前<br>plan：マイグレーションの実行計画                                            |
| request_started      | リクエスト開始時          | environ：環境変数                                             |
| request_finished     | リクエスト完了時          |                                            |
| got_request_exception | リクエスト例外時          | request：HttpRequestオブジェクト                                            |
| setting_changed      | 設定変更時              | setting：変更された設定のキー<br>value：新しい値<br>enter：設定が変更される前に存在したかどうか<br>setting_changed：設定変更のスタック                                            |
| template_rendered    | テンプレートレンダリング時 | template：レンダリングされたテンプレートのパス<br>context：テンプレートに渡されたコンテキスト                                            |
| connection_created   | データベース接続時        | connection：作成されたデータベース接続                                             |

使えるシグナルについて、[こちら](https://docs.djangoproject.com/ja/3.1/ref/signals/#got-request-exception)のページでご確認ください。

# まとめ

Djangoのシグナルを活用することで、アプリケーションの機能を拡張し、柔軟性を高めることができます。 シグナルはDjangoアプリケーションの中で強力なツールであり、正しく使用することでコードの保守性と拡張性を向上させることができます。