## 使用 [[Django]] 和 HTMX 簡化動態網頁開發:Form 與 Serializer 的應用 {#sec-b233da7ff7c9} 在上一篇文章中,我們探討了 **[[HTMX]] 將資料傳輸到伺服器的方式**。 **回顧前文**:[使用 Django 和 HTMX 簡化動態網頁開發 (第四部分):Payload 傳輸方式](/ko/whitedec/2025/1/27/django-htmx-csrf-token-integration/) 我們確認了,如果說傳統 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 請求處理中 Form 與 Serializer 的角色比較](/media/whitedec/blog_img/06d08ad8294d4067af3a9001566ddf5a.webp) --- ## [[HTMX]] 傳送的資料本質上更接近「表單資料」 {#sec-8c911eee119a} 正如上一篇所探討的,HTMX 基本上會收集 HTML 元素的數值並傳送至伺服器。例如,傳送 `
` 內的輸入值、透過 `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 更為契合? {#sec-b1e7f36e5f83} DRF Serializer 無疑是個出色的工具。但若考量其原始設計脈絡,Serializer 更傾向於 **資料序列化和 API 輸入驗證**。 相對地,Django Form 從一開始就是為以下目的而設計: * 處理 HTML 表單輸入 * 伺服器端驗證 * 顯示錯誤訊息 * 保留輸入值 * 重新渲染模板 換句話說,當它與 HTMX 這種 **「接收部分 HTML 並替換」** 的方式結合時,會顯得更加自然。 舉例來說: 使用者提交評論表單。 * 如果驗證失敗怎麼辦? * 輸入的內容應該被保留 * 應該顯示哪些欄位有問題 * 應該重新部分渲染帶有錯誤的表單 Django Form 在處理這類使用者體驗方面表現出色。 當然,Serializer 也提供 `errors` 並能進行驗證。但接下來的步驟,即 **「重新構建整個 HTML 表單使用者體驗」**,Form 處理起來會流暢得多。 因此,單就與 HTMX 的契合度而言,將 Form 視為預設選擇是正確的。 --- ## 最自然的組合:[[HTMX]] + Django Form {#sec-23fc963f6963} 首先,我們來看一個最基本的範例,一個簡單的待辦事項註冊表單。 ### 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="우선순위") ``` 現在,在 View 中,只需將 `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 partial** 4. **若失敗則重新渲染包含錯誤的 Form** 這個流程既符合 Django 風格,也符合 HTMX 風格,最重要的是,它便於維護。 --- ## 在模板中自然地重新顯示錯誤 {#sec-18af3291951b} Django Form 特別出色的地方就在於此。當驗證失敗時,Form 物件中已包含以下資訊: * 使用者輸入的值 * 每個欄位的錯誤訊息 * 非欄位錯誤 (non-field errors) * 哪些欄位無效的狀態 因此,在 partial 模板中可以直接渲染這些資訊。 ### `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 form 重新渲染的連接 也就是說,**雖然可以使用,但會增加一些手動操作**。 正是因為這一點,當與 [[HTMX]] 搭配使用時,Serializer 可能會顯得有些牽強。 --- ## 總結 {#sec-37e5d418cdb7} 在上一篇中,我們探討了 [[HTMX]] 如何傳送資料。而在本篇中,我們整理了如何在 Django 中最自然地驗證這些資料。 * HTMX 基本上與 form-data 配合良好 * Django Form 正是為此流程設計的工具 * 因此,單就與 HTMX 的契合度而言,Form 是最自然的選擇 * DRF Serializer 雖然可以使用,但更偏向於策略性的選擇 我個人認為,要充分利用 HTMX,不僅需要培養 **「以 HTML 方式處理 AJAX 的感覺」**,還需要重新掌握 **「運用 Django Form 和 form tags 的技巧」**。 --- **相關閱讀** * [使用 Django 和 HTMX 簡化動態網頁開發 (第一部分)](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification/) * [使用 Django 和 HTMX 簡化動態網頁開發 - Ajax (第二部分)](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification-2/) * [使用 Django 和 HTMX 簡化動態網頁開發 (第三部分):Django 整合方法](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification-3/) * [使用 Django 和 HTMX 簡化動態網頁開發 (第四部分):Payload 如何傳輸?](/ko/whitedec/2025/1/27/django-htmx-advanced-features/)