Упрощение динамической веб-разработки с Django и HTMX: использование форм и сериализаторов



В предыдущей статье мы рассмотрели, как HTMX передает данные на сервер.

См. предыдущую статью: Упрощение динамической веб-разработки с Django и HTMX (Часть 4): Способы передачи полезной нагрузки (Payload)

Мы убедились, что если функция fetch() в традиционном JavaScript напрямую создает и отправляет JSON-полезную нагрузку, то HTMX больше ориентирован на сбор значений из DOM и их отправку в виде form-data.

Тогда, естественно, возникает вопрос:

"Какой способ валидации данных, полученных через HTMX, наиболее естественен в Django?"

Многие поначалу могут подумать о DRF Serializer. Действительно, сериализатор — мощный инструмент валидации, который можно использовать не только с JSON. Однако при работе с HTMX он иногда может казаться несколько натянутым.

Почему так происходит?

Причина проста: Основной подход HTMX ближе к миру HTML-форм, а в Django уже есть Form, разработанный специально для этого мира.

В этой статье мы рассмотрим, как можно использовать Django Form и DRF Serializer при обработке HTMX-запросов, а также какой из них является более естественным и практичным выбором.

Сравнение ролей Form и Serializer при обработке HTMX-запросов


Данные, отправляемые HTMX, по сути, являются "данными формы"

Как мы видели в предыдущей части, HTMX по умолчанию собирает значения HTML-элементов и отправляет их на сервер. Это может быть отправка входных значений внутри <form>, включение определенных элементов с помощью hx-include или добавление дополнительных значений с помощью hx-vals.

Таким образом, основная философия HTMX ближе к следующему:

  • Не собирает JavaScript-объекты напрямую
  • Собирает значения из HTML-элементов
  • Отправляет запрос на сервер
  • Лучше сочетается с ответами в виде HTML-фрагментов, чем с JSON

Этот подход гораздо важнее, чем кажется, поскольку его структура почти полностью совпадает с типичным процессом обработки форм в Django.

Django Form также разработан с учетом следующих предположений:

  • Пользователь вводит данные в HTML-форму
  • Сервер получает request.POST
  • Форма валидирует данные
  • При наличии ошибок форма перерисовывается
  • При отсутствии проблем данные сохраняются, и отправляется ответ

К этому моменту начинает казаться, что HTMX и Django Form идеально подходят друг другу, как Золушка и хрустальная туфелька.

Поэтому, возможно, стоит сделать такой вывод:

Самый естественный инструмент валидации в Django, который идеально сочетается с HTMX, — это Django Form, а не DRF Serializer.


Почему Django Form подходит лучше



DRF Serializer, безусловно, отличный инструмент. Однако, если учитывать его первоначальный контекст проектирования, сериализатор больше ориентирован на сериализацию данных и валидацию ввода API.

В то время как Django Form изначально был создан для следующего:

  • Обработка ввода HTML-форм
  • Валидация на стороне сервера
  • Отображение сообщений об ошибках
  • Сохранение введенных значений
  • Перерисовка шаблонов

То есть, он гораздо естественнее сочетается с подходом, подобным HTMX, когда "получается и заменяется часть HTML".

Рассмотрим пример.

Пользователь отправляет форму для написания комментария.

  • Что, если валидация не удалась?
  • Введенные данные должны быть сохранены
  • Необходимо показать, какое поле содержит ошибку
  • Форма с ошибками должна быть частично перерисована

Django Form отлично справляется с таким пользовательским опытом.

Конечно, Serializer также предоставляет errors и позволяет выполнять валидацию. Однако следующий шаг — "перестройка всего пользовательского интерфейса HTML-формы" — гораздо более гладко реализуется с помощью Form.

Поэтому, если рассматривать только совместимость с HTMX, правильнее считать Form основным выбором.


Наиболее естественное сочетание: HTMX + Django 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 для валидации.

Обработка в представлении

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. Форма выполняет валидацию
  3. В случае успеха возвращается HTML-фрагмент
  4. В случае неудачи форма с ошибками перерисовывается

Этот подход соответствует духу 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 это можно обработать гораздо проще, используя только серверный код и шаблоны.


Значит ли это, что сериализаторы не нужны?

Утверждение, что сериализаторы не нужны, кажется слишком категоричным. Возможно, лучше выразиться так: Нет необходимости выбирать сериализатор в качестве основного инструмента по умолчанию.

На самом деле, DRF Serializer по-прежнему очень привлекателен в следующих ситуациях:

  • Если DRF уже активно используется в проекте
  • Если необходимо повторно использовать одну и ту же логику валидации для API и серверных страниц
  • Если логика валидации ввода сложна и уже хорошо структурирована в Serializer
  • Если планируется в будущем предоставить ту же функциональность для мобильных приложений или внешних API

То есть, вопрос не в том, "можно ли использовать Serializer с HTMX?", а скорее в том, "есть ли архитектурная выгода в использовании 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)

Как видите, технически проблем нет никаких. Сериализатор не является инструментом, предназначенным только для JSON.

Однако здесь проявляется тонкое различие.

При использовании Form:

  • Сохранение введенных значений
  • Рендеринг полей
  • Привязка ошибок
  • Связь с шаблоном

Все это происходит естественным образом.

В то время как при использовании Serializer:

  • Необходимо вручную адаптировать serializer.errors к структуре шаблона
  • Передавать существующие входные значения отдельно
  • Разработчику приходится больше заботиться о связи с перерисовкой HTML-формы

То есть, использовать его можно, но это требует немного больше ручной работы.

Именно из-за этого момента HTMX и Serializer при совместном использовании может казаться немного натянутым.


Заключение

В предыдущей части мы рассмотрели, как HTMX отправляет данные. А в этой части мы обобщили, какой способ валидации этих данных в Django является наиболее естественным.

  • HTMX по умолчанию хорошо сочетается с form-data
  • Django Form — это инструмент, разработанный специально для этого процесса
  • Поэтому, с точки зрения совместимости с HTMX, Form является наиболее естественным выбором
  • DRF Serializer можно использовать, но это скорее стратегический выбор

Лично я считаю, что для эффективного использования HTMX необходимо восстановить не только "чувство работы с AJAX в стиле HTML", но и "чувство использования Django Form и тегов форм".


Читайте также