この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:3
ページ更新者:T
更新日時:2026-06-11 07:07:02

タイトル: ajaxの使用例(POST編)
SEOタイトル: Django + Ajax POST 通信完全ガイド(CSRF Token / JsonResponse / fetch / DRF)

この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li>Django の Ajax POST は <strong>CSRF Token</strong> をヘッダ <code>X-CSRFToken</code> に必須で同送</li><li>Token 取得: テンプレート <strong><code>{% csrf_token %}</code></strong> または Cookie <code>csrftoken</code></li><li>views.py で <code>request.method == "POST"</code> 判定、<strong><code>request.POST.get()</code></strong> / <code>json.loads(request.body)</code></li><li>レスポンスは <strong><code>JsonResponse({...})</code></strong> または DRF の <code>@api_view + Response</code></li><li>本格運用は <strong>Django REST Framework</strong> + <code>SessionAuthentication</code> / <code>TokenAuthentication</code></li></ul></td></tr></table>
<h2>Django + Ajax POST の全体像</h2>

<p>Django で Ajax から POST する際、最大の関門は <strong>CSRF (Cross-Site Request Forgery) 保護</strong>です。Django は POST / PUT / PATCH / DELETE に対し<strong>毎リクエスト CSRF Token</strong>を要求します。Token を付け忘れると HTTP 403 Forbidden で弾かれます。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">全体フロー

[ Browser ]                            [ Django ]
   │                                       │
   │  GET /form/  (HTML + csrftoken Cookie) │
   │ ◀────────────────────────────────────│
   │                                       │
   │  POST /api/save/                       │
   │   X-CSRFToken: <token>                 │
   │   Content-Type: application/json       │
   │   { "name": "Taro" }                   │
   │ ────────────────────────────────────▶ │
   │                                       │ ← CSRF middleware が検証
   │                                       │ ← views.py で処理
   │  200 { "ok": true, "id": 42 }          │
   │ ◀────────────────────────────────────│</code></pre>
<h2>手順1: CSRF Token をテンプレートに埋め込む</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# templates/form.html #}
<form id="myForm">
    {% csrf_token %}
    <input name="name">
    <input name="email">
    <button type="submit">送信</button>
</form>

<script>
// {% csrf_token %} は次のような hidden input を生成
// <input type="hidden" name="csrfmiddlewaretoken" value="abc123...">
</script></code></pre>
<h2>手順2: jQuery $.ajax での POST</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// CSRF Token を Cookie から取得
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(&apos;;&apos;).shift();
}
const csrftoken = getCookie(&apos;csrftoken&apos;);

// $.ajaxSetup でグローバルに設定(推奨)
$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) {
            xhr.setRequestHeader(&apos;X-CSRFToken&apos;, csrftoken);
        }
    }
});

// POST 実行
$.ajax({
    method: &apos;POST&apos;,
    url: &apos;/api/save/&apos;,
    contentType: &apos;application/json&apos;,
    data: JSON.stringify({
        name: $(&apos;input[name=name]&apos;).val(),
        email: $(&apos;input[name=email]&apos;).val(),
    }),
    success: function (resp) {
        console.log(&apos;OK&apos;, resp);
    },
    error: function (xhr) {
        console.error(&apos;NG&apos;, xhr.status, xhr.responseText);
    }
});</code></pre>
<h2>手順3: fetch API(jQuery 不使用)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// CSRF Token 取得
function getCookie(name) {
    const m = document.cookie.match(&apos;(^|;)\\s*&apos; + name + &apos;\\s*=\\s*([^;]+)&apos;);
    return m ? decodeURIComponent(m.pop()) : &apos;&apos;;
}

