開発・技術選定

Django と Vue を組み合わせて管理画面を作る — 全面SPAにしない構成

管理画面に動的な操作(並び替え、インライン編集、絞り込み)を入れたいが、
全面 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断片を返す」ほうが早く終わる画面は多い