使用 Django 和 HTMX 簡化動態網頁開發:Form 與 Serializer 的應用
在上一篇文章中,我們探討了 HTMX 將資料傳輸到伺服器的方式。
回顧前文:使用 Django 和 HTMX 簡化動態網頁開發 (第四部分):Payload 傳輸方式
我們確認了,如果說傳統 JavaScript 的 fetch() 更接近直接構建 JSON payload 並發送的方式,那麼 HTMX 則更接近從 DOM 收集值並以 form-data 形式發送的方式。
那麼,一個自然而然的問題就浮現了:
「針對 HTMX 傳入的這些資料,在 Django 中使用什麼來驗證會最自然呢?」
起初,許多人可能會想到 DRF Serializer。確實,Serializer 是一個強大的驗證工具,即使不是 JSON 資料也能使用。但實際將其與 HTMX 搭配使用時,有時會感覺有些牽強。
這是為什麼呢?
原因很簡單。
HTMX 的基本流程更貼近 HTML form 的世界,而 Django 中恰好已經存在專為該世界設計的 Form。
在本文中,我們將整理 在處理 HTMX 請求時,Django Form 和 DRF Serializer 各自可以如何應用,以及 哪種方式是更自然且實用的選擇。

HTMX 傳送的資料本質上更接近「表單資料」
正如上一篇所探討的,HTMX 基本上會收集 HTML 元素的數值並傳送至伺服器。例如,傳送 <form> 內的輸入值、透過 hx-include 包含特定元素,或使用 hx-vals 附加額外值。
換言之,HTMX 的基本理念更接近以下幾點:
- 不直接組裝 JavaScript 物件
- 從 HTML 元素中收集值
- 向伺服器發送請求
- 與 HTML 片段回應比 JSON 更契合
這個流程比想像中重要。因為這種結構與 典型的 Django Form 處理流程幾乎完全一致。
Django Form 的設計也基於以下前提:
- 使用者在 HTML 表單中輸入
- 伺服器接收
request.POST - Form 驗證資料
- 若有錯誤則重新渲染
- 若無問題則儲存並回應結果
至此,HTMX 和 Django Form 似乎就像灰姑娘與玻璃鞋般完美契合。
因此,我們或許可以得出這樣的結論:
與 HTMX 最自然契合的 Django 驗證工具是 Django Form,而非 DRF Serializer。
為何 Django Form 更為契合?
DRF Serializer 無疑是個出色的工具。但若考量其原始設計脈絡,Serializer 更傾向於 資料序列化和 API 輸入驗證。
相對地,Django Form 從一開始就是為以下目的而設計:
- 處理 HTML 表單輸入
- 伺服器端驗證
- 顯示錯誤訊息
- 保留輸入值
- 重新渲染模板
換句話說,當它與 HTMX 這種 「接收部分 HTML 並替換」 的方式結合時,會顯得更加自然。
舉例來說:
使用者提交評論表單。
- 如果驗證失敗怎麼辦?
- 輸入的內容應該被保留
- 應該顯示哪些欄位有問題
- 應該重新部分渲染帶有錯誤的表單
Django Form 在處理這類使用者體驗方面表現出色。
當然,Serializer 也提供 errors 並能進行驗證。但接下來的步驟,即 「重新構建整個 HTML 表單使用者體驗」,Form 處理起來會流暢得多。
因此,單就與 HTMX 的契合度而言,將 Form 視為預設選擇是正確的。
最自然的組合:HTMX + Django Form
首先,我們來看一個最基本的範例,一個簡單的待辦事項註冊表單。
Form 定義
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="우선순위")
現在,在 View 中,只需將 request.POST 直接傳入 Form 進行驗證即可。
View 處理
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,
})
這裡的重點非常明確:
- 接收 HTMX 傳送的值作為
request.POST - Form 進行驗證
- 若成功則返回 HTML partial
- 若失敗則重新渲染包含錯誤的 Form
這個流程既符合 Django 風格,也符合 HTMX 風格,最重要的是,它便於維護。
在模板中自然地重新顯示錯誤
Django Form 特別出色的地方就在於此。當驗證失敗時,Form 物件中已包含以下資訊:
- 使用者輸入的值
- 每個欄位的錯誤訊息
- 非欄位錯誤 (non-field errors)
- 哪些欄位無效的狀態
因此,在 partial 模板中可以直接渲染這些資訊。
todo_form.html
<form hx-post="{% url 'todo_create' %}" hx-target="#todo-form-wrap" hx-swap="outerHTML">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="error-box">
{{ form.non_field_errors }}
</div>
{% endif %}
<div>
<label for="{{ form.title.id_for_label }}">제목</label>
{{ form.title }}
{% if form.title.errors %}
<div class="field-error">{{ form.title.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.priority.id_for_label }}">우선순위</label>
{{ form.priority }}
{% if form.priority.errors %}
<div class="field-error">{{ form.priority.errors }}</div>
{% endif %}
</div>
<button type="submit">등록</button>
</form>
這個範例雖然非常簡單,卻很好地展示了 HTMX 和 Django Form 的契合度。
- 失敗時可以重新渲染整個表單
- 使用者輸入的值也能被保留
- 錯誤訊息會自然地顯示在欄位旁
如果是傳統的 fetch() + JSON + 手動 DOM 操作方式,為了實現這種流程可能需要編寫相當多的 JavaScript。然而,透過 HTMX 和 Django Form 的組合,僅需伺服器端程式碼和模板即可大幅簡化處理。
那麼,Serializer 是不是就不需要了?
說 「不需要 Serializer」 可能有些過頭了,或許這樣表達會更好: 「不一定非得將 Serializer 作為預設選擇」,您覺得如何?
實際上,在以下情況中,DRF Serializer 仍然具有十足的吸引力:
- 專案中已廣泛積極使用 DRF 的情況
- 希望在 API 和伺服器渲染畫面中重用相同的驗證邏輯
- 輸入驗證邏輯複雜,且已在 Serializer 中良好組織的情況
- 未來計劃將相同功能暴露給行動應用程式或外部 API 的情況
換言之,Serializer 的問題不在於 「HTMX 能不能用它?」, 而是更接近 「在這裡使用 Serializer,對整體架構來說是否有益?」。
Serializer 也能使用
例如,假設您已經有以下的 Serializer:
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 請求。
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 form 重新渲染的連接
也就是說,雖然可以使用,但會增加一些手動操作。
正是因為這一點,當與 HTMX 搭配使用時,Serializer 可能會顯得有些牽強。
總結
在上一篇中,我們探討了 HTMX 如何傳送資料。而在本篇中,我們整理了如何在 Django 中最自然地驗證這些資料。
- HTMX 基本上與 form-data 配合良好
- Django Form 正是為此流程設計的工具
- 因此,單就與 HTMX 的契合度而言,Form 是最自然的選擇
- DRF Serializer 雖然可以使用,但更偏向於策略性的選擇
我個人認為,要充分利用 HTMX,不僅需要培養 「以 HTML 方式處理 AJAX 的感覺」,還需要重新掌握 「運用 Django Form 和 form tags 的技巧」。
相關閱讀