Итоги работы
Итоговый код API
blog/api/serializers.py:
from rest_framework import serializers
from ..models import Comment, Post
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ["id", "post", "name", "body", "created"]
read_only_fields = ["created"]
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source="author.username")
tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
comments_count = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ["id", "title", "slug", "author", "body", "publish", "status", "tags", "comments_count"]
def get_comments_count(self, obj):
return obj.comments.count()
blog/api/views.py:
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters, permissions, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from ..models import Comment, Post
from .permissions import IsAuthorOrReadOnly
from .serializers import CommentSerializer, PostSerializer
class PostViewSet(viewsets.ModelViewSet):
"""Посты блога. Читать — всем, создавать — вошедшим, изменять — автору."""
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ["status", "author"]
search_fields = ["title", "body"]
ordering_fields = ["publish", "title"]
def get_queryset(self):
qs = Post.objects.select_related("author").prefetch_related("tags")
if self.action in ("list", "retrieve") and not self.request.user.is_staff:
qs = qs.filter(status=Post.Status.PUBLISHED)
return qs
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=["post"])
def publish(self, request, pk=None):
post = self.get_object()
post.status = Post.Status.PUBLISHED
post.save(update_fields=["status"])
return Response({"status": "published"})
class CommentViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Comment.objects.filter(active=True)
serializer_class = CommentSerializer
filterset_fields = ["post"]
blog/api/urls.py:
from django.urls import include, path
from rest_framework.authtoken.views import obtain_auth_token
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register("posts", views.PostViewSet, basename="post")
router.register("comments", views.CommentViewSet)
urlpatterns = [
path("", include(router.urls)),
path("token/", obtain_auth_token),
]
Обратите внимание на basename="post": у PostViewSet нет атрибута queryset (только get_queryset()), поэтому роутеру нужно явно указать базовое имя маршрутов.
settings.py:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticatedOrReadOnly"],
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 10,
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.AnonRateThrottle"],
"DEFAULT_THROTTLE_RATES": {"anon": "1000/day"},
}
Чек-лист
- Понимаю принципы REST и коды ответов.
- Умею писать
ModelSerializerсо связями, вычисляемыми полями и валидацией. - Знаю разницу между
APIView, generic views иViewSet. - Умею добавить фильтры, поиск, сортировку и пагинацию.
- Понимаю, как работают права и токены, и могу написать своё право.
- Умею сгенерировать документацию.
Что дальше
Блог и его API готовы — пора показать их миру. Следующий модуль — деплой на VPS: сервер, домен, Gunicorn, PostgreSQL, NGINX и HTTPS.
Практика решено 0 из 5
Запрос PATCH /api/posts/5/ с токеном. Расставьте этапы его обработки в DRF.
-
Классы аутентификации определяют
request.userпо токену -
get_object()находит пост и проверяетhas_object_permission -
Сериализатор валидирует
request.dataи сохраняет изменения -
Проверяются
permission_classes(сначалаhas_permission) -
Возвращается
Responseс обновлённым постом и кодом 200 -
Роутер направляет запрос в
PostViewSet.partial_update
Анонимный пользователь отправил DELETE /api/posts/5/ при правах IsAuthenticatedOrReadOnly и сессионной аутентификации. Что он получит?
Контрольное задание: CommentViewSet только для чтения (ReadOnlyModelViewSet)
с набором активных комментариев Comment.objects.filter(active=True), сериализатором CommentSerializer
и фильтрацией по полю post через filterset_fields.
from rest_framework import viewsets
class CommentViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Comment.objects.filter(active=True)
serializer_class = CommentSerializer
filterset_fields = ["post"]
Какие пакеты мы использовали в этом модуле?
Соберите API блога: откройте /api/ (корень роутера), создайте пост с токеном через curl, отфильтруйте посты ?search=, проверьте пагинацию и откройте /api/docs/. Нажмите, когда всё заработает.
Войдите, чтобы отмечать пройденные уроки.