📖 Теория: конспект DRF-блока (Уроки 26–31)

🎯 Итоговое повторение К оглавлению урока

⚡ Краткий конспект

  • aggregate() — возвращает словарь вычисленных значений для всего QuerySet.
  • annotate() — добавляет вычисленные поля к каждому объекту QuerySet.
  • order_by(field) — ASC; order_by('-field') — DESC; срезы: [:5].
  • Subquery/OuterRef — подзапросы в ORM; ExpressionWrapper — оборачивание выражений с явным типом.
  • DRF — pip install djangorestframework + 'rest_framework' в INSTALLED_APPS.
  • ModelSerializer — автоматически создаёт поля из модели; fields/__all__/exclude.
  • is_valid() — валидирует данные; validated_data — безопасные данные после валидации.
  • @api_view — декоратор для FBV; APIView — базовый класс для CBV.
  • Generic views — ListAPIView, CreateAPIView, RetrieveUpdateDestroyAPIView.
  • ModelViewSet — все CRUD-методы; Router — автоматическая маршрутизация.
  • query_paramsrequest.query_params.get('key', default) для фильтрации/сортировки.

1. Агрегация данных в Django ORM (Урок 26)

Агрегация позволяет выполнять вычисления над набором данных — суммировать, считать, находить среднее, минимум, максимум.

Основные функции агрегации

ФункцияНазначение
Count('id')Подсчёт количества записей
Sum('price')Сумма значений поля
Avg('price')Среднее значение
Min('price')Минимальное значение
Max('price')Максимальное значение

aggregate() — вычисление по всему QuerySet

Возвращает словарь с результатами. Не возвращает отдельные объекты.

from django.db.models import Avg, Count, Sum, Min, Max

result = Book.objects.aggregate(
    total_books=Count('id'),
    average_price=Avg('price'),
    total_price=Sum('price'),
    min_price=Min('price'),
    max_price=Max('price'),
)
# {'total_books': 42, 'average_price': 15.5, ...}

annotate() — вычисляемые поля к каждому объекту

Добавляет вычисленное поле к каждой записи QuerySet.

from django.db.models import Count

# Количество книг каждого автора
author_stats = Book.objects.values('author').annotate(
    book_count=Count('id')
)
for entry in author_stats:
    print(entry['author'], entry['book_count'])

order_by() и срезы QuerySet

# Сортировка по возрастанию
books = Book.objects.order_by('title')

# Сортировка по убыванию
books = Book.objects.order_by('-published_date')

# Несколько полей: сначала автор ASC, затем дата DESC
books = Book.objects.order_by('author', '-published_date')

# Срезы — ограничение числа записей
first_5 = Book.objects.all()[:5]
next_5  = Book.objects.all()[5:10]
top_5_by_price = Book.objects.order_by('price')[:5]

Подзапросы: Subquery и OuterRef

from django.db.models import OuterRef, Subquery, Min

# Аннотировать каждую книгу минимальной ценой книги того же автора
subquery = Book.objects.filter(
    author=OuterRef('author')
).values('author').annotate(
    min_price=Min('price')
).values('min_price')

books = Book.objects.annotate(min_author_price=Subquery(subquery))

ExpressionWrapper — оборачивание выражений

from django.db.models import F, ExpressionWrapper, fields

books = Book.objects.annotate(
    discount_pct=ExpressionWrapper(
        (1 - F('discounted_price') / F('price')) * 100,
        output_field=fields.FloatField()
    )
)

2. Django REST Framework — введение (Урок 27)

DRF — набор инструментов для создания RESTful API поверх Django. Ключевые компоненты: модели, сериализаторы, представления (views), маршрутизация.

Установка и подключение

# Установка
pip install djangorestframework

# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
]

Основные концепции REST

КонцепцияОписание
РесурсыОбъекты данных, доступные по URL
HTTP-методыGET, POST, PUT, PATCH, DELETE
Статус-коды200 OK, 201 Created, 400 Bad Request, 404 Not Found, 204 No Content
СериализацияПреобразование объектов в JSON и обратно

3. Сериализаторы (Уроки 27–28)

serializers.Serializer — ручное определение

from rest_framework import serializers

class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    author = serializers.CharField(max_length=50)
    published_date = serializers.DateField()

serializers.ModelSerializer — автоматически из модели

