◀ 9.

djangoにおけるHTTPステータスコードの返し方

Djangoでは HttpResponse の status に整数を指定してHTTPステータスコードを返します。JSON APIの応答と、Http404を投げてHTMLエラーハンドラーへ渡す処理は別です。

Djangoの明示的レスポンス、Http404とDEBUG、本文を送らない応答の違い

旧例の401は認証処理ではありません。下の DownloadView は状態値を指定する最小例です。認証・権限確認もダウンロード処理も実装していません。401では認証方式に対応した WWW-Authenticate ヘッダーも必要です。ステータスだけでアクセスを保護したことにはなりません。
この記事の要点
  • Django でHTTP ステータスコードを返す方法
  • 基本: HttpResponse(content, status=404)
  • ショートカット: HttpResponseNotFound() / HttpResponseBadRequest() 等
  • DRF を使うなら Response(data, status=status.HTTP_404_NOT_FOUND)

 

from django.http import HttpResponse

from django.views.generic import View

class DownloadView(View):

    def get(self, request, *args, **kwargs):

            response = HttpResponse(status=401)

            return response

HttpResponseのstatusにステータスコードを渡してreturnすればOK

 

404を返すこととHttp404を投げることの違い

return HttpResponse("Missing", status=404) や HttpResponseNotFound("Missing") は、そのレスポンスを直接返します。handler404 は呼ばれません。raise Http404 は例外経路へ渡し、DEBUG=False ならルートURLconfの handler404、未指定なら既定の404ビューが本文を作ります。URLに一致するパターンがない場合も404の処理へ進みます。

DEBUG=True では404用の技術情報画面になるため、独自404画面の確認は DEBUG=False と適切な ALLOWED_HOSTS で行います。返すだけの404や明示したJSONは、この切替で自動的に別形式になりません。例外の詳細・入力値をそのまま本文へ出さないでください。

JSON APIは本文形式も明示する

JsonResponse({"error": "not_found"}, status=404) は application/json の本文を返します。status=404 だけではJSONになりません。また通常のDjangoの404ハンドラーは Accept: application/json を付けただけでJSONへ切り替わるわけではありません。APIのエラー形式はそのAPI側で統一し、HTML用ハンドラーと分けて設計します。

旧表のDRF例は、別途 Django REST framework を導入した APIView / @api_view 等の処理内で使うものです。通常のDjangoビューに Response だけを移す例ではありません。下の検証例はDjango標準の JsonResponse だけを使い、DRFのレンダラー・認証は検証していません。

204・304・HEADでは本文を送らない

応答使い方と境界
204 No ContentHttpResponse(status=204) のように本文なしで返す。JSONの成功データを載せたいなら、API仕様に合う200等を選ぶ。
304 Not Modified条件付きGET/HEADの条件判定後に使う。HttpResponseNotModified は本文を受け付けない。単に本文がない成功応答の代わりには使わない。
HEADGETに相当するヘッダーを返すが本文は送らない。Viewの既定ではget実装がheadにも利用される。処理が実行されない・副作用が起きないという意味ではない。

HttpResponse("text", status=204) というオブジェクト自体には本文を保持できてしまいます。Django Client は204・304・HEADの本文を除去してからテストへ返します。オブジェクトの content が空であることと送信境界を混同せず、最初から本文を付けない実装にします。実サーバー・プロキシでの送信挙動をClientだけで検証したことにはなりません。下の304は固定ヘッダーの検体で、条件判定やキャッシュ制御の完成実装ではありません。

405とAllowヘッダー

HttpResponseNotAllowed(["GET", "HEAD"]) は405と Allow: GET, HEAD を返します。単なる HttpResponse(status=405) では許可メソッド一覧を自動生成しません。クラスベースの View では、未実装メソッドに405を返し、getがあれば通常HEADも扱い、既定のOPTIONSも Allow に含まれます。許可一覧は実際に提供するメソッドと合わせます。CSRF検査が先に拒否すれば、POSTで405ではなく403になる場合もあります。

認証・権限・CSRFを混同しない

