How to add the feedback form using Django and HTMx

Clock
11.04.2025
Clock
15.04.2025
Clock
6 minutes
An eye
48
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0

Introduction

In this article, I will describe the way to add on your Django website feedback form using only HTMx and a little bit of DaisyUI as a UI library. Everything is done by using my upcoming website (there is no link to it yet). But, hey, there are some images here ٩(◕‿◕。)۶.

Let's get rid of HTML and templates first

As a starting point, we need to choose a location to put the feedback form. It can be a special page, designed only for a form, a contact's page, or the footer of the website. Specifically, I am going to place it in the footer because I do what I want to do. This is how it's going to look like:
This form is uploaded into the page via an AJAX request that is dynamically loaded during the time when the main page is loading. Via HTMx, you could accomplish this like that:
<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>
Here, you can see how I did the request to the server(hx-get) by feedback/ address when this element first appeared in the DOM tree (hx-trigger). The response from the server will be placed in the div element with id="feedback-form-container". Also want to mention, I did use a simple, built-in loading indicator with the help of the hx-indicator attribute, with ID equal to "#simple_indicator".
Please notice the fact that I did not use the hx-swap attribute because of the default value of this attribute. Which is a good fit for me, that is, "innerHTML."
After the request to feedback/ address, the server will respond with the rendered-ready template, 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>
The form will be returned via a context variable, and you can access that variable via the form keyword. BTW, we can iterate through her and get all the form's fields and also errors if there are some. We insert a submit button separate from the rest of the form. And because we want to replace a whole form, we should change the policy of swapping via the hx-swap attribute, and it must be equal to "outerHTML"
Besides the feedback form, in the request, we will receive a result message too. This message will be sent into another template. This is how it is connected with the feedback template:
{% 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 %}
This is how a feedback form with errors will look like.
This is how a feedback form without errors will look like.
The feedback message template (toast-message.html) is structured like this:
<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>
To be able to work correctly, that template must be given two context variables - the message and the status variable. What is more interesting in this template is how and when he sends the requests.
Let me explain. To send the request, the user must click on it or wait 5 seconds after inserting it in the DOM tree (hx-trigger="click, load delay:5s"). The request is sent to api/raw_delete/, and the server will always respond with an empty string and a 200 response code. You could get more about triggers in HTMx here, but about the response codes here.
Here is a view function, which responds only to the GET requests by an api/raw_delete/ address.
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)
It is not required, for sure, to use the GET request; it's just the simplest and fastest to develop. To know more about Python's decorators in Django views, see the link.
In the end, the feedback's message is removed, and the user once again could send the feedback request.

Let's develop the feedback form

After the form templates and message popups from the server have been written, you can and should write a form class that determines which fields should be present in it, which are mandatory, and which are not.
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': _("Каптча заполнена неверно")})
In order to customize a form field for yourself, you need to specify a special widget for it. In widgets, you can specify the values ​​of any attributes (attrs) and also specify an error message (error_messages).
Yes, I know, it would be cool if it were possible to specify an error message in one place, the DRY principle. But unfortunately, I did not find a way to do this ヽ(`⌒´メ)ノ
That's all for the form. When we finish creating the feedback form, we can always add or delete unnecessary fields. And we will not have to do anything else because everything will already be done and pre-configured.

Let's develop and connect the FeedbackView class-based view

All that's left is to process the incoming form and return it. Plus a message about the form status. The presentation of the form is very simple, although not without nuances. Here's its code right away:
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=_("Ошибка")))
Let's start in order. If you do everything according to the official documentation, then in addition to specifying such class members as template_name and form_class, you will also need to specify success_url.
success_url is the URL to which a redirect will be made after the form is successfully submitted.
And in some cases this will be appropriate, but not in mine. At least I did not find the redirect necessary for me. Therefore, having peeked a little into the source code of the parent class (FormMixin), I overrode the form_valid method and simply returned the form with a message about the result to the client.
And of course, we will connect the created presentation class in 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()),
]

Conclusion

I get it; the feedback form works only in case of sending messages back to senders. But it is not always the case. We, for example, could collect those messages in the table (Django model) of the database. And later make use of them: sorting, removing, etc.
About to connect the Django website to email providers I will discuss somewhere later. Till then, we have one of the many ways to add the feedback form to the Django website.
And it is so easy with HTMx, not a single line of JS code, only HTML and Python. It is nice, though.

Comments

(0)

captcha
Send
It's empty now. Be the first (o゚v゚)ノ

Other

Similar articles


How to make simple paginator in Django and HTMx pr. 1

Clock
02.04.2025
An eye
52
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will describe how to create a paginator using Django and the HTMx library. And why it was so easy compared to the paginator on my site. …

How to make simple paginator in Django and HTMx. Adding fitering and sorting feature. pr. 2

Clock
08.04.2025
An eye
25
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article I will describe the process and main code blocks to add sorting and filtering feature to a paginator. This paginator is written in Django using HTMx.

How to customize yourown 404 and 500 pages in Django

Clock
12.04.2025
An eye
48
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will describe the process of customizing pages such as 404 and 500. I will show two main ways to do this and how you can quickly …

How to add sitemap into Django website

Clock
17.04.2025
An eye
28
Hearts
0
Connected dots
0
Connected dots
0
Connected dots
0
In this article, I will describe the simplest and most understandable way to add a sitemap to a Django site. Here you will find three different types of sitemap implementations, …

Used termins


Related questions