🏠 Домашнее задание 12 — Урок 26

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

📋 Проект: Менеджер задач — API ⏱️ Расчётное время: ~90 мин

⚡ Суть ДЗ 12

Создать три API-эндпоинта для менеджера задач с помощью DRF:

  • POST /tasks/ — создание задачи (title, description, status, deadline)
  • GET /tasks/ и GET /tasks/<id>/ — список и конкретная задача
  • GET /tasks/stats/ — агрегирующий эндпоинт (total, по статусам, просроченные)

Сдать: код эндпоинтов + скриншоты Postman.

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

Домашнее задание: Проект "Менеджер задач" — Создание API для управления задачами
Цель: Освоить работу с Django REST Framework для создания, получения и агрегирования данных, используя модели задач.

Задание 1: Эндпоинт для создания задачи

Создайте эндпоинт для создания новой задачи. Задача должна быть создана с полями title, description, status, и deadline.

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

  1. Определите сериализатор для модели Task.
  2. Создайте представление для создания задачи.
  3. Создайте маршрут для обращения к представлению.

Задание 2: Эндпоинты для получения списка задач и конкретной задачи по её ID

Создайте два новых эндпоинта для:

  • Получения списка задач
  • Получения конкретной задачи по её уникальному ID

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

  1. Создайте представления для получения списка задач и конкретной задачи.
  2. Создайте маршруты для обращения к представлениям.

Задание 3: Агрегирующий эндпоинт для статистики задач

Создайте эндпоинт для получения статистики задач, таких как общее количество задач, количество задач по каждому статусу и количество просроченных задач.

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

  1. Определите представление для агрегирования данных о задачах.
  2. Создайте маршрут для обращения к представлению.
Оформите ваш ответ следующим образом:
  • Код эндпоинтов: Вставьте весь код представлений и маршрутов.
  • Скриншоты ручного тестирования: Приложите скриншоты консоли или Postman, подтверждающие успешное выполнение запросов для каждого эндпоинта.

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

Шаг 1: Создание виртуального окружения

# Создаём папку проекта
mkdir task_manager_api
cd task_manager_api

# Создаём venv
python -m venv venv

# Активируем (Windows PowerShell)
.\venv\Scripts\Activate.ps1

# Если ошибка ExecutionPolicy — выполните:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Шаг 2: Установка зависимостей

pip install django djangorestframework

# Фиксируем зависимости
pip freeze > requirements.txt

Шаг 3: Создание Django-проекта и приложения

django-admin startproject config .
python manage.py startapp tasks

Шаг 4: Настройка settings.py

# config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # DRF
    'tasks',           # наше приложение
]

Шаг 5: Git-инициализация

git init
git add .
git commit -m "initial: Django project with DRF"

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

Шаг 1: Модель Task

Связь с теорией: структура модели — основа для ModelSerializer (см. theory.html#serialization).

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

class Task(models.Model):
    STATUS_CHOICES = [
        ('todo', 'To Do'),
        ('in_progress', 'In Progress'),
        ('done', 'Done'),
    ]

    title = models.CharField(max_length=200)
    description = models.TextField(blank=True, default='')
    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        default='todo'
    )
    deadline = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

    @property
    def is_overdue(self):
        """Задача просрочена, если deadline в прошлом и статус не 'done'."""
        if self.deadline and self.status != 'done':
            return self.deadline < timezone.now().date()
        return False

Шаг 2: Миграции

python manage.py makemigrations tasks
python manage.py migrate

Шаг 3: Сериализатор

