📖 Теория: конспект DRF-блока (Уроки 33–38)
⚡ Краткий конспект
- GenericAPIView — queryset + serializer_class; get_queryset() для динамической фильтрации; get_object() для поиска одного объекта; get_serializer_class() для разных сериализаторов.
- Generic Views — 9 готовых классов: ListAPIView, CreateAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView и их комбинации.
- lookup_field — поле модели для поиска (по умолчанию pk); lookup_url_kwarg — имя параметра в URL.
- ViewSets — ModelViewSet (полный CRUD), ReadOnlyModelViewSet (только чтение), GenericViewSet + миксины (кастомный набор). Кастомные методы через
@action. - Router — SimpleRouter или DefaultRouter;
router.register(r'prefix', ViewSet). - filter_backends — DjangoFilterBackend (filterset_fields), SearchFilter (search_fields), OrderingFilter (ordering_fields).
- Soft Deletion — поле is_deleted; переопределение delete() для установки флага; SoftDeleteManager фильтрует is_deleted=False.
- N+1 проблема — select_related() для FK/OneToOne; prefetch_related() для ManyToMany.
- Транзакции — @transaction.atomic / with transaction.atomic(); on_commit; set_rollback.
- Пагинация — PageNumberPagination (?page=N), LimitOffsetPagination (?limit=N&offset=N), CursorPagination (стабильная). Глобальная через DEFAULT_PAGINATION_CLASS.
- Логирование SQL — LOGGING с 'django.db.backends' уровня DEBUG.
1. GenericAPIView (Урок 33)
GenericAPIView расширяет APIView, добавляя поддержку queryset, сериализаторов, фильтрации и пагинации. Разработчики обычно используют его через готовые Generic Views, а не напрямую.
Ключевые атрибуты
| Атрибут | Назначение |
|---|---|
queryset | Набор данных представления |
serializer_class | Класс сериализатора |
filter_backends | Список классов фильтрации |
pagination_class | Класс пагинации |
lookup_field | Поле модели для поиска (default: pk) |
lookup_url_kwarg | Параметр URL для lookup_field |
Ключевые методы
class BookListView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_queryset(self):
# Динамическая фильтрация по параметру запроса
queryset = Book.objects.all()
author = self.request.query_params.get('author')
if author:
queryset = queryset.filter(author=author)
return queryset
def get_serializer_class(self):
# Разные сериализаторы для разных методов
if self.request.method == 'POST':
return BookCreateSerializer
return BookSerializer
def get_serializer_context(self):
# Передача дополнительного контекста в сериализатор
context = super().get_serializer_context()
context['include_related'] = self.request.query_params.get(
'include_related', 'false'
).lower() == 'true'
return context
Переопределение get_object()
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get_object(self):
pk = self.kwargs.get('pk')
try:
book = self.queryset.get(pk=pk, is_banned=False)
except Book.DoesNotExist:
raise NotFound(detail=f"Book with id '{pk}' not found or is banned.")
return book
2. Generic Views (Уроки 33–34)
Готовые классы на основе GenericAPIView + миксины. Каждый реализует стандартные CRUD-операции.
| Класс | HTTP-методы | Назначение |
|---|---|---|
ListAPIView | GET | Список объектов |
CreateAPIView | POST | Создание |
RetrieveAPIView | GET | Один объект по pk |
UpdateAPIView | PUT/PATCH | Обновление |
DestroyAPIView | DELETE | Удаление |
ListCreateAPIView | GET, POST | Список + создание |
RetrieveUpdateAPIView | GET, PUT, PATCH | Чтение + обновление |
RetrieveDestroyAPIView | GET, DELETE | Чтение + удаление |
RetrieveUpdateDestroyAPIView | GET, PUT, PATCH, DELETE | Все три операции над одним объектом |
Минимальное использование
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from .models import Book
from .serializers import BookSerializer
class BookListCreateView(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
Переопределение методов миксинов
class BookListCreateView(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def create(self, request, *args, **kwargs):
data = request.data.copy()
if not data.get('author'):
data['author'] = 'Unknown Author'
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
lookup_field и lookup_url_kwarg
class GenreDetailView(RetrieveUpdateDestroyAPIView):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
lookup_field = 'name' # поиск по полю 'name', не pk
lookup_url_kwarg = 'genre_name' # имя параметра в URL
# urls.py
path('genres/<str:genre_name>/', GenreDetailView.as_view())
3. ViewSets и Router (Урок 35)
ViewSet объединяет логику нескольких HTTP-методов в одном классе. Router автоматически создаёт URL-маршруты.
Виды ViewSets
| Класс | Доступные действия |
|---|---|
ModelViewSet | list, create, retrieve, update, partial_update, destroy |
ReadOnlyModelViewSet | list, retrieve |
GenericViewSet | нет действий по умолчанию — добавляются миксинами |
from rest_framework import viewsets
from .models import Genre
from .serializers import GenreSerializer
class GenreViewSet(viewsets.ModelViewSet):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
Кастомные методы через @action
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Count
class GenreViewSet(viewsets.ModelViewSet):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
@action(detail=False, methods=['get'])
def statistic(self, request):
genres = Genre.objects.annotate(book_count=Count('books'))
data = [
{'id': g.id, 'genre': g.name, 'book_count': g.book_count}
for g in genres
]
return Response(data)
# GET /genres/statistic/
Router: SimpleRouter vs DefaultRouter
from rest_framework.routers import DefaultRouter
from .views import GenreViewSet
router = DefaultRouter() # добавляет страницу /api/ с обзором всех маршрутов
# SimpleRouter не добавляет страницу корня API
router.register(r'genres', GenreViewSet)
urlpatterns = [
path('', include(router.urls)),
]
# Генерирует: GET/POST /genres/, GET/PUT/PATCH/DELETE /genres/{pk}/
GenericViewSet + миксины (кастомный набор)
from rest_framework import mixins, viewsets
class GenreListRetrieveUpdateViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet
):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
# Только list, retrieve, update — без create и destroy
4. filter_backends (Урок 37)
# settings.py
INSTALLED_APPS = ['django_filters', ...]
# views.py
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
class BookListView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['author', 'publisher', 'is_bestseller']
search_fields = ['title', 'author']
ordering_fields = ['published_date', 'price']
# Примеры запросов:
# GET /books/?author=Fitzgerald
# GET /books/?search=Gatsby
# GET /books/?ordering=-price
5. Soft Deletion (Урок 37)
Записи не удаляются физически — они помечаются флагом is_deleted.
# managers.py
from django.db import models
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
# models.py
from .managers import SoftDeleteManager
class Category(models.Model):
name = models.CharField(max_length=100)
is_deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
objects = SoftDeleteManager()
def delete(self, *args, **kwargs):
from django.utils import timezone
self.is_deleted = True
self.deleted_at = timezone.now()
self.save()
6. Ленивая загрузка и N+1 (Урок 37)
QuerySet — ленивый: запрос выполняется только при обращении к данным. Основная проблема — N+1 запросов при доступе к связанным объектам.
# Проблема N+1 — для каждой книги выполняется отдельный запрос к publisher
books = Book.objects.all()
for book in books:
print(book.publisher) # N запросов!
# Решение: select_related (JOIN) для FK/OneToOne
books = Book.objects.select_related('publisher').all()
# prefetch_related для ManyToMany и обратных FK
books = Book.objects.prefetch_related('genres').all()
# Вместе
books = Book.objects.select_related('publisher').prefetch_related('genres').all()
7. Транзакции (Урок 37)
from django.db import transaction
# Декоратор
@transaction.atomic
def my_view(request):
...
# Контекстный менеджер
with transaction.atomic():
publisher = Publisher.objects.create(...)
book = Book.objects.create(publisher=publisher, ...)
# Откат транзакции вручную
with transaction.atomic():
...
if some_condition:
transaction.set_rollback(True)
# Действие после успешного коммита
with transaction.atomic():
book = Book.objects.create(...)
transaction.on_commit(lambda: send_notification(book))
8. Пагинация (Урок 38)
| Класс | Параметры запроса | Особенности |
|---|---|---|
PageNumberPagination | ?page=N&page_size=N | Номер страницы; прост в использовании |
LimitOffsetPagination | ?limit=N&offset=N | Гибкий сдвиг; привычен для SQL-разработчиков |
CursorPagination | ?cursor=... | Стабильный порядок; безопасен при частых изменениях |
# Локальная настройка
from rest_framework.pagination import PageNumberPagination
class BookPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 100
class BookListView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
pagination_class = BookPagination
# Глобальная настройка в settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5,
}
9. Логирование SQL-запросов (Урок 38)
# settings.py
import os
DEBUG = True # Логирование SQL работает только при DEBUG=True
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': os.path.join(BASE_DIR, 'db.log'),
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
},
},
}
10. Регулярные выражения в URL (Урок 35)
from django.urls import re_path
from .views import books_by_date_view
urlpatterns = [
re_path(
r'^books/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$',
books_by_date_view,
name='books-by-date'
),
]
# views.py
@api_view(['GET'])
def books_by_date_view(request, year, month, day):
books = Book.objects.filter(
published_date__year=year,
published_date__month=month,
published_date__day=day
)
serializer = BookSerializer(books, many=True)
return Response({'date': f'{year}-{month}-{day}', 'books': serializer.data})