💻 Примеры — Урок 29

← К оглавлению урока

⚡ Ключевые примеры

# StringRelatedField — read-only через __str__
publisher = serializers.StringRelatedField()

# SlugRelatedField — по slug
publisher = serializers.SlugRelatedField(slug_field='slug', queryset=Publisher.objects.all())

# PrimaryKeyRelatedField — по ID (FK + M2M)
publisher = serializers.PrimaryKeyRelatedField(queryset=Publisher.objects.all())
genres = serializers.PrimaryKeyRelatedField(queryset=Genre.objects.all(), many=True)

# APIView CRUD
class BookView(APIView):
    def get(self, request): return Response(BookSerializer(Book.objects.all(), many=True).data)
    def post(self, request):
        s = BookSerializer(data=request.data)
        return Response(s.data, status=201) if s.is_valid(raise_exception=True) and s.save() else ...

Пример 1: Полные модели Book, Publisher, Genre

# library/models.py
from django.db import models

class Publisher(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)
    established_date = models.DateField()

    def __str__(self):
        return self.name


class Genre(models.Model):
    name = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    published_date = models.DateField()
    publisher = models.ForeignKey(
        Publisher, on_delete=models.CASCADE, null=True, blank=True
    )
    created_at = models.DateTimeField(null=True, blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    discounted_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    is_bestseller = models.BooleanField(default=False)
    genres = models.ManyToManyField(Genre, related_name='books', blank=True)

    def __str__(self):
        return self.title

Пример 5: Полный CRUD через APIView

# library/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Book, Genre
from .serializers import BookSerializer, GenreSerializer


class BookListCreateView(APIView):
    """GET /api/books/ — список  |  POST /api/books/ — создание"""

    def get(self, request):
        books = Book.objects.select_related('publisher').prefetch_related('genres').all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class BookDetailUpdateDeleteView(APIView):
    """GET/PUT/DELETE /api/books/{pk}/"""

    def get_object(self, pk):
        try:
            return Book.objects.get(pk=pk)
        except Book.DoesNotExist:
            return None

    def get(self, request, pk):
        book = self.get_object(pk)
        if book is None:
            return Response({'error': 'Book not found'}, status=status.HTTP_404_NOT_FOUND)
        serializer = BookSerializer(book)
        return Response(serializer.data)

    def put(self, request, pk):
        book = self.get_object(pk)
        if book is None:
            return Response({'error': 'Book not found'}, status=status.HTTP_404_NOT_FOUND)
        serializer = BookSerializer(book, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        book = self.get_object(pk)
        if book is None:
            return Response({'error': 'Book not found'}, status=status.HTTP_404_NOT_FOUND)
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


class GenreCreateView(APIView):
    """POST /api/genres/ — создание жанра"""

    def post(self, request):
        serializer = GenreSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# library/urls.py
from django.urls import path
from .views import BookListCreateView, BookDetailUpdateDeleteView, GenreCreateView

urlpatterns = [
    path('books/', BookListCreateView.as_view(), name='book-list-create'),
    path('books/<int:pk>/', BookDetailUpdateDeleteView.as_view(), name='book-detail'),
    path('genres/', GenreCreateView.as_view(), name='genre-create'),
]

Пример 6: Те же эндпоинты через Generic Views

Сравните с Примером 5 — тот же результат, значительно меньше кода:

# library/views_generic.py
from rest_framework import generics
from .models import Book
from .serializers import BookSerializer

# Заменяет BookListCreateView
class BookListCreateView(generics.ListCreateAPIView):
    queryset = Book.objects.select_related('publisher').prefetch_related('genres').all()
    serializer_class = BookSerializer

# Заменяет BookDetailUpdateDeleteView
class BookDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
Вывод: Generic Views подходят для стандартных сценариев CRUD. APIView нужен, когда требуется нестандартная логика (агрегация, проверки доступа, условные ответы).

Пример 7: ModelViewSet с Router

# library/viewsets.py
from rest_framework.viewsets import ModelViewSet
from .models import Book, Genre
from .serializers import BookSerializer, GenreSerializer

class BookViewSet(ModelViewSet):
    """Полный CRUD: GET/POST /books/, GET/PUT/PATCH/DELETE /books/{pk}/"""
    queryset = Book.objects.select_related('publisher').prefetch_related('genres').all()
    serializer_class = BookSerializer

class GenreViewSet(ModelViewSet):
    queryset = Genre.objects.all()
    serializer_class = GenreSerializer
# library/urls.py (с Router)
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .viewsets import BookViewSet, GenreViewSet

router = DefaultRouter()
router.register(r'books', BookViewSet)
router.register(r'genres', GenreViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Router автоматически создаёт следующие URL:

GET    /api/books/          — список книг
POST   /api/books/          — создание книги
GET    /api/books/{pk}/     — одна книга
PUT    /api/books/{pk}/     — полное обновление
PATCH  /api/books/{pk}/     — частичное обновление
DELETE /api/books/{pk}/     — удаление книги
# Аналогично для /api/genres/