(最終更新月:2023年1月)
✔このような方へ向けて書かれた記事となります
「Djangoのsignalって何ができるのだろうか?」
「signalの書き方が知りたい」
「sogmalを使った実例をおしえてほしい」
✔当記事を通じてお伝えすること
- Django Signalとは
- Signalsの書き方
- 日報アプリでsignalsを使う実例
当記事では、DjangoのSignalsについての基礎知識はもちろん、具体的な書き方まで解説しています。
ぜひ最後までご覧ください。
✔YouTube解説動画
当記事の内容は動画を見ながら進めると、約15分程度で完了します。
動画ならではの情報も解説しているので、記事と一緒にご覧ください。
動画の概要欄には、単元ごとのコードを紹介しているgithubページも載せています。
Djangoのsignalとは?
DjangoのSignal(シグナル)とは、アプリケーションの実行中に起こるイベントで、特定の処理をおこなえる機能です。
アプリケーションの実行中におこるイベントの例としては以下のとおり。
- データの保存
- データの消去
- マイグレートの実行
Signalを活用すると、特定のイベントが発生した際に任意のコードを実行できるようになるのです。
signalの種類
signalにはいくつも種類があります。
Signalの種類 | 引数 | パス | できること |
---|---|---|---|
pre_init | sender args kwargs | django.db.models.signals | Modelインスタンスの__init__メソッドの最初で実行 |
post_init | sender instance | django.db.models.signals | Modelインスタンスの__init__メソッドのあとに実行される |
pre_save | sender instance raw using update_fields | django.db.models.signals | Modelインスタンスのsaveメソッドの前に実行 |
post_save | sender instance created raw using update_fields | django.db.models.signals | Modelインスタンスのsaveメソッドのあとに実行 |
pre_delete | sender instance using origin | django.db.models.signals | レコードが消される前に実行 |
post_delete | 上に同じ | django.db.models.signals | レコードが消された後に実行 |
m2m_changed | sender instance action pre_add post_add pre_remove pre_clear post_clear reverse model pk_set using | django.db.models.signals | ManyToManyFieldに変化があった際に実行される |
class_prepared | sender | django.db.models.signals | モデルクラスが定義され、Djangoのシステムに登録された際に実行される |
pre_migrate | sender app_config verbosity interactive stdout using plan apps | django.db.models.signals | migrateコマンドの前に実行される |
post_migrate | 上に同じ | django.db.models.signals | migrateコマンドの後に実行される |
request_started | sender environ | django.core.signals | HTTPリクエストのプロセスがスタートしたら実行される |
request_finished | sender | django.core.signals | HTTPレスポンスを返したあとに実行される |
got_request_exception | sender request | django.core.signals | HTTPリクエストがエラーとなった際に実行される |
setting_change | sender setting value enter | django.test.signals | コンテキストマネージャーを通じて、テストの設定が変更された際に実行される |
template_rendered | sender template context | django.test.signals | テストシステムがテンプレートを読み込んだ際に実行される |
connection_created | sender connection | django.db.backends.signals | データベースとコネクトされた際に実行される |
signalの書き方
Signalの書き方は以下のとおり。
- イベントに対応したSignalをインポート
- 実行したい関数を定義
- 特定のイベントシグナルに定義した関数をコネクト
テンプレートとして以下のようになります。
#パスから必要なシグナルをインポート
def シグナルで実行したい関数を定義(引数):
実行したい処理
インポートしたシグナル.connect(定義した関数, **引数)
Signalを使った例
具体的にpost_saveで日報アプリに機能を実装した例をご覧ください。
実例をご覧いただければより、使い方のイメージがわきやすいでしょう。
accounts > models.py
from django.db.models.signals import post_save
def post_user_created(sender, instance, created, **kwargs):
if created:
profile_obj = Profile(user=instance)
profile_obj.username = instance.email
profile_obj.save()
post_save.connect(post_user_created, sender=User)
models.pyの最下部に追記します。
説明
Userクラスが保存された後すぐに「Profile」クラスを作成するよう関数を定義しました。
post_saveは、senderで指定されたモデルクラスのインスタンス保存後に実行されるシグナルだからです。
ユーザー名は初期値として、Userオブジェクトのemailにしています。
テストの実行
tests.pyに保存が実行されているかを試すコードを書きます。
テストプログラムは、手動で確かめるよりはるかに効率が良いのです。
post_saveメソッドが正しく機能しているか試してみましょう。
accounts > tests.py
from django.test import TestCase
from django.contrib.auth import get_user_model
from accounts.models import Profile
User = get_user_model()
class AccountsTestCase(TestCase):
def __init__(self, *args, **kwargs):
self.email = "test@itc.tokyo"
self.password = "somepassword"
super().__init__(*args, **kwargs)
def setUp(self):
user_obj = User(email=self.email)
user_obj.set_password(self.password)
user_obj.save()
def test_profile_saved(self):
counter = Profile.objects.count()
self.assertEqual(counter, 1)
profile_obj = Profile.objects.first()
self.assertEqual(profile_obj.user.email, profile_obj.username)
test_profile_saved関数で、以下の2つをチェックしています。
- Profileにデータが自動保存されているか
- ユーザーのメールアドレスとProfileのユーザー名が一致しているか
実行してエラーがでなければ問題ありません。
日報アプリにプロフィール編集ページの実装
チュートリアルを続けてくださっている方に向けて、自動生成されたプロフィールを編集するページを作っていきます。
過去の記事でご紹介している方法なので、コードだけご紹介。
accounts > forms.py
from accounts.models import Profile
class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
exclude = ["user"]
accounts > views.py
from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic.edit import UpdateView
from django.urls import reverse_lazy
from .models import Profile
from .forms import ProfileUpdateForm
class OwnProfileOnly(UserPassesTestMixin):
def test_func(self):
profile_obj = self.get_object()
try:
return profile_obj == self.request.user.profile
except:
return False
class ProfileUpdateView(OwnProfileOnly, UpdateView):
template_name = "accounts/profile-form.html"
model = Profile
form_class=ProfileUpdateForm
success_url = reverse_lazy("nippo-list")
accounts > templates > accounts > profile-form.html
{% extends 'base.html' %}
{% block content %}
<div class="container my-3" style="max-width:700px;">
<div class="text-center my-3">
アカウント設定
</div>
<form method="POST"> {% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">保存</button>
</div>
{% endblock %}
accounts > urls.py
#..
from .views import ProfileUpdateView
urlpatterns = [
#...,
path("<int:pk>/", ProfileUpdateView.as_view(), name="profile-update"),
]
main > urls.py
#...
urlpatterns = [
#...,
path("profile/", include("accounts.urls")),
]
django-allauthに用意されたsignals
django-allauthパッケージにも、専用のシグナルが用意されています。
詳しくは以下の記事でご説明しているのでご覧ください。
まとめ
当記事の内容をまとめます。
- シグナルを使うと、特定のイベント発生時に任意の処理をおこなえる
- シグナルにはさまざまな種類がある
次回は、usernameがEメールアドレスとなっている状態でログインした際には、アカウント設定ページへ、それ以外は通常のLOGIN REDIRECTで指定したページへリダイレクトするよう、ログイン後のリダイレクトを動的に変更する方法をお伝えします。
django-allauthのDefaultAdapterを使った方法になりますので、ここで覚えておくと色々と応用も聞きますのでお楽しみに!