Как добавить форму обратной связи на Django, HTMx

Часы
11.04.2025
Часы
15.04.2025
Часы
5 минут
Глазик
37
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0

Вступление

В этой статье, я опишу как добавить на ваш Django-сайт форму обратной связи используя HTMx и немного DaisyUI, в качестве UI-библиотеки. Всё будет сделано на примере моего нового сайта (ссылки на который у меня пока нет, ибо он ещё в разработке). Но зато будут картинки ٩(◕‿◕。)۶

Разберёмся с html и шаблонами

Для начала, нужно выбрать место, где будет размещена форма. Это может быть отдельная страница, страница с контактами или же подвал сайта. Конкретно я, размещу его в подвале сайта, ибо так хочу. Вот как он будет выглядеть:
Данная форма загружается у меня при помощи AJAX-запроса, то есть динамически, во время загрузки основной страницы. При помощи HTMx это можно сделать вот так:
<nav class="flex flex-col gap-[5px]">
<h6 hx-get="feedback/" hx-trigger="load" hx-target="#feedback-form-container" hx-indicator="#simple_indicator" class="footer-title">{% trans 'Форма обратной связи' %}</h6>
<div id="feedback-form-container">
<div id="simple_indicator" class="htmx-indicator">{% trans 'Loading ...' %}</div>
</div>
</nav>
Здесь я делаю запрос на сервер(hx-get) по адресу feedback/, во время появления данного элемента в DOM(hx-tigger). Ответ сервера я вставляю в элемент div с id="feedback-form-container". Так же я указал простой индикатор загрузки при помощи атрибута hx-indicator, где указал его id.
Заметь, я не использовал атрибут hx-swap, ибо у него есть значение по умолчанию, которое мне подходит, а именно "innerHTML"
После запроса по адресу feedback/, сервер вернёт отрендереный шаблон, feedback-form.html.
{% load i18n %}
{% load static %}

<form id="feedback-form" class="flex flex-col gap-[5px] min-w-[300px]">
{% csrf_token %}
{% for field in form %}
<div class="flex flex-row gap-[5px]">
{{ field }}
</div>
{% for error in field.errors %}
<p class="m-[0] text-error">{{ error }}</p>
{% endfor %}
{% endfor %}
<input hx-post="feedback/" hx-target="#feedback-form" hx-swap="outerHTML" class="btn btn-square btn-outline btn-error w-full" type="submit" value="{% trans 'Отправить' %}">
{% if success_feedback_message %}
{% include 'parts/toast-message.html' with message=success_feedback_message status='success' %}
{% endif %}
{% if error_feedback_message %}
{% include 'parts/toast-message.html' with message=error_feedback_message status='error' %}
{% endif %}
</form>
Сервер вернёт контекстную переменную, form. Мы можем проитерировать её и получить все поля формы, а так же ошибки при заполнении, если таковые имеются. Плюс, мы отдельно вписываем кнопку отправки формы. И так как, наша цель для замены снипета кода, это сама форма, нужно ещё отдельно указать политику замены, то есть hx-swap=outerHTML.
Вместе с формой вернётся сообщение с сервера о результате отправки формы. Это сообщение я отправляю в другой шаблон. Вот так я их подключаю и передаю:
{% if success_feedback_message %}
{% include 'parts/toast-message.html' with message=success_feedback_message status='success' %}
{% endif %}
{% if error_feedback_message %}
{% include 'parts/toast-message.html' with message=error_feedback_message status='error' %}
{% endif %}
То как выглядит вернувшаяся форма с ошибками
То как выглядит вернувшаяся форма без ошибок
Шаблон сообщения с сервера(попап, тост), toast-message.html, выглядит вот так:
<div id="toast_feedback" class="toast toast-bottom toast-end">
<div hx-get="api/raw_delete/" hx-target="#toast_feedback" hx-swap="outerHTML" hx-trigger="click, load delay:5s" class="alert alert-{{status}} flex flex-row gap-[5px]">
<div class="w-[20px] flex">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<span>{{message}}</span>
</div>
</div>
Чтобы корректно работать, данный шаблон должен принимать две контекстные переменные - сообщение(message) и его статус(status). Что более интересно в этом шаблоне, так это то, как и куда он отправляет запросы.
Смотри, отправка запроса происходит при нажатии на сам попап или через 5 секунд после загрузки(hx-trigger="click, load delay:5s"). Запрос отправляется по адресу api/raw_delete/, в качестве ответа сервер всегда возвращает пустую строку и код ответа 200. Больше о триггерах в HTMx можно узнать здесь. А о кодах ответа тут.
Вот представление на сервере, которое отвечает на GET-запросы по адресу api/raw_delete/:
from django.http import HttpResponse
from django.views.decorators.http import require_http_methods

@require_http_methods(["GET"])
def raw_delete(request):
return HttpResponse('', status=200)
Не обязательно, конечно, использовать именно GET-запрос, просто он самый простой в разработке. Больше о декораторах на django-представления можно узнать уже здесь.
По итогу, попап удаляется и пользователь снова может отправить запрос.

Создадим специальную форму, FeedbackForm

