訊息框架

在 Web 應用程式中,您通常需要在處理表單或其他類型的使用者輸入後,向使用者顯示一次性的通知訊息(也稱為「快閃訊息」)。

為此,Django 為匿名和已驗證的使用者提供對基於 Cookie 和 Session 的訊息傳遞的完整支援。訊息框架允許您在一個請求中暫時儲存訊息,並在後續請求(通常是下一個請求)中檢索這些訊息以進行顯示。每個訊息都會被標記一個特定的 level,該 level 決定了其優先順序(例如,infowarningerror)。

啟用訊息

訊息是透過 中介軟體 類別和對應的 上下文處理器 來實現的。

django-admin startproject 建立的預設 settings.py 已經包含啟用訊息功能所需的所有設定

  • 'django.contrib.messages' 位於 INSTALLED_APPS 中。

  • MIDDLEWARE 包含 'django.contrib.sessions.middleware.SessionMiddleware''django.contrib.messages.middleware.MessageMiddleware'

    預設的 儲存後端 依賴 sessions。這就是為什麼必須啟用 SessionMiddleware 並且在 MIDDLEWARE 中出現在 MessageMiddleware 之前。

  • 在您的 TEMPLATES 設定中定義的 DjangoTemplates 後端的 'context_processors' 選項包含 'django.contrib.messages.context_processors.messages'

如果您不想使用訊息,您可以從您的 INSTALLED_APPS 中移除 'django.contrib.messages'、從 MIDDLEWARE 中移除 MessageMiddleware 行,並從 TEMPLATES 中移除 messages 上下文處理器。

設定訊息引擎

儲存後端

訊息框架可以使用不同的後端來儲存臨時訊息。

Django 在 django.contrib.messages 中提供了三個內建的儲存類別

class storage.session.SessionStorage

此類別將所有訊息儲存在請求的 Session 內。因此,它需要 Django 的 contrib.sessions 應用程式。

class storage.cookie.CookieStorage

此類別將訊息資料儲存在 Cookie 中(以秘密雜湊簽署,以防止篡改),以跨請求持續通知。如果 Cookie 資料大小超過 2048 位元組,則會捨棄舊訊息。

class storage.fallback.FallbackStorage

此類別首先使用 CookieStorage,然後回退到使用 SessionStorage 來儲存無法放入單一 Cookie 中的訊息。它也需要 Django 的 contrib.sessions 應用程式。

這種行為會盡可能避免寫入 Session。它應該在一般情況下提供最佳效能。

FallbackStorage 是預設的儲存類別。如果它不適合您的需求,您可以透過將 MESSAGE_STORAGE 設定為其完整導入路徑來選擇另一個儲存類別,例如

MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage"
class storage.base.BaseStorage

若要編寫您自己的儲存類別,請子類化 django.contrib.messages.storage.base 中的 BaseStorage 類別,並實作 _get_store 方法。

訊息等級

訊息框架基於類似於 Python 日誌模組的可設定等級架構。訊息等級允許您依類型對訊息進行分組,以便它們可以在檢視和範本中進行篩選或以不同的方式顯示。

可以從 django.contrib.messages 直接匯入的內建等級為

常數

目的

DEBUG

與開發相關的訊息,這些訊息在生產部署中將被忽略(或移除)

INFO

給使用者的資訊性訊息

SUCCESS

動作成功,例如「您的個人資料已成功更新」

WARNING

未發生故障,但可能迫在眉睫

ERROR

動作成功,或發生其他故障

MESSAGE_LEVEL 設定可用於變更記錄的最低等級(或者可以依請求變更)。嘗試加入等級低於此等級的訊息將被忽略。

訊息標籤

訊息標籤是訊息等級的字串表示形式,加上直接在檢視中加入的任何額外標籤(請參閱下方加入額外訊息標籤以了解更多詳細資訊)。標籤儲存在字串中,並以空格分隔。通常,訊息標籤用作 CSS 類別,以根據訊息類型自訂訊息樣式。預設情況下,每個等級都有一個單一標籤,該標籤是其自身常數的小寫版本

等級常數

標籤

DEBUG

debug

INFO

info

SUCCESS

success

