В предыдущих модулях мы работали с представлениями, написанными в виде функций (FBV — Function-Based Views). Такой подход прост и хорошо подходит для небольших задач, однако в реальных проектах, особенно таких как cinemahub, функциональные представления очень быстро начинают «расти» и усложняться:
- в них накапливается логика обработки разных типов запросов:
GET,POST,PUT,DELETE; - появляются повторяющиеся фрагменты кода, которые сложно поддерживать;
- одни и те же элементы интерфейса (например, формы или фильтры) приходится подключать вручную в каждом представлении.
Чтобы снизить количество дублирования кода и сделать архитектуру более предсказуемой, Django предлагает альтернативу — классы представлений (CBV, Class-Based Views).
Использование CBV позволяет:
- представлять каждое представление как отдельный класс со строгой структурой;
- переопределять только нужные методы;
- применять наследование и
mixins, тем самым уменьшая повторяемость кода; - улучшить читаемость проекта и упростить его поддержку.
В этом уроке мы познакомимся с основой всей системы CBV — классом View, а также рассмотрим самый простой, но очень полезный класс TemplateView.
Классическое функциональное представление выглядит так:
def movie_list(request):
...Мы сами внутри функции определяем, что делать при GET, POST и других типах запросов.
В CBV та же логика разделяется на методы класса:
- get() — обработка GET-запроса
- post() — обработка POST-запроса
- put() — обработка PUT-запроса
- delete() — обработка DELETE-запроса
Django автоматически перенаправляет запрос в нужный метод, если он определён.
Таким образом структура становится предсказуемой: всё, что касается GET — в одном методе, всё, что касается POST — в другом.
Создадим пример класса, который будет выводить форму добавления фильма (в учебных целях мы создадим временную форму).
Файл: movies/forms.py
from django import forms
class AddMovieForm(forms.Form):
title = forms.CharField(max_length=255)
year = forms.IntegerField(min_value=1895, max_value=2050)Эта форма пока не связана с моделью. Модель Movie мы подключим позже, в следующих уроках.
Файл: movies/views.py
from django.views import View
from django.shortcuts import render, redirect
from .forms import AddMovieForm
class AddMovieView(View):
def get(self, request):
form = AddMovieForm()
return render(request, 'movies/add_movie.html', {
'title': 'Добавление фильма',
'form': form,
})
def post(self, request):
form = AddMovieForm(request.POST)
if form.is_valid():
# В этом уроке мы не сохраняем данные в БД, чтобы не отвлекаться
print("Форма прошла валидацию:", form.cleaned_data)
return redirect('movies:add_movie')
return render(request, 'movies/add_movie.html', {
'title': 'Добавление фильма',
'form': form,
})- При GET-запросе (например, при открытии страницы в браузере) вызывается
get(). - При POST (например, при отправке формы) вызывается
post(). - Логика обработки чётко разделена.
- Шаблон один и тот же, но поведение различается.
Файл: movies/urls.py
from django.urls import path
from .views import AddMovieView
app_name = 'movies'
urlpatterns = [
path('add/', AddMovieView.as_view(), name='add_movie'),
]Здесь важно: во всех CBV вызывается метод as_view().
Он создаёт функцию, которую Django может использовать как представление.
Файл: movies/templates/movies/add_movie.html
<h1>{{ title }}</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Сохранить</button>
</form>После запуска сервера:
- открываем в браузере:
http://127.0.0.1:8000/movies/add/
Ожидаем:
- Отобразится форма с полями
titleиyear. - При отправке формы без значений появится сообщение об ошибке.
- Если значения корректные, сервер выведет их в консоли и перенаправит обратно.
Если вы видите ошибку типа:
- TemplateDoesNotExist — неверный путь к шаблону
- ImportError — неправильно указали путь в urls.py
- AttributeError: type object 'AddMovieView' has no attribute 'as_view' — забыли вызвать
as_view()в маршруте
TemplateView используется, когда:
- нужно вывести статическую страницу (например, "О проекте"),
- передать в шаблон фиксированные данные,
- логика представления минимальна.
В отличие от View, TemplateView уже реализует метод get() и возвращает готовый рендер.
Предположим, на главной странице мы хотим показать приветствие и список последних фильмов.
Файл: movies/views.py
from django.views.generic import TemplateView
from .models import Movie
class MoviesHomeView(TemplateView):
template_name = 'movies/home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'Cinemahub — каталог фильмов'
context['last_movies'] = Movie.objects.filter(is_published=True).order_by('-created_at')[:5]
return contexttemplate_name— путь к шаблону.get_context_data()— метод, в который мы добавляем динамические данные.
В этот метод:
- всегда нужно вызывать
super(), - всегда возвращать
context.
Файл: movies/urls.py
from .views import MoviesHomeView
urlpatterns = [
path('', MoviesHomeView.as_view(), name='home'),
]Файл: movies/templates/movies/home.html
<h1>{{ title }}</h1>
<h2>Последние добавленные фильмы</h2>
<ul>
{% for movie in last_movies %}
<li>{{ movie.title }} ({{ movie.year }})</li>
{% empty %}
<li>Фильмов пока нет.</li>
{% endfor %}
</ul>Открываем:
http://127.0.0.1:8000/movies/
Ожидаем увидеть:
- заголовок,
- список последних пяти опубликованных фильмов.
Если получаете ObjectDoesNotExist, то, возможно, у моделей нет данных или неправильно указаны имена полей.
TypeError: AddMovieView() missing 1 required positional argument: 'request'
Решение:
в urls.py всегда нужно писать:
AddMovieView.as_view()TemplateDoesNotExist: movies/home.html
Проверьте:
- правильность пути,
- имя папки
templates/movies/, - регистр имени файла.
Например, забыли добавить title в context.
Исправление:
добавить в get_context_data().
Если видна ошибка:
ImportError: cannot import name MoviesHomeView
Проверьте:
- правильный путь в import,
- что класс находится в
movies/views.py, - что файл сохранён.
Ниже — задания, которые требуют применения CBV, но не копируют примеры из урока. Решения приведены после заданий.
Создайте CBV, который:
-
отображает форму обратной связи с полями:
- message
-
при GET показывает пустую форму;
-
при POST выводит очищенные данные (например, используя
print()) и возвращает ту же страницу.
Используйте:
- класс
View, - форму
forms.Form, - шаблон по аналогии с уроком.
Создайте TemplateView, который:
- отображает страницу «О режиссёрах»;
- в контекст добавляет список всех режиссёров (модель
Director); - выводит их имена в шаблоне.
Создайте CBV, который:
- принимает GET-параметр
q; - выводит список фильмов, в названии которых встречается эта строка;
- если параметр отсутствует, список пустой.
Использовать только класс View.
Файл: movies/forms.py
from django import forms
class ContactForm(forms.Form):
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)Файл: movies/views.py
from django.views import View
from django.shortcuts import render
from .forms import ContactForm
class ContactView(View):
def get(self, request):
form = ContactForm()
return render(request, 'movies/contact.html', {'form': form})
def post(self, request):
form = ContactForm(request.POST)
if form.is_valid():
print(form.cleaned_data)
return render(request, 'movies/contact.html', {'form': form})Шаблон: movies/templates/movies/contact.html
<h1>Обратная связь</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Отправить</button>
</form>Файл: movies/views.py
from django.views.generic import TemplateView
from .models import Director
class DirectorsInfoView(TemplateView):
template_name = 'movies/directors_info.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['directors'] = Director.objects.all()
return contextШаблон: movies/templates/movies/directors_info.html
<h1>Список режиссёров</h1>
<ul>
{% for director in directors %}
<li>{{ director.name }}</li>
{% empty %}
<li>Пока нет режиссёров.</li>
{% endfor %}
</ul>Файл: movies/views.py
from django.views import View
from django.shortcuts import render
from .models import Movie
class MovieSearchView(View):
def get(self, request):
query = request.GET.get('q')
movies = Movie.objects.filter(title__icontains=query) if query else []
return render(request, 'movies/search.html', {
'query': query,
'movies': movies,
})Шаблон: movies/templates/movies/search.html
<h1>Результаты поиска</h1>
{% if query %}
<p>Вы искали: "{{ query }}"</p>
{% else %}
<p>Введите строку поиска.</p>
{% endif %}
<ul>
{% for movie in movies %}
<li>{{ movie.title }}</li>
{% empty %}
{% if query %}
<li>Ничего не найдено.</li>
{% endif %}
{% endfor %}
</ul>- Почему CBV лучше подходят для крупных проектов, чем FBV?
- Какие методы обрабатывают GET- и POST-запросы в классе View?
- Для чего нужен метод
as_view()? - Чем TemplateView отличается от View?
- В каком методе TemplateView добавляют данные в шаблон?
- Что произойдёт, если забыть вызвать
super().get_context_data()? - Что делает
contextв CBV и почему важно вернуть именно его? - Какие ошибки чаще всего возникают при работе с CBV и как их исправить?
- Можно ли использовать TemplateView для обработки POST? Почему?
- Как передать в CBV форму и обработать её валидность?