Представления
Уровни абстракции
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
Напишите функцию-представление post_list с декоратором @api_view(["GET"]),
которая возвращает Response с сериализованным списком опубликованных постов (many=True).
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(["GET"])
def post_list(request):
posts = Post.published.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
Напишите PostViewSet на основе viewsets.ModelViewSet: набор — Post.objects.all(),
сериализатор — PostSerializer, а в perform_create(self, serializer) сохраняйте автора self.request.user.
from rest_framework import viewsets
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Зарегистрируйте PostViewSet в роутере DefaultRouter с префиксом posts
и подключите маршруты роутера в urlpatterns.
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register("posts", views.PostViewSet)
urlpatterns = [
path("", include(router.urls)),
]
Расставьте классы DRF от самого низкоуровневого (больше кода) к самому высокоуровневому (меньше кода).
-
generics.ListCreateAPIView -
@api_view(функция) -
viewsets.ModelViewSet+ роутер -
APIView
Какие действия (actions) автоматически предоставляет ModelViewSet?
Как добавить во ViewSet собственный endpoint POST /api/posts/5/publish/?
Войдите, чтобы отмечать пройденные уроки.