🏠 Домашнее задание 15 — Урок 33

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

📋 Проект: Менеджер задач — Generic Views, фильтрация, поиск, сортировка ⏱️ Расчётное время: ~90–120 мин

⚡ Суть ДЗ 15

Два задания на замену FBV/классов представлений на Generic Views + фильтрацию/поиск/сортировку в проекте «Менеджер задач»:

  • Задание 1: Tasks — ListCreateAPIView + RetrieveUpdateDestroyAPIView + фильтрация по status/deadline, поиск по title/description, сортировка по created_at
  • Задание 2: SubTasks — то же самое для подзадач
  • Агрегирующий эндпойнт статистики — оставить как есть
  • Сдать: ссылку на git + скриншоты из Postman

📋 Текст задания (из LMS)

Домашнее задание: Замена функций представлений на Generic Views для задач и подзадач

Используя Generic Views, замените существующие классы представлений для задач (Tasks) и подзадач (SubTasks) на соответствующие классы для полного CRUD (Create, Read, Update, Delete) функционала. Агрегирующий эндпойнт для статистики задач оставьте как есть. Реализуйте фильтрацию, поиск и сортировку для этих наборов представлений.

Задание 1: Замена представлений для задач (Tasks) на Generic Views

Шаги для выполнения:

  1. Замените классы представлений для задач на Generic Views:
    • Используйте ListCreateAPIView для создания и получения списка задач.
    • Используйте RetrieveUpdateDestroyAPIView для получения, обновления и удаления задач.
  2. Реализуйте фильтрацию, поиск и сортировку:
    • Реализуйте фильтрацию по полям status и deadline.
    • Реализуйте поиск по полям title и description.
    • Добавьте сортировку по полю created_at.

Задание 2: Замена представлений для подзадач (SubTasks) на Generic Views

  1. Замените классы представлений для подзадач на Generic Views:
    • Используйте ListCreateAPIView для создания и получения списка подзадач.
    • Используйте RetrieveUpdateDestroyAPIView для получения, обновления и удаления подзадач.
  2. Реализуйте фильтрацию, поиск и сортировку:
    • Реализуйте фильтрацию по полям status и deadline.
    • Реализуйте поиск по полям title и description.
    • Добавьте сортировку по полю created_at.

Оформление ответа

  1. Предоставьте решение: прикрепите ссылку на git.
  2. Скриншоты тестирования: приложите скриншоты из браузера или Postman, подтверждающие успешное создание, обновление, получение и удаление данных через API.

🔧 Подготовка окружения

Структура проекта (предполагаем, что проект уже существует)

task_manager/
├── manage.py
├── task_manager/
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── tasks/
    ├── models.py
    ├── serializers.py
    ├── views.py
    └── urls.py

1. Активация виртуального окружения

# Windows PowerShell
cd task_manager
.\.venv\Scripts\Activate.ps1

# Проверка
python -m pip list | Select-String "djangorestframework"

2. Установка django-filter (если не установлен)

pip install django-filter
pip freeze > requirements.txt

3. Добавление в settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'django_filters',   # ← добавить
    'tasks',
]

4. Проверка моделей Task и SubTask

Убедитесь, что в tasks/models.py есть поля status, deadline, title, description, created_at. Примерная структура:

# tasks/models.py
from django.db import models

class Task(models.Model):
    STATUS_CHOICES = [
        ('New', 'New'),
        ('In progress', 'In progress'),
        ('Done', 'Done'),
    ]
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='New')
    deadline = models.DateTimeField(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', 'New'),
        ('In progress', 'In progress'),
        ('Done', 'Done'),
    ]
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='New')
    deadline = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    task = models.ForeignKey(Task, on_delete=models.CASCADE,
                             related_name='subtasks')

    def __str__(self):
        return self.title

Если структура изменялась — создайте и примените миграции:

python manage.py makemigrations
python manage.py migrate

💡 Пошаговое решение

