Формы в Django
Зачем формы
Обработка request.POST вручную требует много рутины: проверить, что поля заполнены, что email похож на email, что число — число; при ошибке показать форму снова с сообщениями и введёнными данными. Django-формы делают это за вас:
- генерируют HTML полей;
- проверяют (валидируют) данные;
- приводят их к типам Python;
- показывают ошибки.
Класс Form
library/forms.py:
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(label="Имя", max_length=100)
email = forms.EmailField(label="Email")
subject = forms.ChoiceField(choices=[("q", "Вопрос"), ("bug", "Ошибка")])
message = forms.CharField(label="Сообщение", widget=forms.Textarea(attrs={"rows": 5}))
copy = forms.BooleanField(label="Отправить копию мне", required=False)
Поля формы похожи на поля модели: CharField, IntegerField, EmailField, DateField, ChoiceField, BooleanField, FileField… Все поля по умолчанию обязательны — required=False делает поле необязательным.
Виджет определяет HTML-элемент: TextInput, Textarea, Select, CheckboxInput, PasswordInput, DateInput… Через attrs можно добавить HTML-атрибуты (например, CSS-классы Bootstrap).
Обработка во view
from django.contrib import messages
from django.shortcuts import redirect, render
from .forms import ContactForm
def contact(request):
if request.method == "POST":
form = ContactForm(request.POST) # «связанная» форма с данными
if form.is_valid():
data = form.cleaned_data
send_mail(data["subject"], data["message"], data["email"], ["admin@site.ru"])
messages.success(request, "Сообщение отправлено!")
return redirect("contact") # Post/Redirect/Get
else:
form = ContactForm() # пустая форма
return render(request, "contact.html", {"form": form})
Логика:
- GET — показываем пустую форму.
- POST — заполняем форму данными и проверяем
is_valid(). - Если всё хорошо — делаем дело и перенаправляем (чтобы F5 не отправил форму повторно).
- Если есть ошибки — снова рендерим ту же форму: она уже содержит введённые данные и сообщения об ошибках.
Вывод в шаблоне
Быстро:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Отправить</button>
</form>
Варианты: {{ form }} (с Django 5 — разметка на div), form.as_p, form.as_ul, form.as_table.
Вручную — для полного контроля над вёрсткой:
<form method="post" novalidate>
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
<div class="field">
{{ field.label_tag }}
{{ field }}
{% if field.help_text %}<small>{{ field.help_text }}</small>{% endif %}
{% for error in field.errors %}<div class="error">{{ error }}</div>{% endfor %}
</div>
{% endfor %}
<button type="submit">Отправить</button>
</form>
Так, например, сделаны формы входа и регистрации на этом сайте.
CSRF
{% csrf_token %} добавляет скрытое поле с секретным токеном. Django проверяет его у каждого POST-запроса — это защита от CSRF-атак, когда чужой сайт заставляет браузер пользователя отправить форму на ваш сайт. Без токена — ошибка 403.
ModelForm: форма из модели
Если форма создаёт или редактирует объект модели, не нужно описывать поля заново (помните DRY?):
from .models import Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ["title", "author", "pages", "description"] # или "__all__" (не рекомендуется)
labels = {"title": "Название"}
widgets = {"description": forms.Textarea(attrs={"rows": 4})}
help_texts = {"pages": "Целое число"}
ForeignKey автоматически станет выпадающим списком, ManyToManyField — множественным выбором.
def book_create(request):
form = BookForm(request.POST or None)
if request.method == "POST" and form.is_valid():
book = form.save() # создаёт объект в базе
return redirect(book)
return render(request, "library/book_form.html", {"form": form})
def book_update(request, pk):
book = get_object_or_404(Book, pk=pk)
form = BookForm(request.POST or None, instance=book) # instance — редактируем существующий
if request.method == "POST" and form.is_valid():
form.save()
return redirect(book)
return render(request, "library/book_form.html", {"form": form})
form.save(commit=False) создаёт объект, но не сохраняет его — чтобы дописать поля, которых нет в форме:
post = form.save(commit=False)
post.author = request.user
post.save()
form.save_m2m() # если есть ManyToMany-поля
Валидация
Встроенная
Поля сами проверяют тип, max_length, required, формат email, уникальность (unique=True у модели в ModelForm)…
Проверка одного поля: clean_<поле>
class BookForm(forms.ModelForm):
...
def clean_title(self):
title = self.cleaned_data["title"].strip()
if title.lower() == "без названия":
raise forms.ValidationError("Придумайте название")
return title # обязательно вернуть значение!
Проверка нескольких полей: clean()
class RegisterForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
def clean(self):
cleaned = super().clean()
if cleaned.get("password") != cleaned.get("password2"):
raise forms.ValidationError("Пароли не совпадают")
return cleaned
Ошибка из clean() попадает в form.non_field_errors. Чтобы привязать её к конкретному полю — self.add_error("password2", "Пароли не совпадают").
Загрузка файлов
<form method="post" enctype="multipart/form-data">
form = DocumentForm(request.POST, request.FILES)
Без enctype и request.FILES файлы не дойдут до сервера.
Итог
forms.Form— произвольная форма,forms.ModelForm— форма из модели.- Шаблон обработки: GET → пустая форма; POST →
is_valid()→ действие →redirect. - Данные после проверки — в
form.cleaned_data. - Своя валидация:
clean_<поле>()иclean(). - В POST-форме всегда
{% csrf_token %}.
Практика решено 0 из 6
Создайте форму ContactForm (наследник forms.Form) с полями:
name — строка до 100 символов, email — EmailField, message — строка с виджетом Textarea.
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
Создайте BookForm на основе модели Book (ModelForm) с полями title, author, pages.
from django import forms
from .models import Book
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ["title", "author", "pages"]
Расставьте строки типичной обработки формы во view.
-
else: -
if request.method == "POST": -
if form.is_valid(): -
form = BookForm() -
form.save() -
return redirect("library:book_list") -
return render(request, "library/book_form.html", {"form": form}) -
form = BookForm(request.POST)
Где после успешной проверки is_valid() лежат очищенные данные формы?
Добавьте в BookForm метод проверки поля pages: если значение меньше 1,
выбросьте forms.ValidationError("Слишком мало страниц"), иначе верните значение.
class BookForm(forms.ModelForm):
class Meta:
model = Book
fields = ["title", "author", "pages"]
def clean_pages(self):
pages = self.cleaned_data["pages"]
if pages < 1:
raise forms.ValidationError("Слишком мало страниц")
return pages
Зачем в POST-форме нужен {% csrf_token %}?
Войдите, чтобы отмечать пройденные уроки.