Simplificando el desarrollo web dinámico con Django y HTMX: Uso de Forms y Serializers

En el artículo anterior, examinamos cómo HTMX transmite datos al servidor.

Ver artículo anterior: Simplificando el desarrollo web dinámico con Django y HTMX (Parte 4): Métodos de envío de payload

Confirmamos que, mientras que el fetch() de JavaScript se acerca más a la creación y envío directo de un payload JSON, HTMX se asemeja más a la recopilación de valores del DOM para enviarlos como datos de formulario.

Esto nos lleva naturalmente a la siguiente pregunta:

"¿Cuál es la forma más natural de validar los datos recibidos por HTMX en Django?"

Inicialmente, muchos podrían pensar en el DRF Serializer. De hecho, un Serializer es una potente herramienta de validación y puede usarse incluso sin JSON. Sin embargo, al probarlo con HTMX, a veces puede sentirse un poco forzado.

¿Por qué?

La razón es simple: El flujo básico de HTMX está más alineado con el mundo de los formularios HTML, y Django ya cuenta con Forms diseñados precisamente para ese propósito. En este artículo, analizaremos cómo se pueden utilizar Django Forms y DRF Serializers para procesar solicitudes HTMX, y cuál de ellos es la opción más natural y práctica.

Comparación del rol de Form y Serializer en el procesamiento de solicitudes HTMX


Los datos enviados por HTMX son, en esencia, "datos de formulario"

Como vimos en el artículo anterior, HTMX recopila los valores de los elementos HTML y los envía al servidor. Esto se hace enviando los valores de entrada dentro de un <form>, incluyendo elementos específicos con hx-include, o añadiendo valores adicionales con hx-vals.

Es decir, la filosofía básica de HTMX se acerca a lo siguiente:

  • No ensambla objetos JavaScript directamente.
  • Recopila valores de elementos HTML.
  • Envía una solicitud al servidor.
  • Se integra mejor con respuestas de fragmentos HTML que con JSON.

Este flujo es sorprendentemente importante. Porque esta estructura coincide casi exactamente con el flujo típico de procesamiento de formularios en Django.

Django Form también está diseñado con las siguientes premisas:

  • El usuario introduce datos en un formulario HTML.
  • El servidor recibe request.POST.
  • El Form valida los datos.
  • Si hay errores, se vuelve a renderizar.
  • Si no hay problemas, se guarda y se responde con el resultado.

A estas alturas, HTMX y Django Form parecen encajar perfectamente, como Cenicienta y su zapato de cristal.

Por lo tanto, podríamos llegar a la siguiente conclusión:

La herramienta de validación de Django que mejor se ajusta a HTMX es Django Form, más que DRF Serializer.


¿Por qué Django Form encaja mejor?

DRF Serializer es, sin duda, una excelente herramienta. Sin embargo, si consideramos su contexto de diseño original, un Serializer es una herramienta más orientada a la serialización de datos y la validación de entradas de API.

En cambio, Django Form fue creado desde el principio para:

  • Procesar entradas de formularios HTML.
  • Validación del lado del servidor.
  • Mostrar mensajes de error.
  • Mantener los valores de entrada.
  • Re-renderizar plantillas.

Es decir, cuando se combina con un enfoque como el de HTMX, que "recibe y reemplaza partes del HTML", el flujo es mucho más natural.

Pensemos en un ejemplo:

Un usuario envía un formulario de comentarios.

  • ¿Qué ocurre si la validación falla?
  • Los datos introducidos deben mantenerse.
  • Debe mostrarse qué campos tienen problemas.
  • El formulario con los errores debe re-renderizarse parcialmente.

Django Form maneja muy bien este tipo de experiencia de usuario.

Por supuesto, un Serializer también proporciona errors y permite la validación. Pero el siguiente paso, "reconstruir toda la UX del formulario HTML", es mucho más fluido con un Form.

Por lo tanto, en cuanto a la compatibilidad con HTMX, considerar un Form como la opción predeterminada es lo más apropiado.


La combinación más natural: HTMX + Django Form

Primero, veamos un ejemplo básico. Un sencillo formulario de registro de tareas pendientes.

Definición del Form

from django import forms

class TodoForm(forms.Form):
    title = forms.CharField(max_length=100, label="Título")
    priority = forms.IntegerField(min_value=1, max_value=5, label="Prioridad")

Ahora, en la vista, podemos validar request.POST directamente con el Form.

