消息框架

在网页应用中,相当常见的是,你需要在处理完一个表单或一些其他类型的用户输入后,向用户显示一个一次性的通知消息(也称为“即时消息”)。

为此,Django 为匿名用户和认证用户提供了对基于 cookie 和会话的消息传递的全面支持。消息框架允许你在一个请求中临时存储消息,并在随后的请求(通常是下一个请求)中检索显示。每条消息都有一个特定的 level 标签,以确定其优先级(例如,infowarningerror)。

启用消息

消息是通过一个 中间件 类和相应的 上下文处理器 来实现的。

django-admin startproject 创建的默认 settings.py 已经包含了启用消息功能所需的所有配置:

  • 'django.contrib.messages'INSTALLED_APPS 中。

  • MIDDLEWARE 包含 'django.contrib.session.middleware.SessionMiddleware''django.contrib.message.middleware.MessageMiddleware'

    默认的 存储后端 依赖于 会话。这就是为什么 SessionMiddleware 必须被启用,并且出现在 MIDDLEWAREMessageMiddleware 之前。

  • 在你的 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

这个类在请求的会话中存储所有的消息。因此它需要 Django 的 contrib.session 应用。

class storage.cookie.CookieStorage

该类将消息数据存储在 cookie 中(用密钥哈希签名,以防止被篡改),以便在不同的请求中持久保存通知。如果 cookie 数据大小超过 2048 字节,旧的消息就会被删除。

class storage.fallback.FallbackStorage

这个类首先使用了 CookieStorage,然后又回到了使用 SessionStorage 来处理无法放入单个 cookie 的消息。它也需要 Django 的 contrib.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)

In your template, use something like:

{% 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 序列,因为否则消息存储将不会为下一个请求清除。

The context processor also provides a DEFAULT_MESSAGE_LEVELS variable which is a mapping of the message level names to their numeric value:

{% 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 只隐藏 MessageFailure,否则当消息框架被禁用,人们试图使用 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"

form 中清理出来的数据可以使用 %(field_name)s 语法进行字符串插值。对于模型表单,如果你需要访问保存的 object 中的字段,可以覆盖 get_success_message() 方法。

模型表单的示例 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(以及会话)的工作方式,当同一客户端发出多个请求,并行设置或获取消息时,任何使用 cookie 或会话的后端的行为都是未定义的。例如,如果客户端发起一个在一个窗口(或标签页)中创建消息的请求,然后在第一个窗口重定向之前发起另一个在另一个窗口中获取任何单元化消息的请求,则消息可能会出现在第二个窗口中,而不是在第一个窗口中。

简而言之,当涉及来自同一客户端的多个同步请求时,不能保证将消息传递到创建这些请求的同一个窗口,在某些情况下,也不能保证将消息传递到所有窗口。请注意,这在大多数应用程序中通常不是问题,在 HTML5 中,这将成为不是问题,因为在 HTML5 中,每个窗口/标签都有自己的浏览上下文。

配置

一些 配置 给你控制消息行为。

对于使用 cookie 的后端,cookie 的配置来自会话 cookie 配置:

测试中

New in Django 5.0.

This module offers a tailored test assertion method, for testing messages attached to an HttpResponse.

To benefit from this assertion, add MessagesTestMixin to the class hierarchy:

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


class MsgTestCase(MessagesTestMixin, TestCase):
    pass

Then, inherit from the MsgTestCase in your tests.

MessagesTestMixin.assertMessages(response, expected_messages, ordered=True)

Asserts that messages added to the response matches expected_messages.

expected_messages is a list of Message objects.

By default, the comparison is ordering dependent. You can disable this by setting the ordered argument to False.