# Django 5.2.8 documentation

# 条件视图处理

HTTP 客户端可以发送一些头部信息，用来告诉服务器它们已经看过的资源的副本情况。这通常在检索网页（使用 HTTP 的 `GET` 请求）时使用，以避免发送客户端已经检索过的内容数据。然而，这些相同的头部信息可以用于所有 HTTP 方法（`POST`，`PUT`，`DELETE` 等）。

针对每个从 Django 视图返回的页面（响应），它可能提供了两种HTTP headers：`ETag` header 和 `Last-Modified` header。这些 headers 是 HTTP 的可选项。他们可以在视图函数里设置，或者依赖 `ConditionalGetMiddleware` 中间件来设置 `ETag` header 。

When the client next requests the same resource, it might send along a header such as either If-Modified-Since or If-Unmodified-Since, containing the date of the last modification time it was sent, or either If-Match or If-None-Match, containing the last `ETag` it was sent. If the current version of the page matches the `ETag` sent by the client, or if the resource has not been modified, a 304 status code can be sent back, instead of a full response, telling the client that nothing has changed. Depending on the header, if the page has been modified or does not match the `ETag` sent by the client, a 412 status code (Precondition Failed) may be returned.

当你需要更多的控制，你可以使用针对每个视图的条件处理函数。

## 条件装饰器

有时（事实上，相当经常），你可以创建函数来快速计算资源的 ETag 值或最后修改时间，而不需要 执行构建完整视图所需的所有计算。Django 可以使用这些函数来提供视图处理的“提前退出”选项。告诉客户端内容自上次请求以来没有被修改，也许是这样。

这两个函数被当做参数传递到 `django.views.decorators.http.condition` 装饰器。这个装饰器使用两个函数（你只需要支持其中一个，如果你不能很快计算这两个数量）来判断 HTTP 请求的 headers 和这些资源是否匹配。如果它们没有匹配，会计算一份资源的副本，并调用视图。

条件装饰器如下：

    condition(etag_func=None, last_modified_func=None)

用于计算 ETag 和最后修改时间的两个函数将接收传入的 `request` 对象和与它们帮助包装的视图函数中的相同参数，以相同的顺序。传递给 `last_modified_func` 的函数应返回一个标准的日期时间值，指定资源上次修改的时间，如果资源不存在则返回 `None`。传递给 `etag` 装饰器的函数应返回表示资源的 ETag 的字符串，如果资源不存在则返回 `None`。

如果它们没有通过视图设置并且请求的方法是安全的(`GET` 和 `HEAD`)，那么装饰器就会在请求上设置 `ETag` 和 `Last-Modified` headers 。

用一个例子来解释如何有效地使用这个功能。假设你已经有了这些模型，代表一个博客系统：

    import datetime
    from django.db import models

    class Blog(models.Model): ...

    class Entry(models.Model):
        blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
        published = models.DateTimeField(default=datetime.datetime.now)
        ...

如果在首页正在显示最新的博客文章，只会在有新博客文章时改变，你可以很快地计算最后修改时间。你需要与该博客关联的每篇文章的最新发布时间。一种方法是这样做的：

    def latest_entry(request, blog_id):
        return Entry.objects.filter(blog=blog_id).latest("published").published

然后你可以使用这个函数预先为首页视图提供未变动页面的检测：

    from django.views.decorators.http import condition

    @condition(last_modified_func=latest_entry)
    def front_page(request, blog_id): ...

小心装饰器的顺序

当 `condition()` 返回一个条件响应时，任何位于它下面的装饰器都会被跳过，不会应用于响应。因此，任何需要应用于常规视图响应和条件响应的装饰器必须位于 `condition()` 之上。特别是，`vary_on_cookie()`、`vary_on_headers()` 和 `cache_control()` 应该放在前面，因为 RFC 9110 要求它们设置的标头在 304 响应中存在。

## 仅用于计算一个值的快捷方式

