サイトアイコン ITC Media

【Django】Signalsの基本や全種類を公開|実例付で解説

(最終更新月:2023年1月)

✔このような方へ向けて書かれた記事となります

「Djangoのsignalって何ができるのだろうか?」

「signalの書き方が知りたい」

「sogmalを使った実例をおしえてほしい」

✔当記事を通じてお伝えすること

当記事では、DjangoのSignalsについての基礎知識はもちろん、具体的な書き方まで解説しています。

ぜひ最後までご覧ください。

筆者プロフィール

【現職】プロダクトマネージャー

【副業】ブログ(月間20万PV)/YouTube/Web・アプリ制作

「プログラミング × ライティング × 営業」の経験を活かし、30後半からのIT系職へシフト。現在はプロダクトマネージャーとして、さまざまな関係者の間に入り奮闘してます。当サイトでは、実際に手を動かせるWebアプリの開発を通じて、プログラミングはもちろん、IT職に必要な情報を提供していきます。

【当ブログで紹介しているサイト】

当サイトチュートリアルで作成したデモ版日報アプリ

Django × Reactで開発したツール系Webアプリ

✔人に見せても恥ずかしくないコードを書こう

「リーダブルコード」は、わかりやすく良いコードの定義を教えてくれる本です。

  • 見るからにきれいなコードの書き方
  • コードの分割方法
  • 変数や関数の命名規則

エンジニアのスタンダートとすべき基準を一から解説しています。

何回も読むのに値する本なので、ぜひ手にとって読んでみてください。

Djangoのsignalとは?

DjangoのSignal(シグナル)とは、アプリケーションの実行中に起こるイベントで、特定の処理をおこなえる機能です。

アプリケーションの実行中におこるイベントの例としては以下のとおり。

Signalを活用すると、特定のイベントが発生した際に任意のコードを実行できるようになるのです。

signalの種類

signalにはいくつも種類があります。

Signalの種類引数パスできること
pre_initsender
args
kwargs
django.db.models.signalsModelインスタンスの__init__メソッドの最初で実行
post_initsender
instance
django.db.models.signalsModelインスタンスの__init__メソッドのあとに実行される
pre_savesender
instance
raw
using
update_fields
django.db.models.signalsModelインスタンスのsaveメソッドの前に実行
post_savesender
instance
created
raw
using
update_fields
django.db.models.signalsModelインスタンスのsaveメソッドのあとに実行
pre_deletesender
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.signalsManyToManyFieldに変化があった際に実行される
class_preparedsenderdjango.db.models.signalsモデルクラスが定義され、Djangoのシステムに登録された際に実行される
pre_migratesender
app_config
verbosity
interactive
stdout
using
plan
apps
django.db.models.signalsmigrateコマンドの前に実行される
post_migrate上に同じdjango.db.models.signalsmigrateコマンドの後に実行される
request_startedsender
environ
django.core.signalsHTTPリクエストのプロセスがスタートしたら実行される
request_finishedsenderdjango.core.signalsHTTPレスポンスを返したあとに実行される
got_request_exceptionsender
request
django.core.signalsHTTPリクエストがエラーとなった際に実行される
setting_changesender
setting
value
enter
django.test.signalsコンテキストマネージャーを通じて、テストの設定が変更された際に実行される
template_renderedsender
template
context
django.test.signalsテストシステムがテンプレートを読み込んだ際に実行される
connection_createdsender
connection
django.db.backends.signalsデータベースとコネクトされた際に実行される

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つをチェックしています。

実行してエラーがでなければ問題ありません。

日報アプリにプロフィール編集ページの実装

チュートリアルを続けてくださっている方に向けて、自動生成されたプロフィールを編集するページを作っていきます。

過去の記事でご紹介している方法なので、コードだけご紹介。

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を使った方法になりますので、ここで覚えておくと色々と応用も聞きますのでお楽しみに!

モバイルバージョンを終了