💻 Примеры: DRF-блок (Уроки 33–38)

🎯 Показательные примеры К оглавлению урока

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

# 1. Generic View
class TaskListCreateView(ListCreateAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ['status']
    search_fields = ['title']
    ordering_fields = ['created_at']
    pagination_class = PageNumberPagination

# 2. ModelViewSet
class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

    @action(detail=False, methods=['get'])
    def count_tasks(self, request):
        data = Category.objects.annotate(task_count=Count('tasks'))
        return Response([{'id': c.id, 'count': c.task_count} for c in data])

# Router
router = DefaultRouter()
router.register(r'categories', CategoryViewSet)
urlpatterns = [path('', include(router.urls))]

Пример 1: Generic Views + фильтрация + пагинация

Полноценный CRUD для задач (Task) с фильтрацией, поиском, сортировкой и пагинацией.

# views.py
from rest_framework.generics import ListCreateAPIView, RetrieveUpdateDestroyAPIView
from rest_framework.pagination import PageNumberPagination
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Task
from .serializers import TaskSerializer

class TaskPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100

class TaskListCreateView(ListCreateAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['status', 'deadline']
    search_fields = ['title', 'description']
    ordering_fields = ['created_at']
    pagination_class = TaskPagination

class TaskDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
# urls.py
from django.urls import path
from .views import TaskListCreateView, TaskDetailView

urlpatterns = [
    path('tasks/', TaskListCreateView.as_view(), name='task-list-create'),
    path('tasks/<int:pk>/', TaskDetailView.as_view(), name='task-detail'),
]
# Примеры запросов
# GET /tasks/?status=todo
# GET /tasks/?search=important
# GET /tasks/?ordering=-created_at
# GET /tasks/?page=2&page_size=5

Пример 2: ModelViewSet + @action для подсчёта задач

# views.py
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Count
from .models import Category
from .serializers import CategorySerializer

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

    @action(detail=False, methods=['get'], url_path='count-tasks')
    def count_tasks(self, request):
        """Возвращает количество задач в каждой категории."""
        categories = Category.objects.annotate(task_count=Count('tasks'))
        data = [
            {'id': c.id, 'name': c.name, 'task_count': c.task_count}
            for c in categories
        ]
        return Response(data)
    # GET /categories/count-tasks/

# urls.py
from rest_framework.routers import DefaultRouter
from .views import CategoryViewSet

router = DefaultRouter()
router.register(r'categories', CategoryViewSet)
urlpatterns = [path('', include(router.urls))]

Пример 3: Soft Deletion для категорий

# 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 django.utils import timezone
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):
        self.is_deleted = True
        self.deleted_at = timezone.now()
        self.save()

    def __str__(self):
        return self.name
# Тестирование в shell
# python manage.py shell
from app.models import Category

cat = Category.objects.create(name='Work')
print(Category.objects.count())  # 1

cat.delete()
print(Category.objects.count())  # 0 — SoftDeleteManager фильтрует удалённые
print(Category.objects.get(pk=cat.pk).is_deleted)  # True — физически осталось

Пример 4: Транзакция при создании связанных объектов

# views.py
from django.db import transaction
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Task, Category
from .serializers import TaskSerializer

@api_view(['POST'])
def create_task_in_category(request):
    """Создать категорию и задачу атомарно."""
    try:
        with transaction.atomic():
            category = Category.objects.create(
                name=request.data.get('category_name', 'Default')
            )
            task_data = request.data.copy()
            task_data['category'] = category.id
            serializer = TaskSerializer(data=task_data)
            serializer.is_valid(raise_exception=True)
            serializer.save()
            # Если здесь произойдёт ошибка, Category тоже откатится
            transaction.on_commit(
                lambda: print(f"Task created: {serializer.data['title']}")
            )
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    except Exception as e:
        return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)

Пример 5: get_serializer_context() для условного вывода полей

# views.py
class TaskDetailView(RetrieveAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

    def get_serializer_context(self):
        context = super().get_serializer_context()
        context['include_subtasks'] = (
            self.request.query_params.get('include_subtasks', 'false').lower() == 'true'
        )
        return context

# serializers.py
class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = '__all__'

    def to_representation(self, instance):
        representation = super().to_representation(instance)
        if self.context.get('include_subtasks'):
            representation['subtasks'] = list(
                instance.subtasks.values('id', 'title', 'status')
            )
        return representation

# GET /tasks/1/?include_subtasks=true

Пример 6: select_related + prefetch_related для оптимизации

# views.py
class TaskListView(ListAPIView):
    serializer_class = TaskSerializer

    def get_queryset(self):
        # select_related — FK/OneToOne (JOIN)
        # prefetch_related — ManyToMany (отдельный запрос)
        return Task.objects.select_related('category').prefetch_related(
            'subtasks'
        ).filter(is_deleted=False)

# Без оптимизации: 1 + N запросов (N = число задач)
# С оптимизацией: 2 запроса всегда

Пример 7: CursorPagination для стабильной навигации

# pagination.py
from rest_framework.pagination import CursorPagination

class TaskCursorPagination(CursorPagination):
    page_size = 10
    ordering = '-created_at'  # стабильное поле для курсора

# views.py
class TaskListView(ListAPIView):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    pagination_class = TaskCursorPagination

# Ответ содержит:
# {
#   "next": "http://127.0.0.1:8000/tasks/?cursor=...",
#   "previous": null,
#   "results": [...]
# }