WARNING

warning

ERROR

error

若要變更訊息等級(內建或自訂)的預設標籤,請將 MESSAGE_TAGS 設定為包含您要變更之等級的字典。由於這會擴充預設標籤,因此您只需要為您想要覆寫的等級提供標籤

from django.contrib.messages import constants as messages

MESSAGE_TAGS = {
    messages.INFO: "",
    50: "critical",
}

在檢視和範本中使用訊息

add_message(request, level, message, extra_tags='', fail_silently=False)[原始碼]

加入訊息

若要加入訊息,請呼叫

from django.contrib import messages

messages.add_message(request, messages.INFO, "Hello world.")

某些快捷方法提供了一種標準方法來加入具有常用標籤的訊息(這些標籤通常表示為訊息的 HTML 類別)

messages.debug(request, "%s SQL statements were executed." % count)
messages.info(request, "Three credits remain in your account.")
messages.success(request, "Profile details updated.")
messages.warning(request, "Your account expires in three days.")
messages.error(request, "Document deleted.")

顯示訊息

get_messages(request)[原始碼]

在您的範本中,使用類似

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
    {% endfor %}
</ul>
{% endif %}

如果您使用的是上下文處理器,則您的範本應該使用 RequestContext 進行呈現。否則,請確保 messages 對範本上下文可用。

即使您知道只有一條訊息,您仍然應該迭代 messages 序列,因為否則訊息儲存將不會為下一個請求清除。

上下文處理器還提供了一個 DEFAULT_MESSAGE_LEVELS 變數,它是訊息等級名稱到其數值的映射

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
        {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %}
        {{ message }}
    </li>
    {% endfor %}
</ul>
{% endif %}

在模板之外,您可以使用 get_messages()

from django.contrib.messages import get_messages

storage = get_messages(request)
for message in storage:
    do_something_with_the_message(message)

例如,您可以獲取所有訊息,並將它們以 JSONResponseMixin 的形式返回,而不是以 TemplateResponseMixin 的形式返回。

get_messages() 將會返回已設定的儲存後端的實例。

Message 類別

class Message[原始碼]

當您在模板中迴圈處理訊息列表時,您所獲得的是 Message 類別的實例。它們只有少數幾個屬性

  • message:訊息的實際文字內容。

  • level:描述訊息類型的整數(請參閱上方訊息層級章節)。

  • tags:結合所有訊息標籤的字串(extra_tagslevel_tag),並以空格分隔。

  • extra_tags:一個字串,包含此訊息的自訂標籤,以空格分隔。預設為空。

  • level_tag:層級的字串表示。預設情況下,它是相關常數名稱的小寫版本,但如果您需要,可以使用 MESSAGE_TAGS 設定來變更此設定。

建立自訂訊息層級

訊息層級只不過是整數,因此您可以定義自己的層級常數,並使用它們來建立更客製化的使用者回饋,例如:

CRITICAL = 50


def my_view(request):
    messages.add_message(request, CRITICAL, "A serious error occurred.")

在建立自訂訊息層級時,您應謹慎避免覆寫現有的層級。內建層級的值為:

等級常數

DEBUG

10

INFO

20

SUCCESS

25

WARNING

30

ERROR

40

如果您需要在 HTML 或 CSS 中識別自訂層級,您需要透過 MESSAGE_TAGS 設定來提供對應。

注意

如果您正在建立可重複使用的應用程式,建議僅使用內建的訊息層級,而不要依賴任何自訂層級。

變更每個請求的最小記錄層級

可以使用 set_level 方法來設定每個請求的最小記錄層級

from django.contrib import messages

# Change the messages level to ensure the debug message is added.
messages.set_level(request, messages.DEBUG)
messages.debug(request, "Test message...")

# In another request, record only messages with a level of WARNING and higher
messages.set_level(request, messages.WARNING)
messages.success(request, "Your profile was updated.")  # ignored
messages.warning(request, "Your account is about to expire.")  # recorded

# Set the messages level back to default.
messages.set_level(request, None)

同樣地,可以使用 get_level 來檢索目前有效的層級

from django.contrib import messages

current_level = messages.get_level(request)

