📖 Теория: ORM-запросы, сериализаторы и базовые views

Контекст практикума — всё необходимое перед задачами

⚡ Самое важное

  • annotate() + ExtractWeekDay: добавляет вычисляемое поле к каждой строке QuerySet
  • aggregate() + Count/Avg: возвращает один словарь с итоговым значением
  • Paginator(qs, N): разбивает QuerySet на страницы по N элементов
  • ModelSerializer: автоматически генерирует поля из модели, Meta.fields задаёт список
  • @api_view(['GET']): декоратор DRF превращает функцию во view с поддержкой DRF-запроса
  • JsonResponse(data, safe=False): нужен safe=False если data — список, а не словарь

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

Часть 1: Продвинутые ORM-запросы

Фильтрация по дате — __month, __year

Django ORM позволяет фильтровать по отдельным компонентам даты через lookup-суффиксы.

# Проекты, созданные в текущем месяце
from django.utils import timezone
Project.objects.filter(date_of_creation__month=timezone.now().month)

# Файлы, созданные в конкретный год
ProjectFile.objects.filter(created_at__year=2025)
timezone.now() возвращает текущее UTC-время (или локальное если USE_TZ=False). Предпочтительнее datetime.now(), так как учитывает настройки часового пояса Django.

ExtractWeekDay — день недели из даты

ExtractWeekDay — функция для аннотации QuerySet. Извлекает номер дня недели из поля типа DateField/DateTimeField.

from django.db.models.functions import ExtractWeekDay

files = ProjectFile.objects.annotate(
    weekday=ExtractWeekDay('created_at')
).filter(weekday=2)  # 2 = понедельник
Нумерация дней: ExtractWeekDay использует ISO-стиль, совместимый с базой данных. В большинстве СУБД: воскресенье=1, понедельник=2, ..., суббота=7. Это противоречит Python (где понедельник=0). Проверяйте в своей СУБД!

annotate() — агрегация по строкам

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

from django.db.models import Count, Avg

# Количество файлов для каждого проекта (добавляет поле file_count к каждому проекту)
Project.objects.annotate(file_count=Count('project_files'))

# Среднее количество задач на каждом проекте
Project.objects.annotate(avg_tasks=Avg('tasks__id'))

aggregate() — одно итоговое значение

aggregate() возвращает словарь с одним итоговым значением по всему QuerySet.

from django.db.models import Count

# Общее количество проектов
Project.objects.all().count()          # метод count()
Project.objects.aggregate(n=Count('id'))  # aggregate — тоже работает

values_list() — только нужные поля

# Получить только username и count_of_tasks
User.objects.annotate(
    count_of_tasks=Count('tasks__id')
).values_list('username', 'count_of_tasks')

order_by() — сортировка по нескольким полям

# Сортировка задач по приоритету, затем по дате выполнения
Task.objects.order_by('priority', 'due_date')

# Обратный порядок: '-' перед именем поля
User.objects.annotate(
    count_of_tasks=Count('tasks__id')
).order_by('-count_of_tasks')

Paginator — пагинация

Paginator из django.core.paginator разбивает QuerySet на страницы фиксированного размера.

from django.core.paginator import Paginator

tasks = Task.objects.all()
paginator = Paginator(tasks, 10)   # 10 объектов на страницу
page = paginator.get_page(1)       # первая страница

for task in page:
    print(task.name)
get_page(n) безопаснее page(n): не бросает исключение если номер страницы выходит за границы.

Часть 2: Django REST Framework (DRF)

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

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

# requirements.txt
djangorestframework

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

ModelSerializer

ModelSerializer — сериализатор, который автоматически создаёт поля на основе модели Django. Минимально нужно указать model и fields в классе Meta.

from rest_framework.serializers import ModelSerializer
from management_app.models import Project

class AllProjectsSerializer(ModelSerializer):
    class Meta:
        model = Project
        fields = ['id', 'name']          # конкретные поля
        # fields = '__all__'             # все поля модели

Для сериализации QuerySet передать many=True:

serializer = AllProjectsSerializer(Project.objects.all(), many=True)
data = serializer.data  # список словарей

Вложенные сериализаторы

Поле модели можно представить не как ID, а как вложенный объект, используя другой сериализатор как тип поля:

class TaskInfoSerializer(serializers.ModelSerializer):
    tags = TagsSerializer(many=True)   # вложенный сериализатор

    class Meta:
        model = Task
        fields = ['id', 'name', 'status', 'priority', 'tags', ...]

@api_view — функциональные views

Декоратор @api_view оборачивает обычную функцию в DRF-обработчик. В списке передаются разрешённые HTTP-методы.

from rest_framework.request import Request
from rest_framework.decorators import api_view
from rest_framework import status
from django.http import JsonResponse

@api_view(['GET'])
def get_all_projects(request: Request) -> JsonResponse:
    projects = Project.objects.all()
    if not projects.exists():
        return JsonResponse([], status=status.HTTP_204_NO_CONTENT, safe=False)
    data = AllProjectsSerializer(projects, many=True)
    return JsonResponse(data.data, status=status.HTTP_200_OK, safe=False)
safe=False обязателен когда возвращаете список (JSON array). По умолчанию JsonResponse допускает только словари — это защита от старой уязвимости браузеров, но для API её нужно явно отключать.

Структура URL-маршрутов

# management_app/urls.py
from django.urls import path
from management_app.views import get_all_projects

urlpatterns = [
    path('projects/', get_all_projects),
]

# project_management/urls.py (главный urls.py проекта)
from django.urls import path, include

urlpatterns = [
    path('management_app/', include('management_app.urls')),
]
# Итоговый URL: /management_app/projects/

HTTP-статусы в DRF

КонстантаКодКогда использовать
status.HTTP_200_OK200Успешный GET / PUT
status.HTTP_201_CREATED201Успешный POST (создание)
status.HTTP_204_NO_CONTENT204Нет данных / успешное DELETE
status.HTTP_400_BAD_REQUEST400Ошибка валидации
status.HTTP_404_NOT_FOUND404Объект не найден

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