После того как были написаны шаблоны формы и попапы сообщений с сервера, можно и нужно написать класс формы, которая и определяет какие поля в ней должны присутствовать, какие являются обязательными, а какие нет.
from django import forms
from django.utils.translation import gettext_lazy as _
from captcha.fields import CaptchaField, CaptchaTextInput


class FeedbackForm(forms.Form):
username = forms.CharField(
max_length=25,
min_length=3,
widget=forms.TextInput(
attrs={
'placeholder': _('Имя'),
'class': "w-full"}),
error_messages={'required': _("Это поле обязательно к заполнению")})
email = forms.EmailField(
widget=forms.EmailInput(
attrs={
'placeholder': _('Почта'),
'class': "w-full"}),
error_messages={'required': _("Это поле обязательно к заполнению")})
message = forms.CharField(
widget=forms.Textarea(
attrs={
'placeholder': _('Твоё сообщение'),
'class': "w-full"}),
error_messages={'required': _("Это поле обязательно к заполнению")})
captcha = CaptchaField(
widget=CaptchaTextInput(
attrs={'class': "w-full"}),
error_messages={'required': _("Каптча заполнена неверно")})
Для того чтобы подстроить поле формы под себя, нужно указать для него специальный виджет. В виджетах можно указать значения любых атрибутов(attrs) и так же указать сообщение с ошибкой(error_messages).
Да, знаю, было бы классно если бы можно было указать сообщение с ошибкой в одном месте, DRY-принцип. Но я увы не нашёл способ это сделать ヽ(`⌒´メ)ノ
На этом с формой всё. Когда закончим с созданием формы обратной связи, мы всегда сможем добавить или удалить ненужные поля. И нам больше ничего не придётся делать, ибо всё уже будет сделано и предварительно настроено.

Разработаем и подключим класс-представление FeedbackView

Осталось обработать входящую форму и вернуть её. Плюс сообщение о статусе формы. Представление формы получается очень простым, хотя не без нюансов. Вот сразу её код:
from django.core.mail import send_mail
from django.views.generic import FormView
from .forms import FeedbackForm

class FeedbackView(FormView):
template_name = 'parts/feedback-form.html'
form_class = FeedbackForm

def form_valid(self, form):
subject = f'{form.cleaned_data.get("username")} | {form.cleaned_data.get("email")}'
message = f'{form.cleaned_data.get("message")}'
#send_mail(subject=subject, message=message, from_email=DEFAULT_FROM_EMAIL, recipient_list=[DEFAULT_TO_EMAIL])
return self.render_to_response(self.get_context_data(form=FeedbackForm(), success_feedback_message=_("Успех")))
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form, error_feedback_message=_("Ошибка")))
Начнём по порядку. Если делать всё по официальной документации, то кроме указания таких членов класса, как template_name и form_class нужно ещё будет указать success_url.
success_url - это то URL, на которое будет совершён редирект, после успешной отправки формы.
И в некоторых случаях это будет уместно, но не в нашем. По крайней мере я не нашёл редирект необходимым для меня. Поэтому немного подсмотрев в исходный код родительского класса(FormMixin), переопределил метод form_valid и просто вернул форму с сообщением о результате на клиент.
Ну и конечно же, подключим созданный класс-представление, в urls.py:
from django.urls import path
from Backend import views

urlpatterns = [
path('api/raw_delete/', views.raw_delete, name="raw_delete"),
path('feedback/', views.FeedbackView.as_view()),
]

Заключение

Я понимаю, что форма обратной связи предполагает отправку писем на контактный адрес почты. Хотя это не строго обязательно. Можно например собирать эти письма на отдельную таблицу(Django-модель) в базе данных, а там уже делать с ними что-нибудь(сортировать, удалять, делать рассылку и прочее).
Про то, как можно будет подключить django-сайт к почтовым серверам хостингов я расскажу, как-нибудь отдельно. Ну а пока, имеем один из способов, как можно добавить форму обратной связи на сайт, django-сайт.
И как просто это однако с HTMx, никакого JS кода, только HTML и Python. Приятно однако.


Комментарии

(0)

captcha
Отправить
Сейчас тут пусто. Буть первым (o゚v゚)ノ

Другое

Похожие статьи


Как сделать простой пагинатор на Django и HTMx ч. 1

Часы
02.04.2025
Глазик
62
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу то, как создать пагинатор используя Django и HTMx библиотеку. И то, почему это было так просто в сравнении с пагинатором на моём сайте. С шаблонами …

Как сделать простой пагинатор на Django и HTMx. Добавляем сортировку и фильтры ч. 2

Часы
08.04.2025
Глазик
47
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу процесс и основные блоки кода, для того чтобы добавить сортировку и фильтрацию к пагинатору. Данный пагинатор написан на Django используя HTMx.

Как кастомизировать 404 и 500 страницы ответов в Django

Часы
12.04.2025
Глазик
69
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу процесс кастомизации таких страниц как 404 и 500. Я покажу два основных способа как это сделать и то как можно быстро и легко настроить сервер …

Как добавить карту сайта, sitemap на Django сайт

Часы
17.04.2025
Глазик
22
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0
В этой статье я опишу самый простой и понятный способ добавления карты сайта(sitemap) к Django сайту. Тут ты найдёшь три различных типа имплементации карт сайта, для статических страниц, для страниц …

Использованные термины


Релевантные вопросы