✅ Практикум 4: разбор решений
⚡ Решения — краткий обзор
# Задача 1
Project.objects.filter(date_of_creation__month=timezone.now().month)
# Задача 2
ProjectFile.objects.annotate(weekday=ExtractWeekDay('created_at')).filter(weekday=2)
# Задача 3
Project.objects.all().count()
# Задача 5
Project.objects.annotate(avg_tasks=Avg('tasks__id'))
# Задача 6
User.objects.annotate(count_of_tasks=Count('tasks__id')).values_list('username', 'count_of_tasks')
# Задача 8
User.objects.annotate(count_of_tasks=Count('tasks__id')).order_by('-count_of_tasks').values_list(...)
# Задача 9
Paginator(Task.objects.all(), 10).get_page(1)
# Задача 10
class AllProjectsSerializer(ModelSerializer):
class Meta:
model = Project
fields = ['id', 'name']
Часть 1: ORM-запросы
Решение задачи 1 — Проекты за текущий месяц
from django.utils import timezone
from management_app.models.project import Project
cur_month_projects = Project.objects.filter(
date_of_creation__month=timezone.now().month
)
if cur_month_projects:
for proj in cur_month_projects:
print("=" * 50)
print(proj.name)
print(proj.date_of_creation)
else:
print("EMPTY DATA")
Разбор: Lookup
__month извлекает месяц из DateField/DateTimeField. timezone.now().month возвращает текущий месяц как int (1–12). Django формирует SQL: WHERE EXTRACT(MONTH FROM date_of_creation) = N.
Решение задачи 2 — Файлы проектов в день недели
from django.db.models.functions import ExtractWeekDay
from management_app.models.project import ProjectFile
monday_project_files = ProjectFile.objects.annotate(
weekday=ExtractWeekDay('created_at')
).filter(
weekday=2 # 2 = понедельник
)
if monday_project_files:
for file_ in monday_project_files:
print("=" * 50)
print(file_.file_name)
print(file_.file_path)
else:
print("EMPTY DATA")
Разбор:
annotate(weekday=ExtractWeekDay('created_at')) добавляет виртуальное поле weekday к каждому объекту. Затем фильтруем по нему. Значение 2 = понедельник (ISO: воскресенье=1, понедельник=2).
Решение задачи 3 — Общее количество проектов
from management_app.models.project import Project
count_of_projects = Project.objects.all().count()
print(f"Count of projects: {count_of_projects}")
Разбор: Метод
.count() делает SQL SELECT COUNT(*) — самый эффективный способ посчитать объекты. Не загружает сами объекты в память.
Решение задачи 4 — Количество файлов для каждого проекта
from django.db.models import Count
from management_app.models import Project
projects = Project.objects.all()
project_file_counts = {}
for project in projects:
file_count = project.project_files.aggregate(num_files=Count('id'))
project_file_counts[project.name] = file_count['num_files']
for project_name, num_files in project_file_counts.items():
print(f"Project: {project_name}, Number of files: {num_files}")
Разбор: Для каждого проекта вызываем
.aggregate() на related manager project_files. aggregate() возвращает словарь — берём значение по ключу 'num_files'. Обратите внимание: это N+1 запросов. Более эффективный вариант — Project.objects.annotate(num_files=Count('project_files')).
Решение задачи 5 — Среднее количество задач на каждом проекте
from django.db.models import Avg
from management_app.models import Project
average_project_tasks = Project.objects.annotate(avg_tasks=Avg('tasks__id'))
for project in average_project_tasks:
print(f"Project: {project.name}, Average Number of Tasks: {project.avg_tasks}")
Разбор:
annotate(avg_tasks=Avg('tasks__id')) вычисляет среднее значение поля id связанных задач. Поскольку id автоинкрементный, это фактически даёт среднее значение ID, а не количество. Для подсчёта количества корректнее использовать Count('tasks'), но задача из источника требует Avg.
Решение задачи 6 — Количество задач для каждого пользователя
from django.db.models import Count
from django.contrib.auth.models import User
count_of_tasks_to_user = User.objects.annotate(
count_of_tasks=Count('tasks__id')
).values_list('username', 'count_of_tasks')
print(count_of_tasks_to_user.query) # вывести SQL для отладки
for username, count_of_tasks in count_of_tasks_to_user:
print(f"Developer - {username} | Count of tasks: {count_of_tasks}")
Разбор:
values_list('username', 'count_of_tasks') ограничивает SELECT только нужными полями. QuerySet теперь возвращает кортежи, а не объекты модели. .query — полезный инструмент отладки: печатает итоговый SQL-запрос.
Решение задачи 7 — Сортировка задач по приоритету и дате
from management_app.models import Task
all_tasks = Task.objects.order_by(
'priority',
'due_date'
).values_list(
'name', 'priority', 'due_date'
)
for name, priority, due_date in all_tasks:
print(f"Task name - {name} | Priority: {priority} | Due date - {due_date}")
Разбор:
order_by() принимает несколько полей. Сортировка идёт последовательно: сначала по priority, при равенстве — по due_date. Цепочка order_by().values_list() — эффективный способ получить только нужные поля в отсортированном порядке.
Решение задачи 8 — Сортировка пользователей по количеству задач
from django.db.models import Count
from django.contrib.auth.models import User
sorted_users = User.objects.annotate(
count_of_tasks=Count('tasks__id')
).order_by('-count_of_tasks').values_list('username', 'count_of_tasks')
print(sorted_users.query) # SQL для отладки
for username, count_of_tasks in sorted_users:
print(f"{username} | {count_of_tasks}")
Разбор: Минус перед именем поля в
order_by('-count_of_tasks') задаёт обратный порядок (DESC). Комбинация annotate() + order_by() + values_list() — стандартный паттерн для "топ N пользователей по критерию".
Решение задачи 9 — Пагинация для задач
from management_app.models import Task
from django.core.paginator import Paginator
tasks = Task.objects.all()
pages_paginator = Paginator(tasks, 10) # 10 задач на страницу
page_number = 1
page_tasks = pages_paginator.get_page(page_number)
for task in page_tasks:
print("=" * 50)
print(task.name)
print(task.status)
print(task.priority)
print(task.assignee.username)
Разбор:
Paginator(queryset, per_page) принимает QuerySet и размер страницы. get_page(n) возвращает Page-объект, который можно итерировать. Paginator не загружает весь QuerySet сразу — только нужную страницу (LIMIT/OFFSET в SQL).
Часть 2: Django REST Framework
Решение задачи 10 — Первый сериализатор для проектов
# management_app/serializers/projects.py
from rest_framework.serializers import ModelSerializer
from management_app.models import Project
class AllProjectsSerializer(ModelSerializer):
class Meta:
model = Project
fields = ['id', 'name']
Разбор:
ModelSerializer автоматически создаёт поля на основе модели. Вложенный класс Meta задаёт: model — с какой моделью работаем, fields — какие поля включить в сериализацию. fields = ['id', 'name'] означает только эти два поля, без остальных.
Решение задачи 11 — View для списка всех проектов
from rest_framework.request import Request
from django.http import JsonResponse
from rest_framework import status
from rest_framework.decorators import api_view
from management_app.serializers.projects import AllProjectsSerializer
from management_app.models.project import Project
@api_view(['GET',])
def get_all_projects(request: Request) -> JsonResponse:
all_projects = Project.objects.all()
if not all_projects.exists():
return JsonResponse(
[],
status=status.HTTP_204_NO_CONTENT,
safe=False
)
else:
serialized_data = AllProjectsSerializer(
all_projects, many=True
)
return JsonResponse(
serialized_data.data,
status=status.HTTP_200_OK,
safe=False
)
# —--------------------------
# 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
from django.urls import path, include
urlpatterns = [
path('management_app/', include('management_app.urls')),
]
Разбор:
@api_view(['GET']) — DRF-декоратор, ограничивает обработку только GET-запросами. many=True нужен для сериализации QuerySet (не одного объекта). safe=False в JsonResponse обязателен для возврата списка (массива JSON).
Решение задачи 12 — Сериализатор AllTasksSerializer
from rest_framework.serializers import ModelSerializer
from management_app.models import Task
class AllTasksSerializer(ModelSerializer):
class Meta:
model = Task
fields = ['id', 'name', 'status', 'priority']
Разбор: Аналогично AllProjectsSerializer, но для модели Task. Указываем только 4 поля для "общей" информации о задачах (детальный сериализатор будет в задаче 20).
Решение задачи 13 — View для списка задач с фильтрацией
@api_view(['GET',])
def get_all_tasks(request: Request) -> JsonResponse:
project_name = request.query_params.get('project')
if not project_name:
all_tasks = Task.objects.all()
else:
all_tasks = Task.objects.filter(project__name=project_name)
if not all_tasks.exists():
return JsonResponse(
data=[],
status=status.HTTP_204_NO_CONTENT,
safe=False
)
serialized_data = AllTasksSerializer(all_tasks, many=True)
return JsonResponse(
data=serialized_data.data,
status=status.HTTP_200_OK,
safe=False
)
# management_app/urls.py
from management_app.views import get_all_projects, get_all_tasks
urlpatterns = [
path('projects/', get_all_projects),
path('tasks/', get_all_tasks), # NEW — /management_app/tasks/?project=TIGER
]
Разбор:
request.query_params.get('project') читает query-параметр из URL, например ?project=TIGER. Если параметр не передан — возвращаем все задачи. Если передан — фильтруем через lookup project__name (переход по ForeignKey к полю name).
Решение задачи 14 — TagsSerializer
from rest_framework import serializers
from management_app.models import Tag
class TagsSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
Разбор:
fields = '__all__' включает все поля модели Tag. Обратите внимание на разные способы импорта: здесь from rest_framework import serializers и затем serializers.ModelSerializer, в предыдущих задачах — from rest_framework.serializers import ModelSerializer. Оба варианта корректны.
Решение задачи 15 — View для всех тегов
@api_view(['GET',])
def get_all_tags(request: Request) -> JsonResponse:
tags = Tag.objects.all()
if not tags.exists():
return JsonResponse(
data=[],
status=status.HTTP_204_NO_CONTENT,
safe=False
)
serialized_data = TagsSerializer(tags, many=True)
return JsonResponse(
data=serialized_data.data,
status=status.HTTP_200_OK,
safe=False
)
# management_app/urls.py
from management_app.views import get_all_projects, get_all_tasks, get_all_tags
urlpatterns = [
path('projects/', get_all_projects),
path('tasks/', get_all_tasks),
path('tags/', get_all_tags), # NEW
]
Решение задачи 16 — View тег по id
@api_view(['GET',])
def get_tag_by_id(request: Request, tag_id: int) -> JsonResponse:
try:
tag = Tag.objects.get(id=tag_id)
except Tag.DoesNotExist:
return JsonResponse(
data={},
status=status.HTTP_204_NO_CONTENT
)
serialized_data = TagsSerializer(tag)
return JsonResponse(
data=serialized_data.data,
status=status.HTTP_200_OK,
)
# management_app/urls.py
urlpatterns = [
path('projects/', get_all_projects),
path('tasks/', get_all_tasks),
path('tags/', get_all_tags),
path('tags/<int:tag_id>/', get_tag_by_id), # NEW
]
Разбор:
Tag.objects.get(id=tag_id) — получить один объект по id. Если не найден — бросает исключение Tag.DoesNotExist. Обрабатываем его в try/except и возвращаем 204. URL-паттерн <int:tag_id> автоматически передаёт параметр в функцию.
Решение задачи 17 — View обновление тега (PUT)
@api_view(['PUT',])
def update_required_tag(request: Request, tag_id: int) -> JsonResponse:
try:
tag = Tag.objects.get(id=tag_id)
except Tag.DoesNotExist:
return JsonResponse(
{},
status=status.HTTP_204_NO_CONTENT
)
validated_data = TagsSerializer(tag, request.data)
if validated_data.is_valid(raise_exception=True):
validated_data.save()
return JsonResponse(
validated_data.data,
status=status.HTTP_200_OK,
)
# management_app/urls.py
urlpatterns = [
...
path('tags/<int:tag_id>/update/', update_required_tag), # NEW
]
Разбор:
TagsSerializer(tag, request.data) — создаём сериализатор для обновления: первый аргумент (tag) — существующий объект, второй (request.data) — новые данные. .is_valid(raise_exception=True) бросает ошибку автоматически при ошибке валидации. .save() вызывает update() (а не create()), так как передан экземпляр.
⚠️ Проверить по документации: в современном DRF рекомендуется передавать data явно:
TagsSerializer(tag, data=request.data). Позиционный аргумент работает, но может быть неочевиден.
Решение задачи 18 — View создание тега (POST)
@api_view(['POST',])
def create_new_tag(request: Request) -> JsonResponse:
new_tag = TagsSerializer(data=request.data)
if new_tag.is_valid(raise_exception=True):
new_tag.save()
return JsonResponse(
new_tag.data,
status=status.HTTP_201_CREATED
)
return JsonResponse(
new_tag.errors,
status=status.HTTP_400_BAD_REQUEST
)
# management_app/urls.py
urlpatterns = [
...
path('tags/create/', create_new_tag), # NEW — до <int:tag_id>!
path('tags/<int:tag_id>/', get_tag_by_id),
...
]
Разбор: При создании передаём только
data=request.data (без экземпляра). .save() вызовет create(). Статус 201 Created — стандарт для успешного создания ресурса. Маршрут tags/create/ должен стоять до tags/<int:tag_id>/, иначе Django попытается преобразовать "create" в int.
Решение задачи 19 — View удаление тега (DELETE)
@api_view(['DELETE',])
def delete_required_tag(request: Request, tag_id: int) -> JsonResponse:
try:
tag = Tag.objects.get(id=tag_id)
except Tag.DoesNotExist:
return JsonResponse(
{},
status=status.HTTP_204_NO_CONTENT
)
tag.delete()
return JsonResponse(
{
'message': 'Tag deleted successfully',
},
status=status.HTTP_200_OK
)
# management_app/urls.py
urlpatterns = [
path('projects/', get_all_projects),
path('tasks/', get_all_tasks),
path('tags/', get_all_tags),
path('tags/create/', create_new_tag),
path('tags/<int:tag_id>/', get_tag_by_id),
path('tags/<int:tag_id>/update/', update_required_tag),
path('tags/<int:tag_id>/delete/', delete_required_tag), # NEW
]
Разбор:
tag.delete() удаляет объект из базы данных. После успешного удаления возвращаем 200 с сообщением (в источнике именно 200, хотя REST-стандарт также допускает 204 без тела).
Решение задачи 20 — TaskInfoSerializer (вложенный сериализатор)
from rest_framework import serializers
from management_app.models import Task
from management_app.serializers.tags import TagsSerializer
class TaskInfoSerializer(serializers.ModelSerializer):
tags = TagsSerializer(many=True)
class Meta:
model = Task
fields = [
'id',
'name',
'status',
'priority',
'tags',
'project',
'created_date',
'due_date'
]
Разбор: Поле
tags = TagsSerializer(many=True) переопределяет стандартное поле tags модели. Вместо списка ID тегов, в JSON будет вложенный массив объектов тега (со всеми полями из TagsSerializer). many=True нужен, так как тегов может быть несколько (ManyToMany).
Решение задачи 21 — View детальная информация о задаче
@api_view(['GET',])
def get_task_by_id(request: Request, task_id: int) -> JsonResponse:
try:
task = Task.objects.get(id=task_id)
except Task.DoesNotExist:
return JsonResponse(
{},
status=status.HTTP_204_NO_CONTENT
)
serializer_data = TaskInfoSerializer(task)
return JsonResponse(
serializer_data.data,
status=status.HTTP_200_OK,
)
# management_app/urls.py — финальное состояние
from management_app.views import (
get_all_projects, get_all_tasks, get_all_tags,
get_tag_by_id, update_required_tag,
create_new_tag, delete_required_tag, get_task_by_id
)
urlpatterns = [
path('projects/', get_all_projects),
path('tasks/', get_all_tasks),
path('tasks/<int:task_id>/', get_task_by_id), # NEW
path('tags/', get_all_tags),
path('tags/create/', create_new_tag),
path('tags/<int:tag_id>/', get_tag_by_id),
path('tags/<int:tag_id>/update/', update_required_tag),
path('tags/<int:tag_id>/delete/', delete_required_tag),
]
Разбор: Используем TaskInfoSerializer (не AllTasksSerializer) — он включает вложенные теги и дополнительные поля. Обратите внимание: для одного объекта
many=True не нужен — просто TaskInfoSerializer(task).