Django 教程第 7 部分:会话框架

本教程扩展了我们的 LocalLibrary 网站,为主页添加了一个基于会话的访问计数器。这是一个相对简单的示例,但它确实展示了如何在自己的站点中使用会话框架为匿名用户提供持久行为。

预备知识 完成所有之前的教程主题,包括 Django 教程第 6 部分:通用列表和详情视图
目标 了解会话的使用方式。

概述

我们在之前的教程中创建的 LocalLibrary 网站允许用户浏览目录中的书籍和作者。虽然内容是从数据库动态生成的,但每个用户在使用该网站时都将基本上访问相同的页面和信息类型。

在一个“真实”的图书馆中,您可能希望根据用户之前对网站的使用、偏好等为他们提供定制化的体验。例如,您可以隐藏用户之前确认过的警告消息,以便下次他们访问网站时不再显示,或者存储并尊重他们的偏好(例如,他们希望每页显示多少搜索结果)。

会话框架允许您实现这种行为,从而可以按每个网站访问者存储和检索任意数据。

什么是会话?

Web 浏览器和服务器之间的所有通信都通过 HTTP 进行,而 HTTP 是**无状态**的。协议无状态意味着客户端和服务器之间的消息是完全独立的——没有“序列”或基于先前消息的行为的概念。因此,如果您希望拥有一个能够跟踪与客户端持续关系的网站,则需要自己实现。

会话是 Django(以及大多数互联网)用于跟踪站点和特定浏览器之间“状态”的机制。会话允许您为每个浏览器存储任意数据,并使这些数据在浏览器连接时可供站点使用。与会话关联的单个数据项随后通过一个“键”进行引用,该键用于存储和检索数据。

Django 使用包含特殊**会话 ID** 的 Cookie 来识别每个浏览器及其与站点关联的会话。实际的会话**数据**默认存储在站点数据库中(这比将数据存储在 Cookie 中更安全,因为 Cookie 更容易受到恶意用户的攻击)。您可以将 Django 配置为将会话数据存储在其他位置(缓存、文件、“安全”Cookie),但默认位置是一个良好且相对安全的选项。

启用会话

当我们创建骨架网站时(在教程 2 中),会话已自动启用。

配置设置在项目文件(**django-locallibrary-tutorial/locallibrary/settings.py**)的 `INSTALLED_APPS` 和 `MIDDLEWARE` 部分,如下所示:

python
INSTALLED_APPS = [
    # …
    'django.contrib.sessions',
    # …

MIDDLEWARE = [
    # …
    'django.contrib.sessions.middleware.SessionMiddleware',
    # …

使用会话

您可以从 `request` 参数(作为视图的第一个参数传入的 `HttpRequest`)在视图中访问 `session` 属性。此会话属性表示与当前用户的特定连接(或者更准确地说,与当前**浏览器**的连接,由浏览器为该站点存储的 cookie 中的会话 ID 标识)。

`session` 属性是一个类似字典的对象,您可以在视图中随意读写和修改。您可以执行所有正常的字典操作,包括清除所有数据、测试键是否存在、遍历数据等。不过,大多数情况下,您只会使用标准“字典”API 来获取和设置值。

下面的代码片段展示了如何使用键 `my_car` 获取、设置和删除与当前会话(浏览器)关联的一些数据。

**注意:** Django 的一大优点是您无需考虑将会话与视图中的当前请求关联起来的机制。如果我们在视图中使用下面的片段,我们就会知道 `my_car` 的信息仅与发送当前请求的浏览器相关联。

python
# Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present
my_car = request.session['my_car']

# Get a session value, setting a default if it is not present ('mini')
my_car = request.session.get('my_car', 'mini')

# Set a session value
request.session['my_car'] = 'mini'

# Delete a session value
del request.session['my_car']

API 还提供了许多其他方法,主要用于管理关联的会话 Cookie。例如,有测试客户端浏览器是否支持 Cookie 的方法,设置和检查 Cookie 过期日期的方法,以及从数据存储中清除过期会话的方法。您可以在如何使用会话(Django 文档)中找到完整的 API。

保存会话数据

默认情况下,Django 仅在会话被**修改**(赋值)或**删除**时才会保存到会话数据库并将会话 Cookie 发送到客户端。如果您使用其会话键更新某些数据,如上一节所示,则无需担心!例如:

python
# This is detected as an update to the session, so session data is saved.
request.session['my_car'] = 'mini'

如果您正在更新会话数据**内部**的一些信息,那么 Django 将不会识别您已更改会话并保存数据(例如,如果您更改 `my_car` 数据中的 `wheels` 数据,如下所示)。在这种情况下,您需要明确将会话标记为已修改。

python
# Session object not directly modified, only data within the session. Session changes not saved!
request.session['my_car']['wheels'] = 'alloy'

# Set session as modified to force data updates/cookie to be saved.
request.session.modified = True

**注意:** 您可以通过在项目设置(**django-locallibrary-tutorial/locallibrary/settings.py**)中添加 `SESSION_SAVE_EVERY_REQUEST = True` 来更改行为,使站点在每个请求时更新数据库/发送 cookie。

简单示例 — 获取访问次数

作为一个简单的实际示例,我们将更新我们的图书馆,以告知当前用户他们访问 LocalLibrary 主页的次数。

打开 **/django-locallibrary-tutorial/catalog/views.py**,并将包含 `num_visits` 的行添加到 `index()` 中(如下所示)。

python
def index(request):
    # …

    num_authors = Author.objects.count()  # The 'all()' is implied by default.

    # Number of visits to this view, as counted in the session variable.
    num_visits = request.session.get('num_visits', 0)
    num_visits += 1
    request.session['num_visits'] = num_visits

    context = {
        'num_books': num_books,
        'num_instances': num_instances,
        'num_instances_available': num_instances_available,
        'num_authors': num_authors,
        'num_visits': num_visits,
    }

    # Render the HTML template index.html with the data in the context variable.
    return render(request, 'index.html', context=context)

这里我们首先获取 `'num_visits'` 会话键的值,如果之前未设置,则将其设置为 0。每次收到请求时,我们都会增加该值并将其存储回会话中(以便下次用户访问页面时使用)。然后,`num_visits` 变量会通过我们的上下文变量传递给模板。

**注意:** 我们也可以在此处测试浏览器是否支持 Cookie(有关示例,请参阅如何使用会话),或者设计我们的 UI,使其无论是否支持 Cookie 都无关紧要。

将以下块底部所示的行添加到主 HTML 模板(**//django-locallibrary-tutorial/catalog/templates/index.html**)的“动态内容”部分的底部,以显示 `num_visits` 上下文变量。

django
<h2>Dynamic content</h2>

<p>The library has the following record counts:</p>
<ul>
  <li><strong>Books:</strong> {{ num_books }}</li>
  <li><strong>Copies:</strong> {{ num_instances }}</li>
  <li><strong>Copies available:</strong> {{ num_instances_available }}</li>
  <li><strong>Authors:</strong> {{ num_authors }}</li>
</ul>

<p>
  You have visited this page {{ num_visits }} time{{ num_visits|pluralize }}.
</p>

请注意,我们使用 Django 内置的模板标签 pluralize 在页面被访问多次时添加一个“s”。

保存更改并重新启动测试服务器。每次刷新页面时,数字都应该更新。

总结

您现在知道使用会话来改善与**匿名**用户的交互是多么容易。

在我们的下一篇文章中,我们将解释身份验证和授权(权限)框架,并向您展示如何支持用户帐户。

另见