Procesamiento de la Vista

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"]

            # Lógica de guardado
            # 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,
    })

El punto clave aquí es muy claro:

  1. Recibir los valores enviados por HTMX como request.POST.
  2. El Form realiza la validación.
  3. Si tiene éxito, se devuelve un fragmento HTML.
  4. Si falla, se re-renderiza el Form con los errores.

Este flujo es propio de Django, propio de HTMX y, sobre todo, fácil de mantener.


Volver a mostrar los errores de forma natural en la plantilla

Donde Django Form brilla especialmente es aquí.

Cuando la validación falla, el objeto Form ya contiene la siguiente información:

  • Valores introducidos por el usuario.
  • Mensajes de error por campo.
  • Errores no relacionados con campos.
  • El estado de qué campos no son válidos.

Por lo tanto, la plantilla parcial puede renderizarlo directamente.

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 }}">Título</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 }}">Prioridad</label>
        {{ form.priority }}
        {% if form.priority.errors %}
            <div class="field-error">{{ form.priority.errors }}</div>
        {% endif %}
    </div>

    <button type="submit">Registrar</button>
</form>

Este ejemplo es muy simple, pero ilustra bien la compatibilidad entre HTMX y Django Form.

  • En caso de fallo, el formulario completo puede volver a renderizarse.
  • Los valores introducidos por el usuario se mantienen.
  • Los mensajes de error se adjuntan naturalmente junto a cada campo.

Si se hubiera utilizado el enfoque tradicional de fetch() + JSON + manipulación manual del DOM, se habría necesitado mucho JavaScript para implementar este flujo. Sin embargo, con la combinación de HTMX y Django Form, se puede manejar de forma mucho más sencilla solo con código del lado del servidor y plantillas.


¿Entonces no necesitamos Serializers?

Decir que no se necesita un Serializer sería exagerar. Quizás sería mejor expresarlo así: No es necesario elegir un Serializer como opción predeterminada.

De hecho, un DRF Serializer sigue siendo muy atractivo en las siguientes situaciones:

  • Si ya se está utilizando DRF activamente en todo el proyecto.
  • Si se desea reutilizar la misma lógica de validación tanto en la API como en las pantallas renderizadas por el servidor.
  • Si la lógica de validación de entrada es compleja y ya está bien organizada en un Serializer.
  • Si se planea exponer la misma funcionalidad a aplicaciones móviles o APIs externas en el futuro.

Es decir, la cuestión con un Serializer no es "¿Se puede usar con HTMX?", sino más bien "¿Traer un Serializer aquí aporta algún beneficio a la arquitectura general?".


También se puede usar un Serializer

Por ejemplo, supongamos que ya tenemos un Serializer como el siguiente:

from rest_framework import serializers

class TodoSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    priority = serializers.IntegerField(min_value=1, max_value=5)

En este caso, se puede usar perfectamente en una solicitud 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)

Como puede verse, técnicamente no hay ningún problema. Un Serializer no es una herramienta que solo acepte JSON.

Sin embargo, aquí se revela una sutil diferencia.

Al usar un Form:

  • El mantenimiento de los valores de entrada.
  • La renderización por campo.
  • La vinculación de errores.
  • La conexión con la plantilla.

Todo esto se enlaza de forma natural.

En cambio, al usar un Serializer:

  • Se deben desglosar manualmente los serializer.errors para que se ajusten a la estructura de la plantilla.
  • Los valores de entrada existentes deben pasarse por separado.
  • El desarrollador debe prestar más atención a la conexión con la re-renderización del formulario HTML.

Es decir, se puede usar, pero requiere un poco más de trabajo manual.

Es precisamente por este punto que un Serializer puede sentirse un poco forzado cuando se usa con HTMX.


Conclusión

En el artículo anterior, examinamos cómo HTMX envía datos. En esta entrega, hemos organizado cómo validarlos de la manera más natural en Django.

  • HTMX se integra bien con los datos de formulario de forma predeterminada.
  • Django Form es una herramienta diseñada precisamente para ese flujo.
  • Por lo tanto, en cuanto a la compatibilidad con HTMX, un Form es la opción más natural.
  • DRF Serializer se puede usar, pero es una opción más estratégica.

Personalmente, creo que para aprovechar HTMX al máximo, es necesario recuperar no solo la "sensación de manejar AJAX de forma HTML", sino también la "sensación de utilizar Django Form y sus etiquetas de formulario".


Artículos relacionados