Формы в Django 5.x: пошаговый разбор для начинающих
Создаём рабочую форму обратной связи на Django: от объявления класса до валидации и сохранения в базу. К концу получите форму, которая проверяет данные и не пускает мусор.

К концу разбора у вас будет форма обратной связи, которая принимает имя, email и текст сообщения, проверяет введённое и сохраняет в базу. Займёт это около 30–40 минут, если Django уже установлен и проект создан. Работаем на Django 5.x и Python 3.10+.
Что нужно иметь до начала
- Установленный Django 5.x. Проверьте командой
python -m django --version— должна вывестись версия вида5.0.xили5.1.x. - Созданный проект (
django-admin startproject mysite) и хотя бы одно приложение внутри него (python manage.py startapp feedback). - Приложение
feedbackдобавлено вINSTALLED_APPSвsettings.py. - Применённые начальные миграции:
python manage.py migrateпроходит без ошибок.
Если приложение не подключено в INSTALLED_APPS, миграции модели ниже просто не создадутся — это первая ловушка, в которую попадают новички.
Два способа объявить форму
В Django есть forms.Form и forms.ModelForm. Разница простая: Form вы описываете вручную поле за полем, а ModelForm строится из модели и умеет сохранять данные в базу одним вызовом save(). Для формы, которая пишет в базу, берите ModelForm — меньше дублирования.
Если поля формы один в один повторяют поля модели — вы почти наверняка хотите ModelForm, а не Form. Ручное дублирование полей — частый источник расхождений между формой и таблицей.
Шаг 1. Опишите модель
Форма будет сохранять сообщения, значит нужна таблица. Откройте feedback/models.py:
# feedback/models.py — Django 5.x
from django.db import models
class Message(models.Model):
name = models.CharField("Имя", max_length=100)
email = models.EmailField("Email")
text = models.TextField("Сообщение")
created_at = models.DateTimeField("Создано", auto_now_add=True)
def __str__(self):
return f"{self.name} — {self.created_at:%d.%m.%Y}"
Создайте и примените миграцию:
python manage.py makemigrations feedback
python manage.py migrate
Проверка: в выводе makemigrations появится строка вроде Create model Message, а migrate завершится строкой OK.
Шаг 2. Объявите форму
Создайте файл feedback/forms.py — по соглашению Django формы живут именно здесь:
# feedback/forms.py — Django 5.x
from django import forms
from .models import Message
class MessageForm(forms.ModelForm):
class Meta:
model = Message
fields = ["name", "email", "text"]
widgets = {
"text": forms.Textarea(attrs={"rows": 5}),
}
def clean_text(self):
# Дополнительная проверка: сообщение не короче 10 символов
text = self.cleaned_data["text"]
if len(text.strip()) < 10:
raise forms.ValidationError("Сообщение слишком короткое.")
return text
Поле created_at в fields не попало намеренно: оно заполняется автоматически через auto_now_add, пользователю его вводить не нужно. Метод clean_text — пример поля-специфичной валидации: Django сам вызовет его при проверке формы.
Шаг 3. Напишите представление
Представление отвечает за две ситуации: показать пустую форму на GET и обработать данные на POST. Откройте feedback/views.py:
# feedback/views.py — Django 5.x
from django.shortcuts import render, redirect
from .forms import MessageForm
def contact(request):
if request.method == "POST":
form = MessageForm(request.POST)
if form.is_valid():
form.save() # ModelForm сам создаёт объект Message
return redirect("contact_done")
else:
form = MessageForm()
return render(request, "feedback/contact.html", {"form": form})
def contact_done(request):
return render(request, "feedback/done.html")
Ключевая деталь — редирект после успешного сохранения. Без него обновление страницы отправит форму повторно. Это шаблон PRG (Post/Redirect/Get), и он тут не украшение, а защита от дублей в базе.
Шаг 4. Пропишите маршруты
В feedback/urls.py (создайте файл, если его нет):
# feedback/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("contact/", views.contact, name="contact"),
path("contact/done/", views.contact_done, name="contact_done"),
]
Подключите их в корневом mysite/urls.py:
from django.urls import path, include
urlpatterns = [
path("", include("feedback.urls")),
]
Шаг 5. Сделайте шаблоны
Создайте папку feedback/templates/feedback/ и в ней файл contact.html:
<!-- feedback/templates/feedback/contact.html -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Отправить</button>
</form>
Тег {% csrf_token %} обязателен для любой POST-формы. Без него Django вернёт ошибку 403 — это защита от подделки запросов, а не досадное препятствие. Рядом создайте done.html с любым текстом вроде «Спасибо, сообщение отправлено».
Проверка: запустите python manage.py runserver и откройте http://127.0.0.1:8000/contact/. Должна появиться форма с тремя полями. Отправьте её с коротким текстом — увидите ошибку валидации; с нормальным — редирект на страницу «Спасибо».
Ручной вывод полей вместо form.as_p
{{ form.as_p }} удобен для старта, но верстку вы им не контролируете. Когда понадобится своё оформление, выводите поля по одному:
<form method="post">
{% csrf_token %}
<div>
{{ form.name.label_tag }}
{{ form.name }}
{{ form.name.errors }}
</div>
<div>
{{ form.email.label_tag }}
{{ form.email }}
{{ form.email.errors }}
</div>
<div>
{{ form.text.label_tag }}
{{ form.text }}
{{ form.text.errors }}
</div>
<button type="submit">Отправить</button>
</form>
Если не получилось
- Forbidden (403) CSRF verification failed. В шаблоне нет
{% csrf_token %}внутри тега<form>. Добавьте его первой строкой после открывающего тега. - TemplateDoesNotExist: feedback/contact.html. Django не нашёл шаблон. Проверьте путь: файл должен лежать в
feedback/templates/feedback/contact.html, а приложение — быть вINSTALLED_APPS. Двойное имя папки не опечатка, а способ избежать конфликтов имён между приложениями. - Форма всегда невалидна, ошибок не видно. Скорее всего вы не выводите
{{ form.errors }}или ошибки конкретных полей. Приform.as_pони выводятся сами, при ручной вёрстке добавьте{{ form.имя.errors }}. - no such table: feedback_message. Не применены миграции. Запустите
makemigrationsиmigrate. - Данные не сохраняются, но ошибок нет. Проверьте, что вызываете
form.save()именно внутри веткиif form.is_valid(), а не до неё.
Form или ModelForm — коротко о выборе
| Ситуация | Что брать |
|---|---|
| Поля совпадают с моделью, нужно сохранить в базу | ModelForm |
| Форма поиска, фильтр, письмо без записи в базу | Form |
| Часть полей из модели, часть — вычисляемые | ModelForm + доп. поля |
Что делать дальше
Форма работает — теперь стоит добавить сообщение об успехе через framework messages и настроить отправку письма администратору при новом сообщении. Валидация нескольких связанных полей одновременно (например, «телефон обязателен, если не указан email») делается в методе clean() формы — это отдельная тема, здесь мы разобрали только валидацию одного поля через clean_text. Точные сигнатуры виджетов и полей сверяйте в разделе Forms официальной документации для вашей версии Django.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: Django documentation — Working with forms, Django documentation — Creating forms from models (ModelForm)
Частые вопросы
У меня Django 4.2, а не 5.x — код подойдёт?
Форма отправляется, но в базе пусто. Что проверить?
Чем form.as_p отличается от as_table и as_div?
Нужна ли модель, если форма просто отправляет письмо?
Как добавить проверку сразу двух полей вместе?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.