## 使用 [[Django]] 和 HTMX 简化动态 Web 开发:Form 和 Serializer 的应用 {#sec-b233da7ff7c9} 在上一篇文章中,我们探讨了 **[[HTMX]] 将数据发送到服务器的方式**。 **回顾上一篇**:[使用 Django 和 HTMX 简化动态 Web 开发(第四部分):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 表单的世界,而 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 验证工具是 Django Form,而非 DRF Serializer。** --- ## 为什么 Django Form 更适合? {#sec-b1e7f36e5f83} DRF Serializer 无疑是一个出色的工具。 但考虑到其最初的设计初衷,Serializer 更侧重于**数据序列化和 API 输入验证**。 而 Django Form 从一开始就是为以下目的而创建的: * 处理 HTML 表单输入 * 服务器端验证 * 显示错误消息 * 保留输入值 * 模板重新渲染 这意味着,当与 HTMX 这种**“接收部分 HTML 并替换”的方式**结合时,Django Form 会显得更加自然流畅。 例如,我们来设想一个场景: 用户提交一个评论表单。 * 如果验证失败怎么办? * 用户输入的内容应该被保留 * 需要显示哪些字段有问题 * 需要重新部分渲染带有错误的表单 这些用户体验正是 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="우선순위") ``` 现在,在视图中,我们可以直接将 `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 默认与 form-data 配合良好 * Django Form 正是为此流程设计的工具 * 因此,仅从与 HTMX 的兼容性来看,Form 是最自然的选择 * DRF Serializer 虽然可用,但更偏向于战略性的选择 我个人认为,要充分发挥 HTMX 的潜力,不仅需要**“以 HTML 方式处理 AJAX 的直觉”**,还需要**“重新找回利用 Django Form 和表单标签的直觉”**。 --- **相关阅读** * [使用 Django 和 HTMX 简化动态 Web 开发(第一部分)](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification/) * [使用 Django 和 HTMX 简化动态 Web 开发 - Ajax(第二部分)](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification-2/) * [使用 Django 和 HTMX 简化动态 Web 开发(第三部分):Django 集成方法](/ko/whitedec/2025/1/27/django-htmx-dynamic-web-simplification-3/) * [使用 Django 和 HTMX 简化动态 Web 开发(第四部分):payload 是如何传输的?](/ko/whitedec/2025/1/27/django-htmx-advanced-features/)