🏠 Домашнее задание 14 — Урок 31
⚡ Суть ДЗ 14
- Задание 1: эндпоинт задач по дню недели (
?weekday=вторник) - Задание 2: пагинация подзадач — max 5 на страницу, сортировка по убыванию даты
- Задание 3: фильтрация подзадач по названию главной задачи и/или статусу
Проект «Менеджер задач» из предыдущих ДЗ. Расширяем существующие эндпоинты.
Текст задания из LMS
Задание 1
Написать, или обновить, если уже есть, эндпоинт на получение списка всех задач по дню недели.
- Если никакой параметр запроса не передавался — по умолчанию выводить все записи.
- Если был передан день недели (например вторник) — выводить список задач только на этот день недели.
Задание 2
Добавить пагинацию в отображение списка подзадач. На одну страницу должно отображаться не более 5 объектов. Отображение объектов должно идти в порядке убывания даты (от самого последнего добавленного объекта к самому первому).
Задание 3
Добавить или обновить, если уже есть, эндпоинт на получение списка всех подзадач по названию главной задачи и статусу подзадач.
- Если фильтр-параметры в запросе не передавались — выводить данные по умолчанию, с учётом пагинации.
- Если был передан фильтр-параметр названия главной задачи — выводить данные по этой главной задаче.
- Если был передан фильтр-параметр конкретного статуса подзадачи — выводить данные по этому статусу.
- Если были переданы оба фильтра — выводить данные в соответствии с этими фильтрами.
Подготовка окружения
1. Активация виртуального окружения
# Windows PowerShell
cd C:\path\to\your\project
.\venv\Scripts\Activate.ps1
# Windows CMD
.\venv\Scripts\activate.bat
2. Проверка зависимостей
pip list | findstr -i "django rest"
# Должны быть: Django 5.x, djangorestframework 3.15+
3. Структура проекта
Предполагаемая структура из предыдущих ДЗ:
task_manager/
├── manage.py
├── task_manager/ # основной проект
│ ├── settings.py
│ └── urls.py
└── tasks/ # приложение
├── models.py
├── serializers.py
├── views.py
└── urls.py
4. Проверка моделей
В проекте должны быть модели Task и SubTask. Если нет — создайте:
# tasks/models.py
from django.db import models
class Task(models.Model):
STATUS_CHOICES = [
('new', 'Новая'),
('in_progress', 'В работе'),
('done', 'Выполнена'),
]
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default='new'
)
due_date = models.DateField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class SubTask(models.Model):
STATUS_CHOICES = [
('new', 'Новая'),
('in_progress', 'В работе'),
('done', 'Выполнена'),
]
title = models.CharField(max_length=200)
task = models.ForeignKey(
Task, on_delete=models.CASCADE,
related_name='subtasks'
)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default='new'
)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.task.title} — {self.title}"
5. Миграции
python manage.py makemigrations tasks
python manage.py migrate
6. Git — создание ветки
git checkout -b feature/hw14-query-params
git status
Пошаговое решение
Задание 1: Фильтрация задач по дню недели
Шаг 1.1 — Маппинг дней недели
Django's __week_day использует нумерацию: 1=воскресенье, 2=понедельник, ..., 7=суббота. Создадим маппинг на русские названия.
# tasks/views.py
from django.db.models.functions import ExtractWeekDay
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import Task, SubTask
from .serializers import TaskSerializer, SubTaskSerializer
# Маппинг: русское название -> номер дня в Django (__week_day)
WEEKDAY_MAP = {
'понедельник': 2,
'вторник': 3,
'среда': 4,
'четверг': 5,
'пятница': 6,
'суббота': 7,
'воскресенье': 1,
}
Шаг 1.2 — Реализация APIView
class TaskListView(APIView):
"""Список задач с фильтрацией по дню недели."""
def get(self, request):
weekday = request.query_params.get('weekday')
if weekday:
weekday_lower = weekday.lower()
weekday_num = WEEKDAY_MAP.get(weekday_lower)
if weekday_num is None:
return Response(
{'error': f'Неизвестный день недели: {weekday}. '
f'Допустимые значения: {", ".join(WEEKDAY_MAP.keys())}'},
status=status.HTTP_400_BAD_REQUEST
)
tasks = Task.objects.filter(
due_date__week_day=weekday_num
)
else:
# Параметр не передан — все записи
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
Шаг 1.3 — Сериализатор (если нет)
# tasks/serializers.py
from rest_framework import serializers
from .models import Task, SubTask
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
class SubTaskSerializer(serializers.ModelSerializer):
class Meta:
model = SubTask
fields = '__all__'
Шаг 1.4 — URL-маршрут
# tasks/urls.py
from django.urls import path
from .views import TaskListView
urlpatterns = [
path('tasks/', TaskListView.as_view(), name='task-list'),
]
# task_manager/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('tasks.urls')),
]
Задание 2: Пагинация подзадач (max 5, убывание даты)
Шаг 2.1 — Класс пагинации
# tasks/views.py (добавить в начало)
from rest_framework.pagination import PageNumberPagination
class SubTaskPagination(PageNumberPagination):
page_size = 5 # max 5 объектов на страницу
page_size_query_param = 'page_size' # ?page_size=N
max_page_size = 50
Шаг 2.2 — APIView со списком подзадач
class SubTaskListView(APIView):
"""Список подзадач с пагинацией, сортировка по убыванию даты."""
def get(self, request):
# Сортировка по убыванию created_at (от нового к старому)
subtasks = SubTask.objects.all().order_by('-created_at')
# Пагинация
paginator = SubTaskPagination()
result_page = paginator.paginate_queryset(
subtasks, request, view=self
)
if result_page is not None:
serializer = SubTaskSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = SubTaskSerializer(subtasks, many=True)
return Response(serializer.data)
Шаг 2.3 — URL
# tasks/urls.py
urlpatterns = [
path('tasks/', TaskListView.as_view(), name='task-list'),
path('subtasks/', SubTaskListView.as_view(), name='subtask-list'),
]
Задание 3: Фильтрация подзадач по названию задачи и/или статусу
Шаг 3.1 — Расширяем SubTaskListView
class SubTaskListView(APIView):
"""
Список подзадач: фильтрация по task__title и/или status,
сортировка по убыванию даты, пагинация max 5.
"""
def get(self, request):
filters = {}
# Фильтр по названию главной задачи
task_title = request.query_params.get('task_title')
if task_title:
filters['task__title__icontains'] = task_title
# Фильтр по статусу подзадачи
sub_status = request.query_params.get('status')
if sub_status:
filters['status'] = sub_status
# Применяем фильтры и сортируем
subtasks = SubTask.objects.filter(**filters).order_by('-created_at')
# Пагинация
paginator = SubTaskPagination()
result_page = paginator.paginate_queryset(
subtasks, request, view=self
)
if result_page is not None:
serializer = SubTaskSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = SubTaskSerializer(subtasks, many=True)
return Response(serializer.data)
Примеры URL-запросов (задание 3)
# Все подзадачи (с пагинацией)
GET http://127.0.0.1:8000/api/subtasks/
# По названию главной задачи
GET http://127.0.0.1:8000/api/subtasks/?task_title=Покупки
# По статусу
GET http://127.0.0.1:8000/api/subtasks/?status=done
# По обоим фильтрам
GET http://127.0.0.1:8000/api/subtasks/?task_title=Покупки&status=in_progress
# С кастомным размером страницы
GET http://127.0.0.1:8000/api/subtasks/?page=2&page_size=3
Полный views.py
# tasks/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.pagination import PageNumberPagination
from .models import Task, SubTask
from .serializers import TaskSerializer, SubTaskSerializer
WEEKDAY_MAP = {
'понедельник': 2, 'вторник': 3, 'среда': 4,
'четверг': 5, 'пятница': 6, 'суббота': 7, 'воскресенье': 1,
}
class SubTaskPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 50
class TaskListView(APIView):
def get(self, request):
weekday = request.query_params.get('weekday')
if weekday:
weekday_num = WEEKDAY_MAP.get(weekday.lower())
if weekday_num is None:
return Response(
{'error': f'Неизвестный день: {weekday}'},
status=status.HTTP_400_BAD_REQUEST
)
tasks = Task.objects.filter(due_date__week_day=weekday_num)
else:
tasks = Task.objects.all()
serializer = TaskSerializer(tasks, many=True)
return Response(serializer.data)
class SubTaskListView(APIView):
def get(self, request):
filters = {}
task_title = request.query_params.get('task_title')
sub_status = request.query_params.get('status')
if task_title:
filters['task__title__icontains'] = task_title
if sub_status:
filters['status'] = sub_status
subtasks = SubTask.objects.filter(**filters).order_by('-created_at')
paginator = SubTaskPagination()
result_page = paginator.paginate_queryset(
subtasks, request, view=self
)
if result_page is not None:
serializer = SubTaskSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = SubTaskSerializer(subtasks, many=True)
return Response(serializer.data)
Проверка в VS Code
Запуск сервера через терминал
# В терминале VS Code (Ctrl+`)
cd C:\path\to\project
.\venv\Scripts\Activate.ps1
python manage.py runserver
Сервер запустится на http://127.0.0.1:8000/.
Запуск через F5 (launch.json)
Создайте файл .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Django runserver",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver"],
"django": true,
"justMyCode": true
}
]
}
Нажмите F5 — сервер запустится с поддержкой точек останова.
Точки останова для отладки
Поставьте breakpoint на строку tasks = Task.objects.filter(**filters) или subtasks = SubTask.objects.filter(**filters).order_by('-created_at') в views.py и выполните запрос в Postman — VS Code остановится на этой строке, можно проверить содержимое filters.
Проверка через Postman
Задание 1 — тесты
# Все задачи
GET http://127.0.0.1:8000/api/tasks/
# Ожидаем: все объекты Task
# Задачи на вторник
GET http://127.0.0.1:8000/api/tasks/?weekday=вторник
# Ожидаем: только задачи с due_date в вторник
# Неверный день недели
GET http://127.0.0.1:8000/api/tasks/?weekday=воскресенье1
# Ожидаем: 400 Bad Request с описанием ошибки
Задание 2 — тесты
# Первая страница (5 объектов, от новых к старым)
GET http://127.0.0.1:8000/api/subtasks/
# Ожидаем: count, next, previous, results (max 5)
# Вторая страница
GET http://127.0.0.1:8000/api/subtasks/?page=2
# Ожидаем: следующие 5 объектов
# Кастомный размер
GET http://127.0.0.1:8000/api/subtasks/?page_size=3
Задание 3 — тесты
# По названию задачи
GET http://127.0.0.1:8000/api/subtasks/?task_title=Покупки
# По статусу
GET http://127.0.0.1:8000/api/subtasks/?status=done
# Оба фильтра
GET http://127.0.0.1:8000/api/subtasks/?task_title=Покупки&status=in_progress
# Без фильтров (пагинация должна работать)
GET http://127.0.0.1:8000/api/subtasks/
Добавление тестовых данных через Django Shell
python manage.py shell
from tasks.models import Task, SubTask
from datetime import date
# Создаём задачи с разными днями недели
t1 = Task.objects.create(
title='Покупки', status='new',
due_date=date(2025, 6, 10) # вторник
)
t2 = Task.objects.create(
title='Работа', status='in_progress',
due_date=date(2025, 6, 11) # среда
)
# Создаём подзадачи
SubTask.objects.create(title='Молоко', task=t1, status='new')
SubTask.objects.create(title='Хлеб', task=t1, status='done')
SubTask.objects.create(title='Отчёт', task=t2, status='in_progress')
SubTask.objects.create(title='Презентация', task=t2, status='new')
print("Данные созданы")
Связь с теорией
- Задание 1 использует
due_date__week_day— встроенный lookup из теории (Прямая фильтрация). Маппинг необходим из-за нестандартной нумерации Django (1=воскресенье). - Задание 2 применяет
PageNumberPaginationиorder_by('-created_at')— паттерн из теории (Пагинация). Сортировка по убыванию — через префикс-. - Задание 3 применяет паттерн
filters = {}из теории (Фильтрация). Фильтрtask__title__icontains— фильтрация по связанному полю с регистронезависимым поиском. - Примеры кода из раздела Примеры — прямой аналог задания 1.