信號 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中看到,或者在命令行環境中看到。
信號 signal