Работа с HTML-формами — важная часть любого веб-приложения. До этого момента мы уже создавали формы вручную, использовали функции-представления и класс View, переопределяли методы get() и post(), передавали данные в шаблоны и обрабатывали результаты отправки форм.
Но Django предоставляет гораздо более удобный инструмент для работы с формами — класс FormView. Это полноценный класс-представление, ориентированный именно на задачу «показать форму → обработать отправку → вернуть результат».
Этот урок посвящён глубокому разбору FormView, пониманию его механизма работы, частых ошибок и практическому применению в проекте cinemahub.
Мы будем работать на примере формы добавления нового фильма в каталог. Это не финальная реализация функционала (позже в практике мы будем делать всё по-настоящему), но идеальный учебный контекст, чтобы сосредоточиться на механике FormView.
Когда мы создаём форму через обычный View, нам приходится вручную писать:
- получение формы на GET-запрос
- обработку данных на POST-запрос
- проверку формы
- сохранение данных (если это форма, связанная с моделью)
- редирект после успешной отправки
- повторный рендеринг формы при ошибках
Это большой объём шаблонного кода, который приходится писать снова и снова.
FormView решает эту проблему:
- самостоятельно рендерит форму
- проверяет её валидность
- вызывает методы, которые мы можем переопределить — только там, где нужно
- выполняет редирект
- автоматически передаёт форму в контекст
При этом FormView не навязывает логику сохранения — она остаётся под контролем разработчика, что важно, например, когда нужно выполнить сложную бизнес-логику перед сохранением.
Пусть у нас есть задача: создать форму, через которую можно добавить новый фильм в каталог. Для урока нам не нужно использовать все поля модели Movie, достаточно нескольких.
Файл: movies/models.py
class Movie(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
year = models.PositiveIntegerField()
is_published = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.titleВ этом уроке мы используем только часть полей, чтобы сфокусироваться на FormView.
Файл: movies/forms.py
from django import forms
from .models import Movie
class MovieCreateForm(forms.ModelForm):
class Meta:
model = Movie
fields = ['title', 'description', 'year', 'is_published']ModelForm сам выполнит всю необходимую валидацию — нам не нужно переписывать её вручную.
Теперь создадим класс, который будет отображать и обрабатывать форму.
Файл: movies/views.py
from django.views.generic.edit import FormView
from django.urls import reverse_lazy
from .forms import MovieCreateForm
class MovieCreateView(FormView):
template_name = 'movies/create_movie.html'
form_class = MovieCreateForm
success_url = reverse_lazy('movies:list')Разберём:
- template_name — путь к шаблону, который будет использоваться для формы.
- form_class — класс формы, которая будет рендериться.
- success_url — куда перенаправить пользователя после успешного заполнения.
reverse_lazy() откладывает вычисление URL до момента фактического вызова.
Это важно, потому что в момент загрузки views.py маршруты ещё не загружены.
Если использовать reverse(), можно получить ошибку:
django.core.exceptions.ImproperlyConfigured: The included URLconf ... does not appear to have any patterns in it.
reverse_lazy решает эту проблему.
По умолчанию FormView ничего не сохраняет. Он только проверяет форму.
Поэтому мы должны переопределить метод:
def form_valid(self, form):
form.save()
return super().form_valid(form)Дописываем в наш класс:
class MovieCreateView(FormView):
template_name = 'movies/create_movie.html'
form_class = MovieCreateForm
success_url = reverse_lazy('movies:list')
def form_valid(self, form):
form.save()
return super().form_valid(form)Теперь после успешной отправки формы фильм действительно будет создан.
Иногда нам нужно пробросить заголовок, меню или любую другую информацию.
FormView позволяет сделать это без переопределения get_context_data():
extra_context = {
'title': 'Добавление фильма',
}Итоговая версия:
class MovieCreateView(FormView):
template_name = 'movies/create_movie.html'
form_class = MovieCreateForm
success_url = reverse_lazy('movies:list') # можно заменить на любую другую страницу, например index
extra_context = {
'title': 'Добавление фильма'
}
def form_valid(self, form):
form.save()
return super().form_valid(form)Файл: movies/urls.py
from django.urls import path
from .views import MovieCreateView
app_name = 'movies'
urlpatterns = [
path('add/', MovieCreateView.as_view(), name='add'),
]Файл: movies/templates/movies/create_movie.html
{% extends 'base.html' %} {% block content %}
<h1>{{ title }}</h1>
<form method="post">
{% csrf_token %} {{ form.as_p }}
<button type="submit">Добавить фильм</button>
</form>
{% endblock %}После всех изменений запускаем сервер:
python manage.py runserver
Переходим в браузере:
http://127.0.0.1:8000/movies/add/
Должно отобразиться:
- Заголовок
- Форма с полями title, description, year
- Кнопка отправки
Проверка:
-
Заполняем форму.
-
Отправляем.
-
Если всё корректно — происходит редирект на
movies:list. -
Если формы нет или ошибки — проверяем:
- корректность шаблона
- правильность названия формы (form, а не movie_form и т.п.)
- что форма наследует ModelForm
Причина: неправильный путь в template_name.
Как исправить: убедиться, что файл расположен по указанному пути.
Причина: используете reverse() вместо reverse_lazy()
Исправление: заменить на reverse_lazy().
Причина: забыли переопределить form_valid().
Исправление: добавить form.save().
Причина: используете неправильное имя переменной в шаблоне. FormView передает форму как form.
Создайте класс-представление, основанный на FormView, который выводит форму добавления жанра (Genre).
Форма должна запрашивать два поля:
nameslug
Маршрут должен находиться по адресу /genres/add/.
После успешной отправки пользователь должен перенаправляться на страницу списка жанров (genres:list).
Сделайте форму добавления режиссёра (Director) через FormView.
Форма должна отображать только поле name.
В шаблон нужно передать дополнительный контекст:
title = "Добавление режиссёра"
Создайте форму добавления актёра (Actor) и модифицируйте form_valid() так, чтобы перед сохранением поле name автоматически приводилось к title-case (str.title()).
forms.py
class GenreCreateForm(forms.ModelForm):
class Meta:
model = Genre
fields = ['name', 'slug']views.py
class GenreCreateView(FormView):
template_name = 'genres/create_genre.html'
form_class = GenreCreateForm
success_url = reverse_lazy('genres:list')
def form_valid(self, form):
form.save()
return super().form_valid(form)urls.py
path('add/', GenreCreateView.as_view(), name='add'),class DirectorCreateView(FormView):
template_name = 'directors/create_director.html'
form_class = DirectorCreateForm
success_url = reverse_lazy('directors:list')
extra_context = {'title': 'Добавление режиссёра'}
def form_valid(self, form):
form.save()
return super().form_valid(form)def form_valid(self, form):
actor = form.save(commit=False)
actor.name = actor.name.title()
actor.save()
return super().form_valid(form)- Для чего предназначен класс
FormView? - Почему нельзя использовать
reverse()вместоreverse_lazy()при определенииsuccess_urlв классе? - Какой метод нужно переопределить, чтобы форма сохраняла данные?
- Как передать дополнительные переменные в шаблон без переопределения
get_context_data()? - Как Django передает форму в контекст шаблона? Под каким именем?
- Что произойдет, если в шаблоне обратиться не к
form, а, например, кmovie_form? - Можно ли использовать FormView с обычной
forms.Form, а не ModelForm? - Что делает метод
form_valid()? - Что произойдет при невалидной форме?
- Какие ошибки чаще всего возникают при работе с FormView и как их исправлять?