# Django 5.0.9.dev20240806133106 ドキュメント

# 条件付きのビュー処理

HTTP クライアントは、さまざまな HTTPヘッダーを送信することで、受信したリソースについてサーバーに伝えることができます。この方法は、ウェブページを (`GET` メソッドを利用して) 取得する際に、すでにクライアントが取得しているデータを送信するのを回避するためによく使われます。しかし、同じヘッダーは、全ての HTTP メソッド (`POST`、`PUT`、`DELETE` など) で利用できます。

Django がビューから送り返す各ページ (レスポンス) に対して、Django は2種類の HTTP ヘッダ、`ETag` ヘッダと `Last-Modified` ヘッダを提供できます。これらのヘッダは HTTP レスポンス上ではオプションです。ヘッダはビュー関数で設定することも、`ConditionalGetMiddleware` ミドルウェアを使用して `ETag` ヘッダを設定してもらうこともできます。

クライアントが次に同じリソースをリクエストしたとき、If-Modified-Since や If-Unmodified-Since のような、最後に変更された時刻を含むヘッダ、あるいは、If-Match や If-None-Match のような、最後に送信された `ETag` を含むヘッダとともにリクエストを送信することがあります。もしページの現在のバージョンがクライアントから送信された `ETag` と一致するか、リソースが変更されていない場合、完全なレスポンスの代わりに、ステータスコード304を送り返すことで、クライアントに何も変更がないことを伝えられます。ヘッダによりますが、もしページが変更されていなくて、クライアントから送られてきた `ETag` とも一致しない場合には、ステータスコード412 (Precondition Failed) が返せます。

より細かい粒度のコントロールが必要な場合には、ビューごとの条件付き処理関数を使えます。

## `condition` デコレータ

時には（実際にはかなり頻繁に）、リソースの ETag 値や最終更新時間を迅速に計算する関数も作成できます。これは、完全なビューを構築するために必要なすべての計算を行う 必要がない 場合です。Django は、これらの関数を使用して、ビュー処理の「早期脱出」オプションを提供できます。たとえば、クライアントに対して、コンテンツが前回のリクエスト以降変更されていないことを伝えます。

これら 2 つの関数は `django.views.decorators.http.condition` デコレータのパラメータとして渡されます。このデコレータは、HTTP リクエストのヘッダがリソースのヘッダと一致するかどうかを調べるために、2 つの関数 (両方の量を簡単かつ迅速に計算できない場合は、1 つだけを渡す必要があります) を使用します。もし一致しなければ、リソースの新しいコピーを計算して通常のビューを呼び出します。

`condition` デコレータのシグネチャは次のようになります。

    condition(etag_func=None, last_modified_func=None)

ETag と最終更新時間を計算するための2つの関数は、受信した `request` オブジェクトと、それらがサポートするビュー関数と同じパラメータを、同じ順序で渡されます。 `last_modified_func` に渡された関数は、リソースが最後に変更された時間を指定する標準の datetime 値を返すべきです。または、リソースが存在しない場合は `None` を返すべきです。 `etag` デコレータに渡された関数は、リソースの ETag を表す文字列を返すべきです。または、それが存在しない場合は `None` を返すべきです。

デコレータは、それらがビューによってまだ設定されていない場合、そしてリクエストのメソッドが安全なもの（`GET` または `HEAD`）である場合に、レスポンスに `ETag` ヘッダと `Last-Modified` ヘッダを設定します。

この機能を有効に使う方法は、例を挙げて説明するのが一番でしょう。小さなブログシステムを表す次のようなモデルのペアがあるとします。

    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)
        ...

最新のブログエントリーを表示するフロントページが、新しいブログエントリーを追加したときだけ変更されるのであれば、最終更新時刻をとても速く計算できます。そのブログに関連するすべてのエントリーの最新の `published` 日付が必要です。これを行う一つの方法は次のようなものです。

    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 レスポンスにヘッダを設定することを要求しているため、最初に置くべきです。

## 1つの値を計算するだけのショートカット

一般的なルールとして、ETag と最終更新時刻の 両方 を計算する関数を提供できるのであれば、そうすべきです。どの HTTP クライアントがどのヘッダを送ってくるかはわからないので、両方を扱えるように準備しましょう。しかし、片方の値だけを計算するのが簡単な場合もあり、 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)

### 2つの条件をテストする場合に `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.

最初のデコレータは 2 番目のデコレータについて何も知らないので、 2 番目のデコレータがレスポンスは変更されていると判断した場合でも、変更されていないと答えるかもしれません。条件 `condition` デコレータは両方のコールバック関数を同時に使用して、正しい動作を決定します。

## その他の HTTP メソッドでデコレータを使用する

`condition` デコレータは `GET` と `HEAD` リクエスト以外にも有用です (`HEAD` リクエストはこの状況では `GET` と同じです)。 `POST`、`PUT`、`DELETE` リクエストのチェックにも使うことができます。このような状況では、"not modified" 応答を返すのではなく、変更しようとしているリソースがその間に変更されたことをクライアントに伝えることを考えます。

たとえば、つぎのようなクライアントとサーバを交換する例を考えてみてください。

1. クライアントが `/foo/` をリクエストします。
2. サーバーは何らかのコンテンツを `"abcd1234"` という ETag を付けて返します。
3. クライアントは HTTP `PUT` リクエストを `/foo/` に送信して、リソースを更新します。`If-Match: "abcd1234"` ヘッダも送信して、更新しようとしているバージョンを指定します。
4. サーバーは、`GET` リクエストと同じ方法で (同じ関数を使って) ETag を計算することで、リソースが変更されているかどうかをチェックします。もしリソースが すでに 変更されていた場合、「precondition failed」を意味するステータスコード412を返します。
5. クライアントは、412レスポンスを受信した後、実際に更新する前に、更新されたバージョンのコンテンツを取得するために、`GET` リクエストを `/foo/` に送信します。

この例が示している重要な点は、ETag と最終変更の値を計算するのに、すべての状況で同じ関数が使えるということです。実際、毎回同じ値が返されるように、同じ関数を使う 必要がある のです。

安全ではないリクエストメソッドを含むバリデーターヘッダ

`condition` デコレータは、安全な HTTP メソッド、つまり `GET` と `HEAD` に対してのみバリデータヘッダ (`ETag` と `Last-Modified`) を設定します。 それ以外の場合にこれらを返したい場合は、ビューで設定してください。 `PUT` と `POST` のリクエストに対するバリデーションヘッダの設定の違いについては RFC 9110#section-9.3.4 を参照してください。

## ミドルウェアの条件付き処理との比較

Django は、`django.middleware.http.ConditionalGetMiddleware` 経由で条件付き `GET` 処理を提供しています。多くの状況に適していますが、ミドルウェアは高度な利用に次のような限界があります。

- プロジェクト内のすべてのビューにグローバルに適用されてしまう。
- レスポンスを生成する手間が省けるわけではなく、コストがかかる場合がある。
- HTTP の `GET` リクエストに対してのみ適している。

ここでは、特定の問題に最も適したツールを選択するべきです。もし ETag と変更時刻を高速に計算する手段があり、一部のビューがコンテンツを生成するのに時間がかかるのであれば、このドキュメントで説明した `condition` デコレータを使用することを検討するべきです。もしすべてがすでにかなり高速に動作しているなら、引き続きミドルウェアを使用し続けてください。ビューが変更されていなければ、クライアントに送り返されるネットワークトラフィックは、引き続き削減されます。