PermissionDenied は通常403の例外処理へ進みますが、401/403を返すだけでは利用者の認証や権限判定は実施されません。Djangoの login_required は未ログインなら通常ログイン画面へリダイレクトします。HTMLログインとAPIの認証エラー契約を分けてください。CSRF失敗は既定で403ですが、PermissionDenied の handler403 ではなく CSRF_FAILURE_VIEW で扱います。JSON応答を選んでもCSRF・認証・入力検証が不要になるわけではありません。

独自ハンドラーの最小例

ルートURLconfで handler400 / handler403 / handler404 / handler500 を指定します。400・403・404の関数は (request, exception)、500は (request) を受け取ります。エラー画面の本文を作っても status を省くと200になり得るので、各応答に正しい状態を明示します。DEBUG=True では400・404・500の独自ハンドラーは技術情報画面に置き換わりますが、403まで同じ切替と考えないでください。

次は単独のURLconfとして読み込める例です。各レスポンスは固定値で、DB・外部通信・実資格情報は使いません。認証や情報漏えい防止を保証する構成ではなく、実アプリのエラー処理・認可・ログの安全性は別途確認が必要です。

"""Fixed responses only; no database, credentials, or external services."""

from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.http import (
    Http404, HttpResponse, HttpResponseNotAllowed, HttpResponseNotModified,
    JsonResponse,
)
from django.urls import path
from django.views.generic import View


def direct_404(request):
    return HttpResponse("<p>Missing</p>", status=404)


def raised_404(request):
    raise Http404("Synthetic missing item")


def api_missing(request):
    return JsonResponse({"error": "not_found"}, status=404)


def no_content(request):
    return HttpResponse(status=204)


def not_modified(request):
    # Use only after evaluating a real conditional request in an application.
    return HttpResponseNotModified(headers={"ETag": '"demo-v1"'})


def method_limited(request):
    if request.method not in {"GET", "HEAD"}:
        return HttpResponseNotAllowed(["GET", "HEAD"])
    return HttpResponse("Readable")


class ReadView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse("Readable")


def forbidden(request):
    raise PermissionDenied


def bad_request(request):
    raise SuspiciousOperation("Synthetic invalid request")


def server_error(request):
    raise RuntimeError("Synthetic failure")


def csrf_probe(request):
    return HttpResponse("No write performed")


def error400(request, exception):
    return HttpResponse("<p>Bad request</p>", status=400)


def error403(request, exception):
    return HttpResponse("<p>Forbidden</p>", status=403)


def error404(request, exception):
    return HttpResponse("<p>Custom missing</p>", status=404)


def error500(request):
    return HttpResponse("<p>Server error</p>", status=500)


# These assignments belong in the project's ROOT URLconf, not an included URLconf.
handler400 = error400
handler403 = error403
handler404 = error404
handler500 = error500

urlpatterns = [
    path("direct/", direct_404),
    path("raised/", raised_404),
    path("api/", api_missing),
    path("empty/", no_content),
    path("cached/", not_modified),
    path("method/", method_limited),
    path("class/", ReadView.as_view()),
    path("forbidden/", forbidden),
    path("bad/", bad_request),
    path("failure/", server_error),
    path("csrf/", csrf_probe),
]

確認環境と公式資料

2026年10月26日、Python 3.12.10 / Django 5.2.18 の独立した合成settingsで、Django Clientによる状態・本文・ヘッダーと DEBUG の差を検証しています。実ネットワーク・DB・本番認証・配備環境のテストではありません。エラーのログ設計はログ出力方法、実環境でのみ起きる問題は本番環境へのデプロイも参照してください。

子ページ

子ページはありません

同階層のページ
  1. クラスベースビューの作り方とviewの分割
  2. 関数ベースビューの作り方とviewの分割
  3. URLディスパッチャー(ルーティング処理)
  4. GETとPOSTパラメータ受け取り
  5. クラスベースビューでGET/POSTリクエストの受け取り方
  6. クラスベースビューでテンプレートに値を渡す方法
  7. ビューでリダイレクト
  8. cookieの値の設定と取得
  9. HTTPステータスコードの返し方

最近更新/作成されたページ