如需有關最小記錄層級如何運作的詳細資訊,請參閱上方的訊息層級

新增額外的訊息標籤

為了更直接地控制訊息標籤,您可以選擇性地提供一個字串,其中包含要新增至任何新增方法的額外標籤

messages.add_message(request, messages.INFO, "Over 9000!", extra_tags="dragonball")
messages.error(request, "Email box full", extra_tags="email")

額外標籤會新增在該層級的預設標籤之前,並以空格分隔。

當訊息框架停用時靜默失敗

如果您正在撰寫可重複使用的應用程式(或其他程式碼),並且想要包含訊息功能,但不希望要求使用者在他們不需要時啟用此功能,您可以將額外的關鍵字引數 fail_silently=True 傳遞給任何 add_message 系列的方法。例如

messages.add_message(
    request,
    messages.SUCCESS,
    "Profile details updated.",
    fail_silently=True,
)
messages.info(request, "Hello world.", fail_silently=True)

注意

設定 fail_silently=True 只會隱藏當訊息框架停用且嘗試使用 add_message 系列方法之一時,原本會發生的 MessageFailure。它不會隱藏可能由於其他原因而發生的失敗。

在基於類別的視圖中新增訊息

class views.SuccessMessageMixin

為基於 FormView 的類別新增一個成功訊息屬性

get_success_message(cleaned_data)

cleaned_data 是表單中已清除的資料,用於字串格式化

範例 views.py:

from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import Author


class AuthorCreateView(SuccessMessageMixin, CreateView):
    model = Author
    success_url = "/success/"
    success_message = "%(name)s was created successfully"

可以使用 %(field_name)s 語法來對 form 中已清除的資料進行字串插值。對於 ModelForms,如果您需要存取已儲存 object 中的欄位,請覆寫 get_success_message() 方法。

ModelForms 的範例 views.py:

from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import ComplicatedModel


class ComplicatedCreateView(SuccessMessageMixin, CreateView):
    model = ComplicatedModel
    success_url = "/success/"
    success_message = "%(calculated_field)s was created successfully"

    def get_success_message(self, cleaned_data):
        return self.success_message % dict(
            cleaned_data,
            calculated_field=self.object.calculated_field,
        )

訊息的過期

訊息會標記為在儲存實例被迭代時清除(並在處理回應時清除)。

為了避免訊息被清除,您可以在迭代後將訊息儲存設定為 False

storage = messages.get_messages(request)
for message in storage:
    do_something_with(message)
storage.used = False

平行請求的行為

由於 Cookie(以及 Session)的運作方式,當同一用戶端發出多個平行請求以設定或取得訊息時,任何使用 Cookie 或 Session 的後端的行為都是未定義的。例如,如果一個用戶端在一個視窗(或標籤)中發起一個建立訊息的請求,然後在另一個視窗中發起另一個提取任何未迭代訊息的請求,在第一個視窗重新導向之前,訊息可能會出現在第二個視窗中,而不是預期的第一個視窗中。

簡而言之,當涉及來自同一用戶端的多個同時請求時,無法保證訊息會傳遞到建立它們的同一個視窗,或者在某些情況下根本不會傳遞。請注意,這在大多數應用程式中通常不是問題,並且在 HTML5 中將不再是問題,因為每個視窗/標籤都有自己的瀏覽上下文。

設定

一些設定可讓您控制訊息行為

對於使用 Cookie 的後端,Cookie 的設定取自 Session Cookie 設定

測試

Django 5.0 新增。

此模組提供客製化的測試斷言方法,用於測試附加到 HttpResponse 的訊息。

若要使用此斷言,請將 MessagesTestMixin 新增至類別階層

from django.contrib.messages.test import MessagesTestMixin
from django.test import TestCase


class MsgTestCase(MessagesTestMixin, TestCase):
    pass

然後,在您的測試中繼承自 MsgTestCase

MessagesTestMixin.assertMessages(response, expected_messages, ordered=True)[原始碼]

斷言新增至 messagesresponse 符合 expected_messages

expected_messagesMessage 物件的清單。

預設情況下,比較是依賴於順序的。您可以將 ordered 引數設定為 False 來停用此功能。

回到頂端