Function-based vs Class-based views, mixins, decorators
Представление принимает HttpRequest и возвращает HttpResponse либо совместимый объект ответа. Django предлагает две формы записи: функцию (function-based view, FBV) и класс (class-based view, CBV). Это не два разных уровня возможностей — выбор определяет структуру кода.
Материал актуален для Django 5.2 LTS и 6.0.
Простейшая FBV выглядит так:
from django.http import HttpResponse
from django.views.decorators.http import require_GET
@require_GET
def health(request):
return HttpResponse("ok", content_type="text/plain")Эквивалентная CBV наследуется от View и объявляет обработчик HTTP-метода:
from django.http import HttpResponse
from django.views import View
class HealthView(View):
http_method_names = ["get", "head", "options"]
def get(self, request):
return HttpResponse("ok", content_type="text/plain")В URLconf функция передаётся напрямую, а класс преобразуется в вызываемую view-функцию через as_view():
from django.urls import path
urlpatterns = [
path("health/", health, name="health"),
path("health-cbv/", HealthView.as_view(), name="health_cbv"),
]as_view() создаёт новый экземпляр класса для каждого запроса, вызывает setup(), затем dispatch(). Настройки класса можно передать в as_view(), но это общая конфигурация, а не место для изменяемого состояния запроса.
FBV хорошо подходит короткому сценарию, который читается сверху вниз. Например, форма обратной связи имеет две явные ветви:
from django.contrib import messages
from django.shortcuts import redirect, render
from django.views.decorators.http import require_http_methods
from .forms import FeedbackForm
@require_http_methods(["GET", "POST"])
def feedback(request):
form = FeedbackForm(request.POST or None)
if request.method == "POST" and form.is_valid():
form.save()
messages.success(request, "Сообщение принято")
return redirect("feedback_done")
return render(request, "feedback/form.html", {"form": form})Здесь форма отвечает за преобразование и валидацию ввода, именованный URL — за перенаправление, а схема POST/redirect/GET защищает от повторной отправки при обновлении страницы.
Не создавайте модель напрямую из request.POST.get() только потому, что пример должен быть коротким. Тогда исчезают типовая валидация, нормальные сообщения об ошибках и явный список разрешённых полей.
CBV удобна, если поведение естественно разделяется по HTTP-методам или собирается из стандартных mixin. Та же форма на базовом View:
from django.contrib import messages
from django.shortcuts import redirect, render
from django.views import View
from .forms import FeedbackForm
class FeedbackView(View):
template_name = "feedback/form.html"
def get(self, request):
return render(request, self.template_name, {"form": FeedbackForm()})
def post(self, request):
form = FeedbackForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Сообщение принято")
return redirect("feedback_done")
return render(request, self.template_name, {"form": form})Этот вариант длиннее, поэтому для одного простого сценария CBV не даёт автоматического выигрыша. Его преимущество проявится, если проект уже использует общие mixin или generic views.
dispatch()dispatch() смотрит на request.method и вызывает get(), post(), put() и т. д. Если метод не разрешён или обработчик отсутствует, базовый View возвращает 405 Method Not Allowed и корректный заголовок Allow.
Если head() не объявлен, базовый View использует get() как обработчик HEAD, а сервер не отправляет тело ответа. Не задавайте выдуманный Content-Length: он должен соответствовать реальному представлению ресурса.
OPTIONS также обрабатывается базовым классом. Атрибут http_method_names позволяет сузить поддерживаемый набор, но безопасность записи всё равно требует авторизации, CSRF-защиты и валидации данных.
request.POST заполняется для отправленных form-данных, но не является универсальным парсером JSON и PUT. Для полноценного API удобнее Django REST Framework; ручной endpoint должен явно разобрать content type, ограничить размер тела, обработать ошибочный JSON и вернуть корректные статусы.
dispatch()Общую логику можно поставить до и после обработчика:
import logging
from time import monotonic
from django.views import View
logger = logging.getLogger(__name__)
class TimedView(View):
def dispatch(self, request, *args, **kwargs):
started_at = monotonic()
try:
return super().dispatch(request, *args, **kwargs)
finally:
logger.info(
"view_finished",
extra={
"method": request.method,
"path": request.path,
"duration_seconds": monotonic() - started_at,
},
)Ключевое слово — super(): без него цепочка наследования не дойдёт до следующего mixin и обработчика метода. Для авторизации лучше взять готовые mixin Django, а для сквозного поведения всего проекта — рассмотреть middleware. Логирование в каждом классе через собственный mixin быстро превращается в дублирование.
Mixin добавляет узкую часть поведения. Встроенные auth-mixin располагают слева от самой view:
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
class ArticleUpdateView(
LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView,
):
permission_required = "articles.change_article"
raise_exception = True
model = Article
form_class = ArticleForm
template_name = "articles/article_form.html"
def get_queryset(self):
queryset = super().get_queryset()
if self.request.user.is_staff:
return queryset
return queryset.filter(author=self.request.user)Проверка права модели и ограничение доступных объектов решают разные задачи. Одного change_article недостаточно, если обычный автор должен редактировать только свои статьи. Ограничение в get_queryset() также не раскрывает существование чужого объекта: для него получится 404.
Python строит MRO слева направо по алгоритму C3. Кооперативный mixin принимает *args, **kwargs и вызывает super(). Если один компонент оборвёт цепочку, mixin справа может не выполниться. При неясности посмотрите ArticleUpdateView.__mro__ и исходный код generic view.
Функциональный декоратор адаптируют через method_decorator:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
@method_decorator(never_cache, name="dispatch")
class PrivateDashboardView(LoginRequiredMixin, View):
def get(self, request):
return render(request, "dashboard/private.html")Декорирование dispatch охватывает все HTTP-методы. Декорирование только get не защитит post. Порядок нескольких декораторов влияет на порядок входа и выхода, поэтому длинную цепочку лучше заменить понятным mixin или вынести сквозную политику в middleware.
Для небольшого служебного endpoint достаточно JsonResponse:
from django.http import JsonResponse
from django.views import View
class ArticleSummaryView(View):
def get(self, request, pk):
article = get_object_or_404(
Article.objects.filter(status=Article.Status.PUBLISHED),
pk=pk,
)
return JsonResponse({
"id": article.pk,
"title": article.title,
})Фильтр опубликованных статей применяется и к списку, и к детали; иначе URL детали может раскрыть черновик. JsonResponse сам сериализует словарь и выставляет content type. Авторизация и владение должны проверяться на каждом изменяющем endpoint, а не только в интерфейсе.
Выберите форму, в которой политика приложения видна без прыжков по пяти родительским классам:
Это рекомендации, а не запреты. Переписывание рабочей FBV в CBV само по себе не улучшает архитектуру.
FBV, View, as_view(), dispatch() и generic CBV остаются актуальными в Django 5.2/6.0. Legacy-проекты часто содержат собственные JSON-mixin, ручные проверки request.user и жёстко заданные URL. Их не нужно механически удалять: сначала добавьте тесты, затем заменяйте на JsonResponse, auth-mixin и reverse()/redirect() с именованными маршрутами.
Самодельная CBV, где post() заканчивается pass, не является заготовкой безопасного API: обработчик вернёт None, и Django завершит запрос ошибкой. Даже учебный листинг должен либо возвращать ответ, либо явно быть помечен как неполный псевдокод.
Далее: Generic Class-Based Views