作为常用规则，如果你可以提供函数去同时计算 ETag 和最后的修改时间，你应该这样做。你不知道任何给定的 HTTP 客户端将向你发送哪一个headers，因此需要同时处理这两个headers。然而，有时候只有一个值易于计算，Django 提供的装饰器处理只有 ETag 或 只有 last-modified 的计算。

`django.views.decorators.http.etag` 和 `django.views.decorators.http.last_modified` 装饰器和 `condition` 装饰器一样传递相同的函数类型。它们的签名是这样的：

    etag(etag_func)
    last_modified(last_modified_func)

我们可以使用其中一个装饰器来编写更早一些的那个只使用last-modified函数的例子：

    @last_modified(latest_entry)
    def front_page(request, blog_id): ...

...或:

    def front_page(request, blog_id): ...

    front_page = last_modified(latest_entry)(front_page)

### 测试两个条件时使用 `condition`

如果你想测试两个先决条件，那么试着链接 `etag` 和 `last_modified` 装饰器可能看起来更好。然而，这会导致错误的行为。

    # Bad code. Don't do this!
    @etag(etag_func)
    @last_modified(last_modified_func)
    def my_view(request): ...

    # End of bad code.

第一个装饰器不知道关于第二个装饰器的任何信息，而且可能回答“这个响应没有被修改”，即使第二个装饰器确定不是那样。`condition` 装饰器同时使用两个回调函数来执行正确的动作。

## 将装饰器和其他 HTTP 方法一起使用

`condition` 装饰器不仅仅用于 `GET` 和 `HEAD` 请求（在这个解决方案里 `HEAD``请求和 ``GET` 类似）。它也可以被用于提供 `POST`, `PUT` 和 `DELETE` 请求的检查。在这些情况下，不会返回一个“未修改”的响应，但会告诉客户端它们尝试修改的资源在这期间已被修改。

例如，在客户端和服务端之间考虑下面的交换：

1. 用户请求 `/foo/`。

2. 服务端用一些带有 `"abcd1234"` 的 ETag 响应一些内容。

3. 客户端发送一个 HTTP `PUT` 请求到 `/foo/` 来更新一些资源。它也发送 `If-Match: "abcd1234"` header 来指定它准备更新的版本。

4. 服务端通过计算 ETag（与 `GET` 请求计算的方式相同，使用相同的函数）来检查资源是否被修改。如果资源被改变，它将返回 412 状态码，意思是 "先决条件失败" 。

5. 客户端在收到一个412响应后会发送一个 `GET` 请求到 `/foo/`，在更新它之前用来寻找内容更新的版本。

重要的是这个例子显示的是相同函数在所有情形下可以被用来计算ETag和最后一次修改。事实上，你应该使用相同的函数，这样相同的值会被实时返回。

具有不安全请求方法的验证 headers

`condition` 装饰器仅为安全的 HTTP 方法（即 `GET` 和 `HEAD`）设置验证器标头（`ETag` 和 `Last-Modified`）。如果您希望在其他情况下返回它们，请在视图中设置它们。请参阅 RFC 9110 Section 9.3.4 了解在响应使用 `PUT` 与 `POST` 进行的请求时设置验证器标头的区别。

## 对比中间件的条件处理

Django 通过 `django.middleware.http.ConditionalGetMiddleware` 提供了有条件的 `GET` 处理。虽然易于使用，但是中间件在高级用法上是有限制的：

- 它可被全局应用于项目的所有视图。

- 它不会阻止你生成响应，这样代价可能很昂贵。

- 它只适合HTTP 的 `GET` 请求。

你应该为你的特殊需求选择最合适的工具。如果你有方法可以很迅速地计算 ETags 和 修改时间，并且如果一些视图花了一些时间去生成内容，那么你应该在这个文档中考虑使用 `condition` 装饰器描述。如果所有事务都运行的非常快了，那么坚持使用中间件。如果视图没有变动，那么发送回客户端的网络流量将仍然会减少。