Help us understand the problem. What is going on with this article?

Djangoシグナル登録の簡単なまとめ

More than 5 years have passed since last update.

Djangoのシグナルについて

はじめに

Djangoで特定のイベント発生時にシグナルを受信することができます。

HTTPリクエストを送信する時に呼ばれるrequest_startedや、HTTPリクエストの処理が終了した時に呼ばれるrequest_finishedがそれにあたります。

Receiver関数の登録

レシーバ関数を登録する方法は2通り
・Signal.connect
・receiverデコレータ

connect

Django1.7.1

connect(self, receiver, sender=None, weak=True, dispatch_uid=None)

Django1.7.1のconnect関数になります。
各パラメータは、

receiver
function or instance method
シグナル受信時に実行されるオブジェクトになります。
sender
特定のsenderからのみシグナルを受信する場合に設定します。
weak
ここがTrueならreceiverが弱い参照で保持されます。
dispatch_uid
特定のシグナルに対して重複したモジュールを登録する場合に設定します。
設定する場合は、文字列を指定すれば問題ないです。

Djangoのauthモジュールの例

#user loginのシグナル定義
#django.contrib.auth.signals
user_logged_in = Signal(providing_args=['request', 'user'])

#receiverファンクション
def login_callback(sender, **kwargs):
    #Signal定義のproviding_argsに定義されているオブジェクトを取得できる
    request = kwargs['request']
    user = kwargs['user']

#connect
user_logged_in.connect(login_callback)

これでログイン後にlogin_callbackが呼ばれます。
実際にuser_logged_inがシグナル送信している箇所をみてみます。

    ##django.contrib.auth.__init__
    request.session[SESSION_KEY] = user.pk
    request.session[BACKEND_SESSION_KEY] = user.backend
    request.session[HASH_SESSION_KEY] = session_auth_hash
    if hasattr(request, 'user'):
        request.user = user
    rotate_token(request)
    user_logged_in.send(sender=user.__class__, request=request, user=user)

sendメソッドでsender、request,userを設定しているのがわかります。

receiverデコレータ

レシーバ関数を登録するもう一つの方法は、receiverデコレータを使う方法です。

@receiver(signal, sender=myModel)
def test_callback(sender, **kwargs):
    pass

こんな感じで定義します。
receiverの中を見てみます。

#django.dispatch.dispatcher
def receiver(signal, **kwargs):
    def _decorator(func):
        if isinstance(signal, (list, tuple)):
            for s in signal:
                s.connect(func, **kwargs)
        else:
            signal.connect(func, **kwargs)
        return func
    return _decorator

見てわかる通りconnectを呼んでいます。
複数のシグナルを登録することも可能です。

デコレータ

補足としてreceiverデコレータの挙動をこの自作receiverを参考に見てみます。

#decodeco.py
def receiver(signal, **kwargs):
    def _deco(func): #func=test
        print('receiver %s' % kwargs['sender'])
        print(signal) #signal=sample
        print(func)
        return func
    return _deco

def sample():
    print('sample')

@receiver(sample, sender='sender_mymodel')
def test(sender, **kwargs):
    print('test')

if __name__ == '__main__':
    test(1)

これを実行してみます。

$ python3 decodeco.py 
receiver sender_mymodel
<function sample at 0x7f63129f3050>
<function test at 0x7f63129f3170>
test

デコレータで展開さているのが少しわかると思います。

実際にconnectで登録したuser_logged_inreceiverに変更してみます。

@receiver(user_logged_in, sender=myModel)
def login_callback(sender, **kwargs):
    request = kwargs['request']
    user = kwargs['user']

さらにこのreceiverをわかりやすくしてみます。

#django.dispatch.dispatcher.receiverをlogin_callbackで展開した
def receiver(user_logged_in, {'sender': myModel}):
    def _decorator(login_callback):
        user_logged_in.connect(login_callback, {'sender': myModel})
        return login_callback
    return _decorator

簡単に書くとこんな感じでしょうか。
connectreceiverで登録するのもどちらも同じ様なものなので、用途によって使い分けるといいと思います。

最後に

connectの中を見てみます。

    #django.dispatch.dispatcher
    #長いので、途中省略してます
    def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
        #ここで各レシーバのキーを作成します
        if dispatch_uid:
            lookup_key = (dispatch_uid, _make_id(sender))
        else:
            lookup_key = (_make_id(receiver), _make_id(sender))

        if weak:
            ref = weakref.ref
            receiver_object = receiver
            # Check for bound methods
            if hasattr(receiver, '__self__') and hasattr(receiver, '__func__'):
                ref = WeakMethod
                receiver_object = receiver.__self__
            if sys.version_info >= (3, 4):
                receiver = ref(receiver)
                weakref.finalize(receiver_object, self._remove_receiver)
            else:
                receiver = ref(receiver, self._remove_receiver)

        with self.lock:
            self._clear_dead_receivers()
            for r_key, _ in self.receivers:
                if r_key == lookup_key:
                    break
            else:
                #ここでレシーバ関数を登録
                self.receivers.append((lookup_key, receiver))
            self.sender_receivers_cache.clear()

connectの中を見ると、弱参照の話も必要ですが長くなるので今回は省略します。
最後のself.receivers.append((lookup_key, recever))でレシーバを登録しているのがわかると思います。

これでレシーバ登録までの流れは終わりです。
機会があればsend_live_receiversもまとめられればいいなと思います。

間違っている箇所等ありましたら指摘いただければ助かります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした