ModelViewSet — полный CRUD одним классом; Router строит URLs автоматически
as_view() — превращает класс в функцию для urls.py
Часть 1: Поля для отношений между моделями
В DRF связанные объекты (ForeignKey, ManyToManyField) по умолчанию сериализуются по первичному ключу. Но DRF предоставляет несколько полей для гибкой настройки представления связей.
1. StringRelatedField
StringRelatedField — поле для сериализации связанных объектов в виде строки, используя их метод __str__(). Отображает строковое представление, но не позволяет изменять данные (только чтение).
Использование
# serializers.py
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.StringRelatedField()
class Meta:
model = Book
fields = '__all__'
Объяснение: publisher = serializers.StringRelatedField() отображает строковое представление издателя — результат вызова Publisher.__str__().
Пример GET-запроса
GET http://127.0.0.1:8000/books/1/
# Ожидаемый ответ:
{
"id": 27,
"publisher": "Old Books", # строковое представление через __str__
"title": "Django For Beginners",
"author": "William Vincent",
"published_date": "2021-05-21",
"created_at": null,
"price": "39.99",
"discounted_price": null,
"is_bestseller": true
}
Когда использовать StringRelatedField:
Когда нужно отобразить читаемое имя объекта в ответе API
Когда данные только для чтения и изменение связи через API не требуется
Для вывода списков, где важна читаемость, а не ID
2. SlugRelatedField
SlugRelatedField — поле для работы со связанными объектами по определённому полю (slug), а не первичному ключу. Позволяет указывать slug-значения при создании или обновлении данных.
Модель Publisher с полем slug
# models.py
class Publisher(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
established_date = models.DateField()
Использование SlugRelatedField
# serializers.py
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.SlugRelatedField(
slug_field='slug',
queryset=Publisher.objects.all()
)
class Meta:
model = Book
fields = '__all__'
Объяснение: slug_field='slug' указывает поле Publisher, по которому происходит поиск. queryset — набор данных для валидации значения.
PrimaryKeyRelatedField — поле для работы с первичными ключами связанных объектов. Стандартный выбор для ForeignKey. Позволяет указывать ID при создании или обновлении данных.
Позволяет указывать первичные ключи для связанных объектов при создании или обновлении
Обеспечивает ссылку на связанные объекты по их ID, что упрощает работу с базой данных
Использование для ForeignKey
# serializers.py
from rest_framework import serializers
from .models import Book, Publisher
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.PrimaryKeyRelatedField(
queryset=Publisher.objects.all()
)
class Meta:
model = Book
fields = '__all__'
Объяснение: queryset=Publisher.objects.all() предоставляет набор данных для поиска объекта по ID.
4. Many-to-Many с PrimaryKeyRelatedField
Для ManyToManyField используется тот же PrimaryKeyRelatedField, но с параметром many=True.
# serializers.py
from rest_framework import serializers
from .models import Book, Genre, Publisher
class BookSerializer(serializers.ModelSerializer):
publisher = serializers.PrimaryKeyRelatedField(
queryset=Publisher.objects.all()
)
genres = serializers.PrimaryKeyRelatedField(
queryset=Genre.objects.all(),
many=True # указывает на список объектов
)
class Meta:
model = Book
fields = '__all__'
Сериализатор для Genre
# serializers.py
from library.models import Genre
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = '__all__'
Class-Based Views (CBV) — подход к созданию представлений в DRF, где представления реализуются как классы. CBV обеспечивают объектно-ориентированный подход, позволяя лучше структурировать, расширять и переиспользовать код.
CBV делятся на несколько категорий — от базовых до высокоуровневых. Каждая категория предоставляет различный уровень абстракции.
5. Иерархия Class-Based Views
Уровень 1: APIView (базовый)
Основной класс для создания CBV. Предоставляет методы для обработки HTTP-запросов: get, post, put, patch, delete.
Использование: создание кастомных представлений с нуля, требующих специфической логики
Уровень: Низкоуровневый — всё пишется вручную
Уровень 2: GenericAPIView + Mixins (средний)
GenericAPIView расширяет APIView и добавляет базовую функциональность: пагинация, фильтрация, атрибуты queryset и serializer_class.
CreateModelMixin — метод create() для создания объекта
RetrieveModelMixin — метод retrieve() для получения объекта
UpdateModelMixin — методы update() и partial_update()
DestroyModelMixin — метод destroy() для удаления
ListModelMixin — метод list() для списка объектов
Комбинация GenericAPIView + нужных миксинов = гибкие представления с минимальным кодом.
Уровень 3: Generic Views (высокоуровневый)
Готовые классы, объединяющие GenericAPIView и нужные миксины:
Класс
HTTP-методы
Назначение
ListAPIView
GET
Список объектов
CreateAPIView
POST
Создание объекта
RetrieveAPIView
GET
Один объект по ID
UpdateAPIView
PUT, PATCH
Обновление объекта
DestroyAPIView
DELETE
Удаление объекта
ListCreateAPIView
GET, POST
Список + создание
RetrieveUpdateAPIView
GET, PUT, PATCH
Получение + обновление
RetrieveDestroyAPIView
GET, DELETE
Получение + удаление
RetrieveUpdateDestroyAPIView
GET, PUT, PATCH, DELETE
Получение + обновление + удаление
Уровень 4: ViewSets (максимальный уровень абстракции)
ModelViewSet — полный набор операций CRUD. Объединяет все generic представления и миксины. Используется совместно с Router для автоматической генерации URL.
from rest_framework.viewsets import ModelViewSet
from rest_framework.routers import DefaultRouter
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# Router автоматически создаёт все URL
router = DefaultRouter()
router.register(r'books', BookViewSet)
urlpatterns = router.urls
# Результат: /books/, /books/{pk}/ с GET/POST/PUT/PATCH/DELETE
ReadOnlyModelViewSet — только операции чтения (list и retrieve). Для представлений, где запись через API не требуется.
6. Преимущества CBV перед FBV
Аспект
FBV (Function-Based)
CBV (Class-Based)
Структура кода
Процедурный подход
Объектно-ориентированный, классы
Повторное использование
Копирование кода
Наследование, миксины
Расширение
Модификация функции
Переопределение методов в подклассе
Стандартные операции
Пишутся вручную
Generic Views готовы "из коробки"
Тестирование
Вся функция целиком
Каждый метод отдельно
Часть 3: APIView
7. Что такое APIView
APIView — базовый класс представления в Django REST Framework. Расширяет стандартный класс Django View и предоставляет функциональность для работы с DRF-запросами и ответами.
Основные возможности APIView
Обработка HTTP-запросов: методы get, post, put, patch, delete
Встроенная поддержка API: автоматическая обработка запросов и ответов в формате JSON
Кастомизация: переопределение любого метода для специфической логики
Представление для списка и создания объектов
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer
class BookListCreateView(APIView):
def get(self, request):
"""GET /books/ — список всех книг"""
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request):
"""POST /books/ — создание новой книги"""
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)
Объяснения: get обрабатывает GET-запросы — возвращает список всех объектов. post обрабатывает POST-запросы — создаёт новый объект.
Представление для получения, обновления и удаления объектов
# views.py
class BookDetailUpdateDeleteView(APIView):
def get(self, request, pk):
"""GET /books/{pk}/ — получение конкретной книги"""
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response({'error': 'Book not found'}, status=status.HTTP_404_NOT_FOUND)
serializer = BookSerializer(book)
return Response(serializer.data)
def put(self, request, pk):
"""PUT /books/{pk}/ — полное обновление"""
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
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):
"""DELETE /books/{pk}/ — удаление"""
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response({'error': 'Book not found'}, status=status.HTTP_404_NOT_FOUND)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)