# 信號
Django包含一個“信號的分發器”,允許解耦的應用在信號出現在框架的任何地方時,都能獲得通知。簡單來說,信號允許指定的 _發送器_通知一系列的_接收器_,一些操作已經發生了。當一些代碼會相同事件感興趣時,會十分有幫助。
Django 提供了[_一系列的內建信號_](../ref/signals.html),允許用戶的代碼獲得DJango的特定操作的通知。這包含一些有用的通知:
* [`django.db.models.signals.pre_save`](../ref/signals.html#django.db.models.signals.pre_save "django.db.models.signals.pre_save") & [`django.db.models.signals.post_save`](../ref/signals.html#django.db.models.signals.post_save "django.db.models.signals.post_save")
在模型 [`save()`](../ref/models/instances.html#django.db.models.Model.save "django.db.models.Model.save")方法調用之前或之后發送。
* [`django.db.models.signals.pre_delete`](../ref/signals.html#django.db.models.signals.pre_delete "django.db.models.signals.pre_delete") & [`django.db.models.signals.post_delete`](../ref/signals.html#django.db.models.signals.post_delete "django.db.models.signals.post_delete")
在模型[`delete()`](../ref/models/instances.html#django.db.models.Model.delete "django.db.models.Model.delete")方法或查詢集的[`delete()`](../ref/models/querysets.html#django.db.models.query.QuerySet.delete "django.db.models.query.QuerySet.delete") 方法調用之前或之后發送。
* [`django.db.models.signals.m2m_changed`](../ref/signals.html#django.db.models.signals.m2m_changed "django.db.models.signals.m2m_changed")
模型上的 [`ManyToManyField`](../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 修改時發送。
* [`django.core.signals.request_started`](../ref/signals.html#django.core.signals.request_started "django.core.signals.request_started") & [`django.core.signals.request_finished`](../ref/signals.html#django.core.signals.request_finished "django.core.signals.request_finished")
Django建立或關閉HTTP 請求時發送。
關于完整列表以及每個信號的完整解釋,請見[_內建信號的文檔_](../ref/signals.html) 。
你也可以[定義和發送你自己的自定義信號](#defining-and-sending-signals);見下文。
## 監聽信號
你需要注冊一個_接收器_函數來接受信號,它在信號使用[`Signal.connect()`](#django.dispatch.Signal.connect "django.dispatch.Signal.connect")發送時被調用:
`Signal.``connect`(_receiver_[, _sender=None_, _weak=True_, _dispatch_uid=None_])
<table class="docutils field-list" frame="void" rules="none"><colgroup><col class="field-name"><col class="field-body"></colgroup><tbody valign="top"><tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body">* **receiver** – 和這個信號連接的回調函數。詳見[_接收器函數_](#receiver-functions)。
* **sender** – 指定一個特定的發送器,來從它那里接受信號。詳見[_連接由指定發送器發送的信號_](#connecting-to-specific-signals)。
* **weak** – DJango通常以弱引用儲存信號處理器。這就是說,如果你的接收器是個局部變量,可能會被垃圾回收。當你調用信號的`connect()`方法是,傳遞 `weak=False`來防止這樣做。
* **dispatch_uid** – 一個信號接收器的唯一標識符,以防信號多次發送。詳見[_防止重復的信號_](#preventing-duplicate-signals)。
</td></tr></tbody></table>
讓我們來看一看它如何通過注冊在每次在HTTP請求結束時調用的信號來工作。我們將會連接到[`request_finished`](../ref/signals.html#django.core.signals.request_finished "django.core.signals.request_finished") 信號。
### 接收器函數
首先,我們需要定義接收器函數。接受器可以是Python函數或者方法:
```
def my_callback(sender, **kwargs):
print("Request finished!")
```
注意函數接受`sender`函數,以及通配符關鍵字參數(`**kwargs`);所有信號處理器都必須接受這些參數。
我們[過一會兒](#connecting-to-signals-sent-by-specific-senders)再關注發送器,現在先看一看`**kwargs`參數。所有信號都發送關鍵字參數,并且可以在任何時候修改這些關鍵字參數。在 [`request_finished`](../ref/signals.html#django.core.signals.request_finished "django.core.signals.request_finished")的情況下,它被記錄為不發送任何參數,這意味著我們可能需要像`my_callback(sender)`一樣編寫我們自己的信號處理器。
這是錯誤的 -- 實際上,如果你這么做了,Django會拋出異常。這是因為無論什么時候信號中添加了參數,你的接收器都必須能夠處理這些新的參數。
### 連接接收器函數
有兩種方法可以將一個接收器連接到信號。你可以采用手動連接的方法:
```
from django.core.signals import request_finished
request_finished.connect(my_callback)
```
或者使用[`receiver()`](#django.dispatch.receiver "django.dispatch.receiver") 裝飾器來自動連接:
`receiver`(_signal_)
<table class="docutils field-list" frame="void" rules="none"><colgroup><col class="field-name"><col class="field-body"></colgroup><tbody valign="top"><tr class="field-odd field"><th class="field-name">Parameters:</th><td class="field-body">**signal** – A signal or a list of signals to connect a function to.</td></tr></tbody></table>
下面是使用裝飾器連接的方法:
```
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
```
現在,我們的`my_callback`函數會在每次請求結束時調用。
這段代碼應該放在哪里?
嚴格來說,信號處理和注冊的代碼應該放在你想要的任何地方,但是推薦避免放在應用的根模塊和`models`模塊中,以盡量減少產生導入代碼的副作用。
實際上,信號處理通常定義在應用相關的`signals`子模塊中。信號接收器在你應用配置類中的[`ready()`](../ref/applications.html#django.apps.AppConfig.ready "django.apps.AppConfig.ready") 方法中連接。如果你使用;額 [`receiver()`](#django.dispatch.receiver "django.dispatch.receiver")裝飾器,只是在[`ready()`](../ref/applications.html#django.apps.AppConfig.ready "django.apps.AppConfig.ready")內部導入`signals`子模塊就可以了。
Changed in Django 1.7:
由于[`ready()`](../ref/applications.html#django.apps.AppConfig.ready "django.apps.AppConfig.ready")并不在Django之前版本中存在,信號的注冊通常在`models`模塊中進行。
注意
[`ready()`](../ref/applications.html#django.apps.AppConfig.ready "django.apps.AppConfig.ready") 方法會在測試期間執行多次,所以你可能想要[_防止重復的信號_](#preventing-duplicate-signals),尤其是打算在測試中發送它們的情況。
### 連接由指定發送器發送的信號
一些信號會發送多次,但是你只想接收這些信號的一個確定的子集。例如,考慮 [`django.db.models.signals.pre_save`](../ref/signals.html#django.db.models.signals.pre_save "django.db.models.signals.pre_save") 信號,它在模型保存之前發送。大多數情況下,你并不需要知道 _任何_模型何時保存 -- 只需要知道一個_特定的_模型何時保存。
在這些情況下,你可以通過注冊來接收只由特定發送器發出的信號。對于[`django.db.models.signals.pre_save`](../ref/signals.html#django.db.models.signals.pre_save "django.db.models.signals.pre_save")的情況, 發送者是被保存的模型類,所以你可以認為你只需要由某些模型發出的信號:
```
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`實例保存時被調用。
不同的信號使用不同的對象作為他們的發送器;對于每個特定信號的細節,你需要查看[_內建信號的文檔_](../ref/signals.html)。
### 防止重復的信號
在一些情況下,向接收者發送信號的代碼可能會執行多次。這會使你的接收器函數被注冊多次,并且導致它對于同一信號事件被調用多次。
如果這樣的行為會導致問題(例如在任何時候模型保存時使用信號來發送郵件),傳遞一個唯一的標識符作為 `dispatch_uid`參數來標識你的接收器函數。標識符通常是一個字符串,雖然任何可計算哈希的對象都可以。最后的結果是,對于每個唯一的`dispatch_uid`值,你的接收器函數都只被信號調用一次:
```
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
```
## 定義和發送信號
你的應用可以利用信號功能來提供自己的信號。
### 定義信號
_class _`Signal`([_providing_args=list_])
所有信號都是 [`django.dispatch.Signal`](#django.dispatch.Signal "django.dispatch.Signal") 的實例。`providing_args`是一個列表,包含參數的名字,它們由信號提供給監聽者。理論上是這樣,但是實際上并沒有任何檢查來保證向監聽者提供了這些參數。
例如:
```
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
```
這段代碼聲明了`pizza_done`信號,它向接受者提供`toppings`和 `size` 參數。
要記住你可以在任何時候修改參數的列表,所以首次嘗試的時候不需要完全確定API。
### 發送信號
Django中有兩種方法用于發送信號。
`Signal.``send`(_sender_, _**kwargs_)
`Signal.``send_robust`(_sender_, _**kwargs_)
調用 [`Signal.send()`](#django.dispatch.Signal.send "django.dispatch.Signal.send")或者[`Signal.send_robust()`](#django.dispatch.Signal.send_robust "django.dispatch.Signal.send_robust")來發送信號。你必須提供`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), ... `],它代表了被調用的接收器函數和他們的響應值。
`send()` 與 `send_robust()`在處理接收器函數產生的異常時有所不同。`send()`_不會_ 捕獲任何由接收器產生的異常。它會簡單地讓錯誤往上傳遞。所以在錯誤產生的情況,不是所有接收器都會獲得通知。
`send_robust()`捕獲所有繼承自Python `Exception`類的異常,并且確保所有接收器都能得到信號的通知。如果發生了錯誤,錯誤的實例會在產生錯誤的接收器的二元組中返回。
New in Django 1.8:
調用`send_robust()`的時候,所返回的錯誤的`__traceback__`屬性上會帶有 traceback。
## 斷開信號
`Signal.``disconnect`([_receiver=None_, _sender=None_, _weak=True_, _dispatch_uid=None_])
調用[`Signal.disconnect()`](#django.dispatch.Signal.disconnect "django.dispatch.Signal.disconnect")來斷開信號的接收器。 [`Signal.connect()`](#django.dispatch.Signal.connect "django.dispatch.Signal.connect")中描述了所有參數。如果接收器成功斷開,返回 `True` ,否則返回`False`。
`receiver`參數表示要斷開的已注冊接收器。如果`dispatch_uid` 用于定義接收器,可以為`None`。
Changed in Django 1.8:
增加了返回的布爾值。
> 譯者:[Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html),原文:[Signals](https://docs.djangoproject.com/en/1.8/topics/signals/)。
>
> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 協議發布,轉載請保留作者署名和文章出處。
>
> [Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html)人手緊缺,有興趣的朋友可以加入我們,完全公益性質。交流群:467338606。
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架