from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'          # все поля
        # fields = ['title', 'author', 'price']  # выборочно
        # exclude = ['created_at']  # исключить поле

Валидация: is_valid и validated_data

data = {'title': 'Django for Beginners', 'author': 'W. Vincent', 'published_date': '2021-05-21'}
serializer = BookSerializer(data=data)

if serializer.is_valid():
    validated = serializer.validated_data  # безопасные данные
    serializer.save()                      # вызывает create() или update()
else:
    print(serializer.errors)              # {'author': [...]}

# Вариант с автоматическим исключением при ошибке
serializer.is_valid(raise_exception=True)

Переопределение полей

class BookSerializer(serializers.ModelSerializer):
    created_at = serializers.DateTimeField(read_only=True)
    discounted_price = serializers.DecimalField(
        max_digits=10, decimal_places=2, write_only=True, required=False
    )
    class Meta:
        model = Book
        fields = '__all__'
        read_only_fields = ['id', 'created_at']

Переопределение create и update

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['title', 'author', 'published_date', 'price']

    def create(self, validated_data):
        from django.utils import timezone
        validated_data['created_at'] = timezone.now()
        return super().create(validated_data)

    def update(self, instance, validated_data):
        if 'title' in validated_data:
            validated_data['title'] = validated_data['title'].strip().title()
        return super().update(instance, validated_data)

Кастомная валидация

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

    def validate_price(self, value):
        if value < 0:
            raise serializers.ValidationError("Цена не может быть отрицательной.")
        return value

    def validate(self, data):
        if data.get('discounted_price') and data['discounted_price'] >= data['price']:
            raise serializers.ValidationError("Скидочная цена должна быть ниже обычной.")
        return data

4. Представления (Views) DRF (Уроки 27–29)

Function-Based View с @api_view

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Book
from .serializers import BookSerializer

@api_view(['GET', 'POST'])
def book_list_create(request):
    if request.method == 'GET':
        books = Book.objects.all()
        serializer = BookSerializer(books, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    elif request.method == 'POST':
        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-Based View: APIView

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

class BookDetailView(APIView):
    def get(self, request, pk):
        try:
            book = Book.objects.get(pk=pk)
        except Book.DoesNotExist:
            return Response({'error': 'Not found'}, status=status.HTTP_404_NOT_FOUND)
        serializer = BookSerializer(book)
        return Response(serializer.data)

    def put(self, request, pk):
        book = Book.objects.get(pk=pk)
        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 = Book.objects.get(pk=pk)
        book.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

5. Generic views и ModelViewSet (Урок 29)

Generic views — готовые классы

КлассHTTP-методыНазначение
ListAPIViewGETСписок объектов
CreateAPIViewPOSTСоздание объекта
RetrieveAPIViewGETОдин объект по pk
UpdateAPIViewPUT/PATCHОбновление объекта
DestroyAPIViewDELETEУдаление объекта
ListCreateAPIViewGET, POSTСписок + создание
RetrieveUpdateDestroyAPIViewGET, PUT, PATCH, DELETEОдин объект: чтение + обновление + удаление
from rest_framework import generics
from .models import Book
from .serializers import BookSerializer

class BookListCreateView(generics.ListCreateAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

class BookDetailView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

ModelViewSet — все CRUD за один класс

from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

Router — автоматическая маршрутизация

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

router = DefaultRouter()
router.register(r'books', BookViewSet, basename='book')

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

6. query_params — фильтрация и сортировка (Урок 31)

from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializers import BookSerializer

class BookFilterView(APIView):
    def get(self, request):
        queryset = Book.objects.all()

        # Фильтрация по автору
        author = request.query_params.get('author')
        if author:
            queryset = queryset.filter(author__icontains=author)

        # Фильтрация по минимальной цене
        min_price = request.query_params.get('min_price')
        if min_price:
            queryset = queryset.filter(price__gte=min_price)

        # Сортировка
        ordering = request.query_params.get('ordering', 'title')
        queryset = queryset.order_by(ordering)

        # Пагинация
        page = int(request.query_params.get('page', 1))
        page_size = int(request.query_params.get('page_size', 10))
        start = (page - 1) * page_size
        end = start + page_size
        queryset = queryset[start:end]

        serializer = BookSerializer(queryset, many=True)
        return Response(serializer.data)