Шаг 1: Обновить сериализаторы (если нужно)

Убедитесь, что сериализаторы покрывают все нужные поля. Если их ещё нет — создайте:

# tasks/serializers.py
from rest_framework import serializers
from .models import Task, SubTask


class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'description', 'status', 'deadline', 'created_at']
        read_only_fields = ['created_at']


class SubTaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = SubTask
        fields = ['id', 'title', 'description', 'status', 'deadline',
                  'created_at', 'task']
        read_only_fields = ['created_at']

Связь с теорией: см. Часть 4 теории — filter_backends.

Шаг 2: Заменить представления Tasks на Generic Views

# tasks/views.py
from rest_framework.generics import (
    ListCreateAPIView,
    RetrieveUpdateDestroyAPIView,
)
from rest_framework import filters
from django_filters.rest_framework import DjangoFilterBackend
from .models import Task, SubTask
from .serializers import TaskSerializer, SubTaskSerializer

# ────── Tasks ──────

class TaskListCreateView(ListCreateAPIView):
    """
    GET  /tasks/        — список задач (с фильтрацией/поиском/сортировкой)
    POST /tasks/        — создание задачи
    """
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    filter_backends = [
        DjangoFilterBackend,     # ?status=New, ?deadline=2025-12-31
        filters.SearchFilter,    # ?search=текст
        filters.OrderingFilter,  # ?ordering=created_at или ?ordering=-created_at
    ]
    filterset_fields = ['status', 'deadline']
    search_fields = ['title', 'description']
    ordering_fields = ['created_at']


class TaskDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    """
    GET    /tasks/<pk>/  — получение задачи
    PUT    /tasks/<pk>/  — полное обновление
    PATCH  /tasks/<pk>/  — частичное обновление
    DELETE /tasks/<pk>/  — удаление
    """
    queryset = Task.objects.all()
    serializer_class = TaskSerializer


# ────── SubTasks ──────

class SubTaskListCreateView(ListCreateAPIView):
    """
    GET  /subtasks/     — список подзадач
    POST /subtasks/     — создание подзадачи
    """
    queryset = SubTask.objects.all()
    serializer_class = SubTaskSerializer
    filter_backends = [
        DjangoFilterBackend,
        filters.SearchFilter,
        filters.OrderingFilter,
    ]
    filterset_fields = ['status', 'deadline']
    search_fields = ['title', 'description']
    ordering_fields = ['created_at']


class SubTaskDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    """
    GET    /subtasks/<pk>/  — получение подзадачи
    PUT    /subtasks/<pk>/  — полное обновление
    PATCH  /subtasks/<pk>/  — частичное обновление
    DELETE /subtasks/<pk>/  — удаление
    """
    queryset = SubTask.objects.all()
    serializer_class = SubTaskSerializer

Шаг 3: Обновить URL-маршруты

# tasks/urls.py
from django.urls import path
from .views import (
    TaskListCreateView,
    TaskDetailUpdateDeleteView,
    SubTaskListCreateView,
    SubTaskDetailUpdateDeleteView,
    # Агрегирующий эндпойнт статистики оставляем как есть:
    # TaskStatsView,
)

urlpatterns = [
    # Tasks
    path('tasks/', TaskListCreateView.as_view(), name='task-list-create'),
    path('tasks/<int:pk>/', TaskDetailUpdateDeleteView.as_view(),
         name='task-detail-update-delete'),

    # SubTasks
    path('subtasks/', SubTaskListCreateView.as_view(),
         name='subtask-list-create'),
    path('subtasks/<int:pk>/', SubTaskDetailUpdateDeleteView.as_view(),
         name='subtask-detail-update-delete'),

    # Агрегирующий эндпойнт (не трогаем)
    # path('tasks/stats/', TaskStatsView.as_view(), name='task-stats'),
]

Шаг 4: Запустить сервер и проверить

python manage.py runserver

