使用 Django 和 HTMX 简化动态 Web 开发:Form 和 Serializer 的应用
在上一篇文章中,我们探讨了 HTMX 将数据发送到服务器的方式。
回顾上一篇:使用Django和HTMX简化动态Web开发(第四部分):Payload传输方式
我们发现,如果说传统 JavaScript 的 fetch() 更接近于直接构建 JSON payload 并发送,那么 HTMX 则更倾向于从 DOM 收集值并以 form-data 的形式发送。
那么,一个自然而然的问题便浮现出来:
“对于 HTMX 传入的数据,Django 应该使用什么工具进行验证才最自然?”
起初,许多人可能会想到 DRF Serializer。实际上,Serializer 确实是一个强大的验证工具,即使不是 JSON 数据也能使用。但当我们尝试将其与 HTMX 结合使用时,总会感觉有些格格不入。
这是为什么呢?
原因很简单:
HTMX 的基本工作流程更贴近 HTML 表单的世界,而 Django 中恰好已经存在专为此类场景设计的 Form。
在本文中,我们将整理 在 HTMX 请求处理中,Django Form 和 DRF Serializer 各自的用法,并探讨 哪种方案更为自然和实用。

HTMX 发送的数据本质上更接近“表单数据”
正如我们在上一篇中看到的那样,HTMX 默认会收集 HTML 元素的值并将其发送到服务器。这包括发送 <form> 中的输入值,通过 hx-include 包含特定元素,或通过 hx-vals 附加额外的值。
换句话说,HTMX 的核心理念更接近于:
- 不直接组装 JavaScript 对象
- 从 HTML 元素中收集值
- 向服务器发送请求
- 与 JSON 相比,更适合接收 HTML 片段响应
这个流程比想象中更为重要。 因为它与典型的 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 会显得更加自然流畅。
例如,我们来设想一个场景:
用户提交一个评论表单。
- 如果验证失败怎么办?
- 用户输入的内容应该被保留
- 需要显示哪些字段有问题
- 需要重新部分渲染带有错误的表单
这些用户体验正是 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="우선순위")
现在,在视图中,我们可以直接将 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 片段
- 失败时重新渲染带有错误的 Form
这个流程既符合 Django 的风格,也符合 HTMX 的风格,最重要的是,它易于维护。
在模板中自然地重新显示错误
Django Form 在这里尤其出色。
当验证失败时,Form 对象中已经包含了以下信息:
- 用户输入的值
- 各字段的错误消息
- 非字段错误
- 哪些字段无效的状态
因此,在局部模板中可以直接渲染这些信息。
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 表单重新渲染的连接
也就是说,虽然可以使用,但会增加一些手动操作。
正是因为这一点,当与 HTMX 结合使用时,Serializer 可能会让人感觉有些生硬。
总结
在上一篇文章中,我们探讨了 HTMX 如何发送数据。而在本文中,我们总结了在 Django 中如何最自然地验证这些数据。
- HTMX 默认与 form-data 配合良好
- Django Form 正是为此流程设计的工具
- 因此,仅从与 HTMX 的兼容性来看,Form 是最自然的选择
- DRF Serializer 虽然可用,但更偏向于战略性的选择
我个人认为,要充分发挥 HTMX 的潜力,不仅需要“以 HTML 方式处理 AJAX 的直觉”,还需要“重新找回利用 Django Form 和表单标签的直觉”。
相关阅读
目前没有评论。