Связь с теорией: ModelSerializer автоматически строит поля из модели (см. theory.html#serialization).

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

class TaskSerializer(serializers.ModelSerializer):
    is_overdue = serializers.BooleanField(read_only=True)

    class Meta:
        model = Task
        fields = [
            'id',
            'title',
            'description',
            'status',
            'deadline',
            'created_at',
            'is_overdue',
        ]
        read_only_fields = ['id', 'created_at', 'is_overdue']

Шаг 4: Представления (Views)

Связь с примерами: паттерн @api_view + Response (см. examples.html — Пример 12).

# tasks/views.py
from django.utils import timezone
from django.db.models import Count, Q

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

from .models import Task
from .serializers import TaskSerializer


# === Задание 1: Создание задачи ===

@api_view(['POST'])
def task_create(request):
    """POST /api/tasks/ — создание новой задачи."""
    serializer = TaskSerializer(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)


# === Задание 2: Список и конкретная задача ===

@api_view(['GET'])
def task_list(request):
    """GET /api/tasks/ — список всех задач."""
    tasks = Task.objects.all().order_by('-created_at')
    serializer = TaskSerializer(tasks, many=True)
    return Response(serializer.data)


@api_view(['GET'])
def task_detail(request, pk):
    """GET /api/tasks/<pk>/ — получение задачи по ID."""
    try:
        task = Task.objects.get(pk=pk)
    except Task.DoesNotExist:
        return Response(
            {'error': f'Task with id={pk} not found'},
            status=status.HTTP_404_NOT_FOUND
        )
    serializer = TaskSerializer(task)
    return Response(serializer.data)


# === Задание 3: Агрегирующий эндпоинт ===

@api_view(['GET'])
def task_stats(request):
    """GET /api/tasks/stats/ — статистика по задачам.

    Возвращает:
    - total: общее количество задач
    - by_status: количество задач по каждому статусу
    - overdue: количество просроченных задач (deadline < today, status != 'done')
    """
    today = timezone.now().date()

    # Общее количество
    total = Task.objects.count()

    # По статусам — используем annotate (тема урока 26)
    by_status_qs = Task.objects.values('status').annotate(
        count=Count('id')
    )
    by_status = {entry['status']: entry['count'] for entry in by_status_qs}

    # Просроченные задачи — фильтрация через Q-объекты
    overdue_count = Task.objects.filter(
        deadline__lt=today,
        status__in=['todo', 'in_progress']
    ).count()

    return Response({
        'total': total,
        'by_status': by_status,
        'overdue': overdue_count,
    })

Шаг 5: URL-маршруты

# tasks/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('tasks/', views.task_list, name='task-list'),
    path('tasks/create/', views.task_create, name='task-create'),
    path('tasks/stats/', views.task_stats, name='task-stats'),
    path('tasks/<int:pk>/', views.task_detail, name='task-detail'),
]
# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('tasks.urls')),
]

Шаг 6: Запуск сервера

python manage.py runserver

🔍 Проверка в VS Code и Postman

Запуск через VS Code (F5)

Создайте файл .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 — сервер запустится в режиме отладки. Для точек останова: кликните слева от номера строки в views.py, затем выполните запрос — VS Code остановится на точке.

Тестирование через Postman

Тест 1: Создание задачи (POST)

  • Метод: POST
  • URL: http://127.0.0.1:8000/api/tasks/create/
  • Headers: Content-Type: application/json
  • Body (raw JSON):
    {
      "title": "Изучить DRF сериализаторы",
      "description": "Прочитать документацию и написать первый serializer",
      "status": "todo",
      "deadline": "2026-06-30"
    }
  • Ожидаемый ответ: 201 Created с JSON-объектом задачи

Тест 2: Список задач (GET)

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

Тест 3: Конкретная задача (GET by ID)

  • Метод: GET
  • URL: http://127.0.0.1:8000/api/tasks/1/
  • Ожидаемый ответ: 200 OK с объектом задачи
  • Несуществующий ID: GET /api/tasks/999/404 Not Found

Тест 4: Агрегирующий эндпоинт (GET stats)

  • Метод: GET
  • URL: http://127.0.0.1:8000/api/tasks/stats/
  • Ожидаемый ответ:
    {
      "total": 3,
      "by_status": {
        "todo": 2,
        "in_progress": 1,
        "done": 0
      },
      "overdue": 1
    }

Тестирование через терминал (curl / Python requests)

# Создать задачу
curl -X POST http://127.0.0.1:8000/api/tasks/create/ \
  -H "Content-Type: application/json" \
  -d '{"title": "Test task", "status": "todo"}'

# Список задач
curl http://127.0.0.1:8000/api/tasks/

# Статистика
curl http://127.0.0.1:8000/api/tasks/stats/

Django Admin (опционально)

# tasks/admin.py
from django.contrib import admin
from .models import Task

@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    list_display = ['title', 'status', 'deadline', 'created_at']
    list_filter = ['status']
    search_fields = ['title', 'description']
# Создать суперпользователя и войти в /admin/
python manage.py createsuperuser