1. 程式人生 > >django訊號 signal

django訊號 signal

django自帶一套訊號機制來幫助我們在框架的不同位置之間傳遞資訊。也就是說,當某一事件發生時,訊號系統可以允許一個或多個傳送者(senders)將通知或訊號(signals)傳送給一組接受者(receivers)。

訊號系統包含以下三要素:

  • 傳送者-訊號的發出方

  • 訊號-訊號本身

  • 接收者-訊號的接受者

Django內建了一整套訊號,下面是一些比較常用的:

  • django.db.models.signals.pre_save & django.db.models.signals.post_save

在ORM模型的save()方法呼叫之前或之後傳送訊號

  • django.db.models.signals.pre_delete & django.db.models.signals.post_delete

在ORM模型或查詢集的delete()方法呼叫之前或之後傳送訊號。

  • django.db.models.signals.m2m_changed

當多對多欄位被修改時傳送訊號。

  • django.core.signals.request_started & django.core.signals.request_finished

當接收和關閉HTTP請求時傳送訊號。

一、監聽訊號

要接收訊號,請使用Signal.connect()

方法註冊一個接收器。當訊號傳送後,會呼叫這個接收器。

方法原型:

Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)[source]

引數:

receiver :當前訊號連線的回撥函式,也就是處理訊號的函式。 
sender :指定從哪個傳送方接收訊號。 
weak : 是否弱引用
dispatch_uid :訊號接收器的唯一識別符號,以防訊號多次傳送。

下面以如何接收每次HTTP請求結束後傳送的訊號為例,連線到Django內建的現成的request_finished

訊號。

1. 編寫接收器

接收器其實就是一個Python函式或者方法:

def my_callback(sender, **kwargs):
    print("Request finished!")

請注意,所有的接收器都必須接收一個sender引數和一個**kwargs萬用字元引數。

2. 連線接收器

有兩種方法可以連線接收器,一種是下面的手動方式:

from django.core.signals import request_finished

request_finished.connect(my_callback) 

另一種是使用receiver()裝飾器:

from django.core.signals import request_finished
from django.dispatch import receiver @receiver(request_finished) def my_callback(sender, **kwargs): print("Request finished!") 

3. 接收特定傳送者的訊號

一個訊號接收器,通常不需要接收所有的訊號,只需要接收特定傳送者發來的訊號,所以需要在sender引數中,指定傳送方。下面的例子,只接收MyModel模型的例項儲存前的訊號。

from django.db.models.signals import pre_save
from django.dispatch import receiver from myapp.models import MyModel @receiver(pre_save, sender=MyModel) def my_handler(sender, **kwargs): ... 

my_handler函式只在MyModel例項儲存時被呼叫。

4. 防止重複訊號

為了防止重複訊號,可以設定dispatch_uid引數來標識你的接收器,識別符號通常是一個字串,如下所示:

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier") 

最後的結果是,對於每個唯一的dispatch_uid值,你的接收器都只繫結到訊號一次。

二、自定義訊號

除了Django為我們提供的內建訊號(比如前面列舉的那些),很多時候,我們需要自己定義訊號。

類原型:class Signal(providing_args=list)[source]

所有的訊號都是django.dispatch.Signal的例項。providing_args引數是一個列表,由訊號將提供給監聽者的引數的名稱組成。可以在任何時候修改providing_args引數列表。

下面定義了一個新訊號:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"]) 

上面的例子定義了pizza_done訊號,它向接受者提供size和toppings 引數。

三、傳送訊號

Django中有兩種方法用於傳送訊號。

Signal.send(sender, **kwargs)[source]


Signal.send_robust(sender,** kwargs)[source] 

必須提供sender引數(大部分情況下是一個類名),並且可以提供任意數量的其他關鍵字引數。

例如,這樣來發送前面的pizza_done訊號:

class PizzaStore(object):
    ... def send_pizza(self, toppings, size): pizza_done.send(sender=self.__class__, toppings=toppings, size=size) ... 

send()send_robust()返回一個元組對的列表[(receiver, response), ... ],表示接收器和響應值二元元組的列表。

四、斷開訊號

方法:

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)[source]

Signal.disconnect()用來斷開訊號的接收器。和Signal.connect()中的引數相同。如果接收器成功斷開,返回True,否則返回False。

五、訊號使用例項

訊號可能不太好理解,下面我在Django內編寫一個例子示範一下:

首先在根URLCONF中寫一條路由:

from django.conf.urls import url
from django.contrib import admin from app1 import views urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^signal/$', views.create_signal), ] 

這個很好理解,我在專案裡建立了一個app1應用,在它的views.py中建立了一個create_signal檢視,通過/signal/可以訪問這個檢視。這些都不重要,隨便配置,只要能正常工作就行。

然後在views.py中自定義一個訊號,以及建立create_signal檢視:

from django.shortcuts import HttpResponse
import time import django.dispatch from django.dispatch import receiver # 定義一個訊號 work_done = django.dispatch.Signal(providing_args=['path', 'time']) def create_signal(request): url_path = request.path print("我已經做完了工作。現在我傳送一個訊號出去,給那些指定的接收器。") # 傳送訊號,將請求的url地址和時間一併傳遞過去 work_done.send(create_signal, path=url_path, time=time.strftime("%Y-%m-%d %H:%M:%S")) return HttpResponse("200,ok") 

自定義的訊號名叫work_done,它很簡單,接收請求url地址和請求時間兩個引數。

create_signal檢視內,獲取請求的url,生成請求的時間,作為引數,傳遞到send方法。

這樣,我們就傳送了一個訊號。

然後,再寫一個接收器:

@receiver(work_done, sender=create_signal) def my_callback(sender, **kwargs): print("我在%s時間收到來自%s的訊號,請求url為%s" % (kwargs['time'], sender, kwargs["path"])) 

通過裝飾器註冊為接收器。內部接收字典引數,並解析打印出來。

最終views.py檔案如下:

from django.shortcuts import HttpResponse
import time import django.dispatch from django.dispatch import receiver # Create your views here. # 定義一個訊號 work_done = django.dispatch.Signal(providing_args=['path', 'time']) def create_signal(request): url_path = request.path print("我已經做完了工作。現在我傳送一個訊號出去,給那些指定的接收器。") # 傳送訊號,將請求的IP地址和時間一併傳遞過去 work_done.send(create_signal, path=url_path, time=time.strftime("%Y-%m-%d %H:%M:%S")) return HttpResponse("200,ok") @receiver(work_done, sender=create_signal) def my_callback(sender, **kwargs): print("我在%s時間收到來自%s的訊號,請求url為%s" % (kwargs['time'], sender, kwargs["path"])) 

現在可以來測試一下。python manage.py runserver啟動伺服器。瀏覽器中訪問http://127.0.0.1:8000/signal/。重點不再瀏覽器的返回,而在後臺返回的內容:

我已經做完了工作。現在我傳送一個訊號出去,給那些指定的接收器。
我在2017-12-18 17:10:12時間收到來自<function create_signal at 0x0000000003AFF840>的訊號,請求url為/signal/

這些提示資訊,可以在Pycharm中看到,或者在命令列環境中看到。