Открыть в браузере DRF Browsable API:

  • http://127.0.0.1:8000/tasks/ — список задач
  • http://127.0.0.1:8000/subtasks/ — список подзадач

🔬 Проверка в Postman

Связь с теорией: см. Пример 4 — filter_backends.

Создание задачи (POST)

POST http://127.0.0.1:8000/tasks/
Content-Type: application/json

{
    "title": "Изучить DRF фильтрацию",
    "description": "Изучить filter_backends, search_fields, ordering_fields",
    "status": "New",
    "deadline": "2025-12-31T23:59:00Z"
}

Ожидаемый ответ: 201 Created
{
    "id": 1,
    "title": "Изучить DRF фильтрацию",
    "description": "...",
    "status": "New",
    "deadline": "2025-12-31T23:59:00Z",
    "created_at": "2025-06-09T10:00:00Z"
}

Получение списка (GET)

GET http://127.0.0.1:8000/tasks/
Ожидаемый ответ: 200 OK — массив всех задач

Фильтрация по статусу

GET http://127.0.0.1:8000/tasks/?status=New
Ожидаемый ответ: только задачи со статусом "New"

Поиск по тексту

GET http://127.0.0.1:8000/tasks/?search=DRF
Ожидаемый ответ: задачи, где title или description содержит "DRF"

Сортировка по дате создания

# По убыванию (новые сначала)
GET http://127.0.0.1:8000/tasks/?ordering=-created_at

# По возрастанию (старые сначала)
GET http://127.0.0.1:8000/tasks/?ordering=created_at

Получение конкретной задачи (GET)

GET http://127.0.0.1:8000/tasks/1/
Ожидаемый ответ: 200 OK — объект задачи

Частичное обновление (PATCH)

PATCH http://127.0.0.1:8000/tasks/1/
Content-Type: application/json

{"status": "In progress"}

Ожидаемый ответ: 200 OK — обновлённый объект

Удаление (DELETE)

DELETE http://127.0.0.1:8000/tasks/1/
Ожидаемый ответ: 204 No Content

Те же тесты повторить для SubTasks

Замените /tasks/ на /subtasks/ во всех запросах выше.

💻 Отладка в VS Code

Запуск через терминал

# В терминале VS Code (Ctrl+`)
.\.venv\Scripts\Activate.ps1
python manage.py runserver

Запуск через 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 — сервер запустится с поддержкой отладки.

Точки останова

Для отладки filter_backends поставьте точку останова в методе get_queryset() или в методе filter_queryset():

  1. Откройте tasks/views.py
  2. Кликните слева от номера строки в методе класса (например, на строке queryset = Task.objects.all())
  3. Нажмите F5 и отправьте запрос из Postman
  4. VS Code остановится на точке — можно просмотреть self.request.query_params в панели Variables

Проверка установки пакетов

# В терминале VS Code
pip show django-filter
# Должно показать версию и расположение пакета

📤 Оформление ответа

1. Коммит в git

# Добавить изменения
git add tasks/views.py tasks/urls.py tasks/serializers.py requirements.txt

# Коммит
git commit -m "feat: замена FBV на Generic Views для Tasks и SubTasks (ДЗ 15)

- TaskListCreateView и TaskDetailUpdateDeleteView с filter_backends
- SubTaskListCreateView и SubTaskDetailUpdateDeleteView с filter_backends
- Фильтрация по status, deadline; поиск по title, description; сортировка по created_at
"

# Пуш на GitHub
git push origin main

2. Скриншоты для сдачи

Сделайте скриншоты из Postman, подтверждающие:

  1. POST /tasks/ — успешное создание (код 201)
  2. GET /tasks/ — список задач
  3. GET /tasks/?status=New — фильтрация
  4. GET /tasks/?search=... — поиск
  5. GET /tasks/?ordering=-created_at — сортировка
  6. PATCH /tasks/1/ — обновление (код 200)
  7. DELETE /tasks/1/ — удаление (код 204)
  8. Те же операции для SubTasks