Представления

⏱ 30 мин · 🧩 заданий: 6

Уровни абстракции

DRF предлагает несколько способов писать представления — от явного к компактному:

Способ Когда удобен
@api_view Простой нестандартный endpoint
APIView Класс с методами get/post/..., полный контроль
Generic views Типовой CRUD для одной модели
ViewSet + роутер Весь CRUD ресурса одним классом

@api_view

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response


@api_view(["GET", "POST"])
def post_list(request):
    if request.method == "GET":
        serializer = PostSerializer(Post.published.all(), many=True)
        return Response(serializer.data)

    serializer = PostSerializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(author=request.user)
    return Response(serializer.data, status=status.HTTP_201_CREATED)

APIView

from django.shortcuts import get_object_or_404
from rest_framework.views import APIView


class PostDetail(APIView):
    def get(self, request, pk):
        post = get_object_or_404(Post, pk=pk)
        return Response(PostSerializer(post).data)

    def patch(self, request, pk):
        post = get_object_or_404(Post, pk=pk)
        serializer = PostSerializer(post, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    def delete(self, request, pk):
        get_object_or_404(Post, pk=pk).delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Generic views

Готовые классы для типовых операций:

Класс Методы
ListAPIView GET список
CreateAPIView POST
RetrieveAPIView GET один
UpdateAPIView PUT, PATCH
DestroyAPIView DELETE
ListCreateAPIView GET список + POST
RetrieveUpdateDestroyAPIView GET + PUT + PATCH + DELETE
class PostListCreate(generics.ListCreateAPIView):
    queryset = Post.published.select_related("author")
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)


class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

Точки расширения: get_queryset(), get_serializer_class(), perform_create(), perform_update(), perform_destroy().

ViewSet и роутеры

ViewSet объединяет все действия ресурса в одном классе:

from rest_framework import viewsets


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.select_related("author")
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

Роутер сам строит маршруты:

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register("posts", views.PostViewSet)
router.register("comments", views.CommentViewSet)

urlpatterns = [
    path("", include(router.urls)),
]
Адрес Метод Действие ViewSet
/api/posts/ GET list
/api/posts/ POST create
/api/posts/5/ GET retrieve
/api/posts/5/ PUT / PATCH update / partial_update
/api/posts/5/ DELETE destroy

DefaultRouter также создаёт корневую страницу /api/ со списком ресурсов.

Если нужно только чтение — ReadOnlyModelViewSet (list и retrieve).

Разные сериализаторы для действий

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()

    def get_serializer_class(self):
        if self.action == "list":
            return PostListSerializer
        return PostDetailSerializer

Свои действия: @action

from rest_framework.decorators import action


class PostViewSet(viewsets.ModelViewSet):
    ...

    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        post = self.get_object()
        post.status = Post.Status.PUBLISHED
        post.save()
        return Response({"status": "published"})

    @action(detail=False)
    def drafts(self, request):
        drafts = self.get_queryset().filter(status=Post.Status.DRAFT)
        return Response(self.get_serializer(drafts, many=True).data)

Роутер добавит POST /api/posts/5/publish/ и GET /api/posts/drafts/.

Тестирование API

from rest_framework.test import APITestCase


class PostApiTests(APITestCase):
    def test_list(self):
        resp = self.client.get("/api/posts/")
        self.assertEqual(resp.status_code, 200)

    def test_create_requires_auth(self):
        resp = self.client.post("/api/posts/", {"title": "X", "body": "Y"}, format="json")
        self.assertEqual(resp.status_code, 403)

Итог

  • @api_view и APIView — максимум контроля, generic views — типовой CRUD, ModelViewSet — весь ресурс одним классом.
  • perform_create() — место, чтобы дописать автора и другие поля.
  • Роутер (DefaultRouter) генерирует маршруты для ViewSet.
  • @action добавляет собственные endpoints.

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Напишите функцию-представление post_list с декоратором @api_view(["GET"]), которая возвращает Response с сериализованным списком опубликованных постов (many=True).

Задание 2 Написать код

Напишите PostViewSet на основе viewsets.ModelViewSet: набор — Post.objects.all(), сериализатор — PostSerializer, а в perform_create(self, serializer) сохраняйте автора self.request.user.

Задание 3 Написать код

Зарегистрируйте PostViewSet в роутере DefaultRouter с префиксом posts и подключите маршруты роутера в urlpatterns.

Задание 4 Расставить по порядку

Расставьте классы DRF от самого низкоуровневого (больше кода) к самому высокоуровневому (меньше кода).

  1. generics.ListCreateAPIView
  2. @api_view (функция)
  3. viewsets.ModelViewSet + роутер
  4. APIView
Задание 5 Несколько вариантов

Какие действия (actions) автоматически предоставляет ModelViewSet?

Задание 6 Один вариант ответа

Как добавить во ViewSet собственный endpoint POST /api/posts/5/publish/?

Войдите, чтобы отмечать пройденные уроки.