💻 Примеры — Урок 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/