async function postJSON(url, data) {
    const res = await fetch(url, {
        method: &apos;POST&apos;,
        credentials: &apos;same-origin&apos;,
        headers: {
            &apos;Content-Type&apos;: &apos;application/json&apos;,
            &apos;X-CSRFToken&apos;: getCookie(&apos;csrftoken&apos;),
            &apos;X-Requested-With&apos;: &apos;XMLHttpRequest&apos;,
        },
        body: JSON.stringify(data),
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
}

// 利用
document.getElementById(&apos;myForm&apos;).addEventListener(&apos;submit&apos;, async (e) => {
    e.preventDefault();
    try {
        const json = await postJSON(&apos;/api/save/&apos;, {
            name: e.target.name.value,
            email: e.target.email.value,
        });
        console.log(&apos;OK&apos;, json);
    } catch (err) {
        console.error(err);
    }
});</code></pre>
<h2>手順4: Django views.py で受ける(FBV)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># views.py
import json
from django.http import JsonResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST

@require_POST
def api_save(request):
    # JSON ボディ
    try:
        payload = json.loads(request.body.decode(&apos;utf-8&apos;))
    except json.JSONDecodeError:
        return HttpResponseBadRequest(&apos;invalid JSON&apos;)

    name  = (payload.get(&apos;name&apos;) or &apos;&apos;).strip()
    email = (payload.get(&apos;email&apos;) or &apos;&apos;).strip()

    if not name or not email:
        return JsonResponse({&apos;ok&apos;: False, &apos;errors&apos;: {&apos;name&apos;: &apos;必須&apos;, &apos;email&apos;: &apos;必須&apos;}}, status=400)

    # DB 保存
    from .models import Contact
    obj = Contact.objects.create(name=name, email=email)
    return JsonResponse({&apos;ok&apos;: True, &apos;id&apos;: obj.id})


# urls.py
from django.urls import path
from . import views
urlpatterns = [
    path(&apos;api/save/&apos;, views.api_save, name=&apos;api_save&apos;),
]</code></pre>
<h3>FormData(multipart)で受ける場合</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python">@require_POST
def api_upload(request):
    name = request.POST.get(&apos;name&apos;, &apos;&apos;).strip()
    file = request.FILES.get(&apos;avatar&apos;)
    if not file:
        return JsonResponse({&apos;ok&apos;: False, &apos;msg&apos;: &apos;file required&apos;}, status=400)

    # ファイル保存
    from django.core.files.storage import default_storage
    path = default_storage.save(f&apos;avatars/{file.name}&apos;, file)
    return JsonResponse({&apos;ok&apos;: True, &apos;path&apos;: path})</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// FormData は Content-Type を自動設定(境界文字列付き)
const fd = new FormData();
fd.append(&apos;name&apos;, &apos;Taro&apos;);
fd.append(&apos;avatar&apos;, fileInput.files[0]);

await fetch(&apos;/api/upload/&apos;, {
    method: &apos;POST&apos;,
    body: fd,
    headers: { &apos;X-CSRFToken&apos;: getCookie(&apos;csrftoken&apos;) },
});</code></pre>
<h2>CSRF Token が無いとどうなるか</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text"># レスポンス
HTTP/1.1 403 Forbidden

<h1>Forbidden (403)</h1>
<p>CSRF verification failed. Request aborted.</p>

# settings.py
CSRF_COOKIE_HTTPONLY = False   # JS から読みたいなら False(既定)
CSRF_COOKIE_SAMESITE = &apos;Lax&apos;   # クロスサイトでは &apos;None&apos; + Secure

# 例外的に CSRF をスキップしたい view(外部 Webhook 等)
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def webhook(request):
    ...</code></pre>
<h2>CBV(Class-Based View)で書く</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python">from django.views import View
from django.http import JsonResponse
import json

class SaveAPI(View):
    def post(self, request):
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({&apos;ok&apos;: False, &apos;msg&apos;: &apos;invalid JSON&apos;}, status=400)
        # ...
        return JsonResponse({&apos;ok&apos;: True})

# urls.py
path(&apos;api/save/&apos;, SaveAPI.as_view()),</code></pre>
<h2>Django REST Framework との比較</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># api/views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

@api_view([&apos;POST&apos;])
@permission_classes([IsAuthenticated])
def save(request):
    name  = request.data.get(&apos;name&apos;)
    email = request.data.get(&apos;email&apos;)
    return Response({&apos;ok&apos;: True, &apos;name&apos;: name})


# ModelSerializer + ViewSet が本命
from rest_framework import viewsets, serializers
from .models import Contact

class ContactSerializer(serializers.ModelSerializer):
    class Meta:
        model = Contact
        fields = &apos;__all__&apos;

class ContactViewSet(viewsets.ModelViewSet):
    queryset = Contact.objects.all()
    serializer_class = ContactSerializer

# urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(&apos;contacts&apos;, ContactViewSet)
urlpatterns += router.urls</code></pre>
<p>DRF の場合、デフォルトの <code>SessionAuthentication</code> は CSRF を要求します(ブラウザから叩く時)。<code>TokenAuthentication</code> / <code>JWT</code> は CSRF 不要。</p>

<h2>Laravel Sanctum との比較</h2>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e8f5e9"><th>項目</th><th>Django</th><th>Laravel Sanctum</th></tr></thead>
<tbody>
<tr><td>CSRF</td><td><code>X-CSRFToken</code> 必須</td><td><code>X-XSRF-TOKEN</code> 必須(SPA)</td></tr>
<tr><td>セッション認証</td><td>標準</td><td><code>Sanctum::actingAs</code></td></tr>
<tr><td>API Token</td><td>DRF Token / JWT 追加</td><td>標準(Personal Access Token)</td></tr>
<tr><td>SPA テンプレート</td><td>Templates + 部分 Ajax</td><td>Vue/React + Inertia</td></tr>
</tbody>
</table>

<h2>セキュリティ設定</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># settings.py
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = &apos;DENY&apos;
SECURE_HSTS_SECONDS = 60 * 60 * 24 * 365
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# 本番のみ
CSRF_TRUSTED_ORIGINS = [&apos;https://app.example.com&apos;]
CORS_ALLOWED_ORIGINS = [&apos;https://app.example.com&apos;]   # django-cors-headers</code></pre>
<h2>よくあるトラブル</h2>

<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#fff3e0"><th>症状</th><th>原因</th><th>対処</th></tr></thead>
<tbody>
<tr><td>403 Forbidden CSRF failed</td><td>X-CSRFToken 未送信 / Token 不一致</td><td>Cookie から取得 → header にセット</td></tr>
<tr><td>POST が GET 扱いされる</td><td><code>method: 'post'</code> なのに body 未送信</td><td><code>Content-Type</code> 設定 + <code>body</code> 確認</td></tr>
<tr><td>CORS エラー</td><td>クロスオリジン</td><td>django-cors-headers 導入</td></tr>
<tr><td>JSON が空</td><td><code>request.POST</code> で取得</td><td><code>json.loads(request.body)</code> を使う</td></tr>
<tr><td>500 internal error</td><td>views で例外</td><td>DEBUG=True で原因確認</td></tr>
</tbody>
</table>

<h2>FAQ</h2>

<p><strong>Q: SPA から Ajax POST する時の CSRF はどうする?</strong><br />
A: ログイン後に Cookie <code>csrftoken</code> が払い出されます。JS で読み <code>X-CSRFToken</code> ヘッダに付ければ通ります。CORS では <code>credentials: 'include'</code>。</p>

<p><strong>Q: <code>@csrf_exempt</code> はどんな時に使う?</strong><br />
A: 外部からの Webhook 受信(Stripe / GitHub 等)で署名検証を別に行う場合。<strong>通常の自社フロントには使わない</strong>でください。</p>

<p><strong>Q: ファイル + JSON を同時に送りたい</strong><br />
A: <code>FormData</code> でフィールドを追加し、JSON はテキスト化して <code>fd.append('meta', JSON.stringify(data))</code>。Django 側で <code>json.loads(request.POST['meta'])</code>。</p>