## [[Django]]とHTMXで動的Web開発をシンプルに:FormとSerializerの活用 {#sec-b233da7ff7c9} 前回の記事では、**[[HTMX]]がデータをサーバーに送信する方法**について詳しく解説しました。 **前回の記事**:[DjangoとHTMXで動的Web開発をシンプルに (第4回): ペイロード送信方式](/ko/whitedec/2025/1/27/django-htmx-csrf-token-integration/) 従来のJavaScriptの`fetch()`がJSONペイロードを直接作成して送信する方法に近いとすれば、**HTMXはDOMから値を収集し、フォームデータのように送信する方法**に近いことを確認しましたね。 では、ここで自然と疑問が湧いてきます。 **「HTMXで送られてきたこのデータを、Djangoでは何を使って検証するのが最も自然なのだろうか?」** 最初に多くの方が**DRF Serializer**を思い浮かべるかもしれません。実際にSerializerは強力なバリデーションツールであり、JSON形式でなくても使用できます。しかし、いざHTMXと組み合わせてみると、どうも不自然に感じることもあります。 なぜでしょうか? 理由は単純です。 **HTMXの基本的な流れはHTMLフォームの世界に近く、Djangoにはその世界のために設計された`Form`がすでに存在するからです。** この記事では、**HTMXリクエストの処理において`Django Form`と`DRF Serializer`をそれぞれどのように活用できるか**、そして**どちらがより自然で実用的な選択肢なのか**を整理していきます。 ![HTMXリクエスト処理におけるFormとSerializerの役割比較](/media/whitedec/blog_img/06d08ad8294d4067af3a9001566ddf5a.webp) --- ## [[HTMX]]が送信するデータは結局「フォームデータ」に近い {#sec-8c911eee119a} 前回の記事で見たように、HTMXは基本的にHTML要素の値を収集してサーバーに送信します。`
`内の入力値を送ったり、`hx-include`で特定の要素を含めたり、`hx-vals`で追加の値を付加したりする、といった具合です。 つまり、HTMXの基本的な哲学は以下の点に集約されます。 * JavaScriptオブジェクトを直接組み立てない * HTML要素から値を集める * サーバーにリクエストを送信する * JSONよりもHTMLの断片応答と相性が良い この流れは、想像以上に非常に重要です。なぜなら、この構造は**典型的なDjango Formの処理フローとほぼ一致するから**です。 Django Formもまた、以下のような前提で設計されています。 * ユーザーがHTMLフォームに入力する * サーバーが`request.POST`を受け取る * Formがデータを検証する * エラーがあれば再レンダリングする * 問題がなければ保存し、結果を応答する こうなると、HTMXとDjango Formはまるでシンデレラとガラスの靴のようにぴったりと合うような気がしてきます。 したがって、このような結論を導き出しても良いのではないでしょうか。 **HTMXと最も自然に連携するDjangoの検証ツールは、DRF SerializerよりもDjango Formである。** --- ## なぜDjango Formの方が相性が良いのか {#sec-b1e7f36e5f83} DRF Serializerはもちろん優れたツールです。 しかし、本来の設計の文脈を考えると、Serializerは**データシリアライズとAPI入力検証**により適したツールです。 一方、Django Formは最初から以下の目的のために作られました。 * HTMLフォーム入力の処理 * サーバーサイドのバリデーション * エラーメッセージの表示 * 入力値の保持 * テンプレートの再レンダリング つまり、HTMXのように**「HTMLの一部を再取得して置き換える方式」**と組み合わせたときに、はるかに自然な形で機能します。 例えば、考えてみましょう。 ユーザーがコメント作成フォームを送信します。 * バリデーションに失敗したら? * 入力内容は保持されるべきです * どのフィールドに問題があるか表示する必要があります * エラーが付加されたフォームを部分的に再レンダリングする必要があります このようなUXは、Django Formが非常に得意とするところです。 もちろん、Serializerも`errors`を提供し、検証も可能です。しかし、次のステップである**「HTMLフォームのUX全体を再構成する作業」**は、Formの方がはるかにスムーズです。 そのため、HTMXとの相性だけを考えると、Formが基本的な選択肢であると考えるのが適切です。 --- ## 最も自然な組み合わせ:[[HTMX]] + Django Form {#sec-23fc963f6963} まず、最も基本的な例を見てみましょう。簡単なTODO登録フォームです。 ### Formの定義 {#sec-fba81a38a40a} ```python from django import forms class TodoForm(forms.Form): title = forms.CharField(max_length=100, label="제목") priority = forms.IntegerField(min_value=1, max_value=5, label="우선순위") ``` これで、ビューでは`request.POST`をFormにそのまま渡して検証すれば良いだけです。 ### Viewの処理 {#sec-568150151637} ```python from django.shortcuts import render from django.http import HttpResponse def todo_create(request): if request.method == "POST": form = TodoForm(request.POST) if form.is_valid(): title = form.cleaned_data["title"] priority = form.cleaned_data["priority"] # 保存ロジックを実行 # Todo.objects.create(title=title, priority=priority) return render(request, "todos/partials/todo_item.html", { "title": title, "priority": priority, }) return render(request, "todos/partials/todo_form.html", { "form": form, }, status=400) form = TodoForm() return render(request, "todos/partials/todo_form.html", { "form": form, }) ``` ここでのポイントは非常に明確です。 1. **HTMXが送信した値を`request.POST`で受け取る** 2. **Formが検証する** 3. **成功すればHTMLのパーシャルを返す** 4. **失敗すればエラーを含むFormを再レンダリングする** この流れはDjangoらしく、HTMXらしく、そして何よりもメンテナンスが容易です。 --- ## テンプレートでエラーを自然に再表示する {#sec-18af3291951b} Django Formが特に輝く部分はまさにここです。 検証失敗時、Formオブジェクトにはすでに以下の情報が含まれています。 * ユーザーが入力した値 * フィールドごとのエラーメッセージ * 非フィールドエラー * どのフィールドが無効であるかの状態 したがって、パーシャルテンプレートでは、これらをそのままレンダリングすれば良いのです。 ### `todo_form.html` {#sec-8a4853cf4018} ```html {% csrf_token %} {% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %}
{{ form.title }} {% if form.title.errors %}
{{ form.title.errors }}
{% endif %}
{{ form.priority }} {% if form.priority.errors %}
{{ form.priority.errors }}
{% endif %}
``` この例は非常に単純ですが、[[HTMX]]とDjango Formの相性の良さをよく示しています。 * 失敗時にはフォーム全体を再レンダリングでき * ユーザーが入力した値も保持され * エラーメッセージもフィールドの横に自然に表示されます 従来の`fetch()` + JSON + 手動DOM操作方式であれば、このようなフローを実装するためにJavaScriptをかなり多く書く必要があったでしょう。しかし、HTMXとDjango Formの組み合わせでは、サーバーサイドのコードとテンプレートだけで、はるかにシンプルに処理できます。 --- ## ではSerializerは不要なのか? {#sec-5f0d93fcd48a} **Serializerは不要だ**という言い方は言い過ぎで、このくらいで表現するのが良いのではないでしょうか。 **あえてSerializerをデフォルトで選択する必要はない**、といったところでしょうか? 実際、[[DRF]] Serializerは、以下のような状況では依然として十分に魅力的です。 * すでにプロジェクト全体でDRFを積極的に使用している場合 * 同じ検証ロジックをAPIとサーバーレンダリング画面で共有したい場合 * 入力検証ロジックが複雑で、それをSerializerにすでにうまくまとめてある場合 * 後で同じ機能をモバイルアプリや外部APIにも公開する計画がある場合 つまり、Serializerは**「HTMXで使えるか?」**の問題ではなく、 **「あえてここでSerializerを使うことが、全体的なアーキテクチャ上でメリットがあるか?」**の問題に近いでしょう。 --- ## Serializerも使用は可能 {#sec-bfbe9c61b12c} 例えば、すでに以下のようなSerializerがあると仮定しましょう。 ```python from rest_framework import serializers class TodoSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) priority = serializers.IntegerField(min_value=1, max_value=5) ``` この場合、HTMXリクエストでも十分に活用できます。 ```python from django.shortcuts import render def todo_create_with_serializer(request): if request.method == "POST": serializer = TodoSerializer(data=request.POST) if serializer.is_valid(): title = serializer.validated_data["title"] priority = serializer.validated_data["priority"] return render(request, "todos/partials/todo_item.html", { "title": title, "priority": priority, }) return render(request, "todos/partials/todo_form_serializer.html", { "errors": serializer.errors, "data": request.POST, }, status=400) ``` ご覧の通り、**技術的には全く問題ありません。** SerializerはJSONのみを受け取るツールではないからです。 しかし、ここで微妙な違いが明らかになります。 Formを使用する場合: * 入力値の保持 * フィールドごとのレンダリング * エラーバインディング * テンプレートとの連携 これらが自然に連携します。 一方、Serializerを使用する場合: * `serializer.errors`をテンプレートの構造に合わせて直接展開する必要があり * 既存の入力値も別途渡す必要があり * HTMLフォームの再レンダリングとの連携を開発者がより多く考慮する必要があります つまり、**使用は可能ですが、手作業が少し増える傾向があります。** まさにこの点が、[[HTMX]]と組み合わせて使う際にSerializerがやや不自然に感じられる理由かもしれません。 --- ## まとめ {#sec-37e5d418cdb7} 前回の記事では、[[HTMX]]がデータをどのように送信するかを見てきました。そして今回の記事では、そのデータをDjangoでどのように検証するのが最も自然であるかを整理しました。 * HTMXは基本的にフォームデータと相性が良いです * Django Formはまさにそのフローのために設計されたツールです * そのためHTMXとの相性だけを考えるとFormが最も自然です * DRF Serializerは使用可能ですが、より戦略的な選択肢に近いと言えます 個人的には、HTMXを適切に活用するには、**「AJAXをHTMLらしく扱う感覚」**だけでなく、**「Django Formとフォームタグを活用する感覚」**も同時に取り戻す必要があると考えています。 --- **関連記事** * [DjangoとHTMXで動的Web開発をシンプルに (第1回)](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification/) * [DjangoとHTMXで動的Web開発をシンプルに - Ajax (第2回)](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification-2/) * [DjangoとHTMXで動的Web開発をシンプルに (第3回): Django統合方法](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification-3/) * [DjangoとHTMXで動的Web開発をシンプルに (第4回): ペイロードはどう扱う?](/ko/whitedec/2025/1/27/django-htmx-advanced-features/)