APIView, generics и ViewSet
Три уровня абстракции для эндпоинтов DRF — от ручного APIView до ViewSet с автоматической маршрутизацией.
Вьюха DRF — это объект, который принимает
request, выбирает сериализатор и набор данных и возвращаетResponse. DRF предлагает три уровня:APIView(всё руками), generic-вьюхи (типовые операции готовы) иViewSet(несколько операций в одном классе + роутер).
Зачем это знать на практике
Выбор уровня абстракции определяет, сколько кода вы напишете и сколько потом будете поддерживать. Возьмёте слишком низкий уровень — утонете в одинаковых get/post для каждого ресурса. Возьмёте слишком высокий, не понимая его, — не сможете вставить нестандартную логику и будете воевать с фреймворком. Поэтому полезно знать все три уровня и осознанно спускаться или подниматься по лестнице абстракции под конкретную задачу.
APIView: полный контроль
APIView — это DRF-надстройка над Django-View: методы называются по HTTP-глаголам, на вход даётся разобранный request.data, на выход — Response (DRF сам отрендерит его в JSON).
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class ArticleList(APIView):
def get(self, request):
articles = Article.objects.all()
data = ArticleSerializer(articles, many=True).data
return Response(data)
def post(self, request):
serializer = ArticleSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Видно, что для каждого ресурса этот код почти одинаков. Именно эту рутину и устраняют следующие уровни.
Generic-вьюхи: типовые операции готовы
Generic-вьюхи закрывают стандартный CRUD. Достаточно указать queryset и serializer_class — а нужное поведение даёт выбор базового класса:
from rest_framework import generics
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
Две короткие вьюхи дают полноценный API: список + создание и получение + изменение + удаление одной записи. Имена классов читаются как список операций:
| Класс | HTTP | Операция |
ListCreateAPIView | GET, POST | список, создание |
RetrieveUpdateDestroyAPIView | GET, PUT/PATCH, DELETE | чтение, обновление, удаление одной записи |
ListAPIView | GET | только список (read-only) |
Когда нужна нестандартная логика, переопределяют хук, а не весь метод. Например, подставить текущего пользователя в автора при создании:
class ArticleList(generics.ListCreateAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
ViewSet и роутеры
Даже две generic-вьюхи на ресурс повторяют queryset и serializer_class. ModelViewSet собирает весь CRUD в один класс:
from rest_framework import viewsets
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
В ViewSet методы называются по действиям, а не по HTTP-глаголам: list, create, retrieve, update, partial_update, destroy. ModelViewSet реализует их все. Маршруты для них генерирует роутер — не нужно вручную прописывать каждый URL:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r"articles", ArticleViewSet)
urlpatterns = router.urls
# создаст:
# GET/POST /articles/
# GET/PUT/PATCH/DEL /articles/{pk}/
Своё действие добавляют декоратором @action — роутер сам сделает для него URL:
from rest_framework.decorators import action
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
@action(detail=True, methods=["post"])
def publish(self, request, pk=None):
article = self.get_object()
article.is_published = True
article.save()
return Response({"status": "опубликовано"})
# даст POST /articles/{pk}/publish/
Что выбрать
- APIView — когда логика нетиповая: эндпоинт не вокруг одной модели, своя обработка, сторонний сервис.
- Generic-вьюхи — когда нужен обычный CRUD, но хочется явно видеть, какие операции у ресурса, и точечно настраивать каждую.
- ViewSet + роутер — когда ресурсов много и они однотипные: меньше всего кода, единые URL по соглашению.
Полезно помнить: это одна лестница. ViewSet построен на generic-миксинах, а те — на APIView. Поэтому переопределять хуки (get_queryset, perform_create) можно на любом уровне одинаково.
Как это работает под капотом
В основе всего лежит APIView.dispatch(). Он оборачивает Django-request в DRF-Request (с .data и согласованием контента), запускает аутентификацию, проверку прав и throttling, а затем по HTTP-методу вызывает нужный обработчик. Generic-вьюхи добавляют миксины (ListModelMixin, CreateModelMixin и т.д.): каждый миксин — это готовый метод вроде list(), опирающийся на get_queryset() и get_serializer(). ViewSet отличается тем, что не привязывает методы к HTTP-глаголам сразу: роутер при регистрации вызывает as_view({"get": "list", "post": "create"}) и так строит соответствие «URL + метод → действие». Поэтому один и тот же ViewSet обслуживает и список, и деталь — роутер просто создаёт две точки входа с разными отображениями.
Частые ошибки
- Возвращать обычный Django
JsonResponseиз APIView. Теряется согласование контента и DRF-рендеринг; нуженrest_framework.response.Response. - Переопределять весь метод ради мелочи. Чтобы подставить пользователя или отфильтровать выборку, есть хуки
perform_create()иget_queryset(). - Прописывать URL для ViewSet руками. Без роутера придётся вручную раскладывать действия по методам — теряется весь смысл ViewSet.
- Путать действия и HTTP-глаголы. В ViewSet метод называется
create, а неpost; в APIView — наоборот,post. - Забыть
basenameприrouter.registerбезqueryset. Еслиquerysetне задан атрибутом, роутер не выведет имя маршрута и упадёт.
Итоги
APIView— методы по HTTP-глаголам, максимум контроля, максимум ручного кода.- Generic-вьюхи (
ListCreateAPIViewи др.) дают CRUD поqueryset+serializer_class; тонкая настройка — через хуки. ModelViewSetобъединяет все действия в один класс; URL генерирует роутер.- Свои действия в ViewSet добавляют декоратором
@action. - Все три уровня — одна лестница абстракции на базе
APIView; хуки переопределяются одинаково.