🔖 Справочник — Урок 29

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

⚡ Быстрый справочник

# Поля для связей (serializers.py)
publisher = serializers.StringRelatedField()             # __str__, read-only
publisher = serializers.SlugRelatedField(               # по slug-полю
    slug_field='slug', queryset=Publisher.objects.all())
publisher = serializers.PrimaryKeyRelatedField(         # по ID (FK)
    queryset=Publisher.objects.all())
genres = serializers.PrimaryKeyRelatedField(            # M2M
    queryset=Genre.objects.all(), many=True)

# APIView (views.py)
from rest_framework.views import APIView
class MyView(APIView):
    def get(self, request): ...
    def post(self, request): ...
    def put(self, request, pk): ...
    def delete(self, request, pk): ...

# URLs
path('items/', MyView.as_view(), name='items')

# Generic Views
from rest_framework.generics import ListCreateAPIView
class BookList(ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

# ViewSet + Router
from rest_framework.viewsets import ModelViewSet
from rest_framework.routers import DefaultRouter
class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
router = DefaultRouter()
router.register(r'books', BookViewSet)
urlpatterns = router.urls

Поля сериализатора для связей

ПолеСинтаксисЗаписьВозвращает
StringRelatedField serializers.StringRelatedField() Нет str(obj)
SlugRelatedField serializers.SlugRelatedField(slug_field='поле', queryset=...) По slug Значение slug-поля
PrimaryKeyRelatedField serializers.PrimaryKeyRelatedField(queryset=...) По ID Integer (PK)
M2M через PK serializers.PrimaryKeyRelatedField(queryset=..., many=True) Список ID Список Integer

StringRelatedField

from rest_framework import serializers

class BookSerializer(serializers.ModelSerializer):
    # Отображает Publisher.__str__() — только чтение
    publisher = serializers.StringRelatedField()

    class Meta:
        model = Book
        fields = '__all__'

SlugRelatedField

class BookSerializer(serializers.ModelSerializer):
    publisher = serializers.SlugRelatedField(
        slug_field='slug',                    # поле модели Publisher
        queryset=Publisher.objects.all()      # обязателен для записи
    )

    class Meta:
        model = Book
        fields = '__all__'

# Для read-only (без записи) — queryset не нужен:
publisher = serializers.SlugRelatedField(
    slug_field='slug',
    read_only=True
)

PrimaryKeyRelatedField

class BookSerializer(serializers.ModelSerializer):
    # ForeignKey по ID
    publisher = serializers.PrimaryKeyRelatedField(
        queryset=Publisher.objects.all()
    )
    # ManyToManyField по списку ID
    genres = serializers.PrimaryKeyRelatedField(
        queryset=Genre.objects.all(),
        many=True
    )

    class Meta:
        model = Book
        fields = '__all__'

APIView

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ItemListCreateView(APIView):
    def get(self, request):
        """GET — список объектов"""
        items = Item.objects.all()
        serializer = ItemSerializer(items, many=True)
        return Response(serializer.data)

    def post(self, request):
        """POST — создание объекта"""
        serializer = ItemSerializer(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 ItemDetailView(APIView):
    def get_object(self, pk):
        """Вспомогательный метод — избегает дублирования try/except"""
        try:
            return Item.objects.get(pk=pk)
        except Item.DoesNotExist:
            return None

    def get(self, request, pk):
        item = self.get_object(pk)
        if item is None:
            return Response({'error': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
        return Response(ItemSerializer(item).data)

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

    def patch(self, request, pk):
        item = self.get_object(pk)
        if item is None:
            return Response({'error': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
        # partial=True — разрешает частичное обновление
        serializer = ItemSerializer(item, data=request.data, partial=True)
        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):
        item = self.get_object(pk)
        if item is None:
            return Response({'error': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
        item.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Регистрация APIView в urls.py

from django.urls import path
from .views import ItemListCreateView, ItemDetailView

urlpatterns = [
    path('items/', ItemListCreateView.as_view(), name='item-list'),
    path('items/<int:pk>/', ItemDetailView.as_view(), name='item-detail'),
]

Generic Views — шаблоны

from rest_framework import generics

# Только список
class BookListView(generics.ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

# Список + создание
class BookListCreateView(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

# Получение + обновление + удаление
class BookDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

GenericAPIView + Mixins вручную

from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin, CreateModelMixin

class BookListCreateView(ListModelMixin, CreateModelMixin, GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

ViewSet + Router

from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
from rest_framework.routers import DefaultRouter

# Полный CRUD
class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

# Только чтение
class GenreViewSet(ReadOnlyModelViewSet):
    queryset = Genre.objects.all()
    serializer_class = GenreSerializer

# Router создаёт URL автоматически:
router = DefaultRouter()
router.register(r'books', BookViewSet)
router.register(r'genres', GenreViewSet)

urlpatterns = router.urls
# Генерирует:
# GET/POST   /books/
# GET/PUT/PATCH/DELETE /books/{pk}/
# GET        /genres/
# GET        /genres/{pk}/

Статус-коды HTTP в DRF

КонстантаКодКогда использовать
HTTP_200_OK200GET, PUT, PATCH — успешно
HTTP_201_CREATED201POST — объект создан
HTTP_204_NO_CONTENT204DELETE — удалено, нет тела ответа
HTTP_400_BAD_REQUEST400Ошибка валидации
HTTP_404_NOT_FOUND404Объект не найден
HTTP_405_METHOD_NOT_ALLOWED405Метод не разрешён
from rest_framework import status
# Использование:
return Response(data, status=status.HTTP_201_CREATED)
return Response({'error': '...'}, status=status.HTTP_404_NOT_FOUND)