管理画面に動的な操作(並び替え、インライン編集、絞り込み)を入れたいが、
全面 SPA にはしたくない。このときに使っている構成です。
方針:ページはサーバー、部品だけクライアント
Django テンプレートでページを描く
└── 一部の <div> に Vue をマウントする
ルーティング・認証・権限はすべて Django 側に残します。
こうすると 認証まわりを二重に作らなくて済みます。
<div id="order-table" data-orders="{{ orders_json }}"></div>
<script type="module" src="{% static 'js/order-table.js' %}"></script>
初期データは data-* 属性で渡します。
初期表示のための API を作らずに済み、画面が一瞬空になることもありません。
JSON をテンプレートに埋めるときの注意
素朴に埋め込むと XSS になります。Django なら json_script を使います。
{{ orders|json_script:"orders-data" }}
const orders = JSON.parse(document.getElementById("orders-data").textContent);
これで </script> を含む文字列が来ても壊れません。
テンプレートに直接 {{ json }} と書くのは避けてください。
CSRF で必ず詰まる
非同期で POST する時点で、CSRF トークンが必要になります。
function csrfToken() {
return document.querySelector("[name=csrfmiddlewaretoken]").value;
}
await fetch("/admin/orders/reorder/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken(),
},
body: JSON.stringify({ order: ids }),
});
ヘッダー名は X-CSRFToken(大文字小文字に注意)です。
ここを間違えて 403 に悩む時間が、経験上いちばん長くなります。
サーバー側は「部分HTML」を返す選択肢もある
JSON を返して JavaScript で組み立てるより、
HTML の断片を返して差し替えるほうが早く終わるケースが多くあります。
def order_rows(request):
orders = filter_orders(request.GET)
return render(request, "admin/_order_rows.html", {"orders": orders})
テンプレートが1つで済み、表示ロジックがサーバー側に一本化されます。
管理画面のように SEO も初期表示速度も重要でない画面では、これで十分でした。
まとめ
- ページはサーバーで描き、動的な部品だけマウントする
- 初期データは
json_scriptで安全に渡す - 非同期 POST では
X-CSRFTokenヘッダーを忘れない - JSON より「HTML断片を返す」ほうが早く終わる画面は多い