🏠 Домашнее задание 13 — Урок 29
⚡ Суть ДЗ 13
Пять заданий на углубление работы с сериализаторами и CBV в DRF (проект «Менеджер задач»):
- 1: SubTaskCreateSerializer — поле created_at как read_only
- 2: CategoryCreateSerializer — методы create/update с проверкой уникальности
- 3: TaskDetailSerializer — вложенный сериализатор для SubTask
- 4: TaskCreateSerializer — валидация поля deadline (дата не в прошлом)
- 5: SubTaskListCreateView + SubTaskDetailUpdateDeleteView на APIView + маршруты
Сдать: код сериализаторов и представлений + скриншоты Postman или VS Code.
📋 Текст задания из LMS
Цель: Освоить настройку сериализаторов для работы с подзадачами и категориями, включая переопределение полей, использование вложенных сериализаторов, методов create и update, а также классы представлений.
Задание 1: Переопределение полей сериализатора
Создайте SubTaskCreateSerializer, в котором поле created_at будет доступно только для чтения (read_only).
Шаги для выполнения:
- Определите
SubTaskCreateSerializerв файлеserializers.py. - Переопределите поле
created_atкакread_only.
Задание 2: Переопределение методов create и update
Создайте сериализатор для категории CategoryCreateSerializer, переопределив методы create и update для проверки уникальности названия категории. Если категория с таким названием уже существует, возвращайте ошибку валидации.
Шаги для выполнения:
- Определите
CategoryCreateSerializerв файлеserializers.py. - Переопределите метод
createдля проверки уникальности названия категории. - Переопределите метод
updateдля аналогичной проверки при обновлении.
Задание 3: Использование вложенных сериализаторов
Создайте сериализатор для TaskDetailSerializer, который включает вложенный сериализатор для полного отображения связанных подзадач (SubTask). Сериализатор должен показывать все подзадачи, связанные с данной задачей.
Шаги для выполнения:
- Определите
TaskDetailSerializerв файлеserializers.py. - Вложите
SubTaskSerializerвнутрьTaskDetailSerializer.
Задание 4: Валидация данных в сериализаторах
Создайте TaskCreateSerializer и добавьте валидацию для поля deadline, чтобы дата не могла быть в прошлом. Если дата в прошлом, возвращайте ошибку валидации.
Шаги для выполнения:
- Определите
TaskCreateSerializerв файлеserializers.py. - Переопределите метод
validate_deadlineдля проверки даты.
Задание 5: Создание классов представлений
Создайте классы представлений для работы с подзадачами (SubTasks), включая создание, получение, обновление и удаление подзадач. Используйте классы представлений (APIView) для реализации этого функционала.
Шаги для выполнения:
- Создайте класс представления для создания и получения списка подзадач (
SubTaskListCreateView). - Создайте класс представления для получения, обновления и удаления подзадач (
SubTaskDetailUpdateDeleteView). - Добавьте маршруты в файле
urls.py, чтобы использовать эти классы.
- Код сериализаторов и представлений: вставьте весь код из
serializers.pyиviews.py. - Скриншоты ручного тестирования: приложите скриншоты консоли или Postman, подтверждающие успешное выполнение запросов.
🛠️ Подготовка окружения
Шаг 1: Создание виртуального окружения и проекта
Если продолжаете проект из ДЗ 12 — пропустите этот шаг. Если начинаете с нуля:
# Создаём папку проекта
mkdir task_manager
cd task_manager
# Создаём 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-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 + DRF task manager"
📐 Модели проекта
ДЗ 13 предполагает, что модели уже созданы (из ДЗ 12). Для нового проекта — создайте их:
# tasks/models.py
from django.db import models
from django.utils import timezone
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.name
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)
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='tasks'
)
def __str__(self):
return self.title
class SubTask(models.Model):
STATUS_CHOICES = Task.STATUS_CHOICES
title = models.CharField(max_length=200)
description = models.TextField(blank=True, default='')
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='todo'
)
task = models.ForeignKey(
Task,
on_delete=models.CASCADE,
related_name='subtasks'
)
created_at = models.DateTimeField(auto_now_add=True)
deadline = models.DateField(null=True, blank=True)
def __str__(self):
return f"{self.title} (подзадача к: {self.task.title})"
# После создания моделей:
python manage.py makemigrations tasks
python manage.py migrate
✅ Пошаговое решение
Задание 1: SubTaskCreateSerializer — поле read_only
Связь с теорией: read_only_fields и явное переопределение поля в сериализаторе (см. theory.html — Поля для отношений).
# tasks/serializers.py
from rest_framework import serializers
from .models import SubTask, Task, Category
from django.utils import timezone
class SubTaskCreateSerializer(serializers.ModelSerializer):
"""
Задание 1: поле created_at — только для чтения.
DRF не позволит клиенту установить created_at при POST/PUT.
"""
created_at = serializers.DateTimeField(read_only=True)
class Meta:
model = SubTask
fields = ['id', 'title', 'description', 'status', 'task', 'deadline', 'created_at']
Альтернативный способ через read_only_fields:
class SubTaskCreateSerializer(serializers.ModelSerializer):
class Meta:
model = SubTask
fields = ['id', 'title', 'description', 'status', 'task', 'deadline', 'created_at']
read_only_fields = ['created_at'] # то же самое, короче
Задание 2: CategoryCreateSerializer — методы create и update
Связь с теорией: переопределение методов сохранения в сериализаторе (см. theory.html).
class CategoryCreateSerializer(serializers.ModelSerializer):
"""
Задание 2: уникальность названия категории при create и update.
"""
class Meta:
model = Category
fields = ['id', 'name']
def create(self, validated_data):
name = validated_data.get('name')
# Проверяем уникальность при создании
if Category.objects.filter(name=name).exists():
raise serializers.ValidationError(
{'name': f'Категория с названием "{name}" уже существует.'}
)
return super().create(validated_data)
def update(self, instance, validated_data):
name = validated_data.get('name', instance.name)
# Проверяем уникальность при обновлении (исключаем текущий объект)
if Category.objects.filter(name=name).exclude(pk=instance.pk).exists():
raise serializers.ValidationError(
{'name': f'Категория с названием "{name}" уже существует.'}
)
return super().update(instance, validated_data)
Задание 3: TaskDetailSerializer — вложенный сериализатор
Связь с примерами: вложенные сериализаторы для отображения связанных объектов (см. examples.html).
class SubTaskSerializer(serializers.ModelSerializer):
"""Базовый сериализатор для SubTask — используется как вложенный."""
class Meta:
model = SubTask
fields = ['id', 'title', 'description', 'status', 'deadline', 'created_at']
class TaskDetailSerializer(serializers.ModelSerializer):
"""
Задание 3: TaskDetail — включает все подзадачи полностью.
subtasks — вложенный сериализатор, many=True, read_only=True.
"""
subtasks = SubTaskSerializer(many=True, read_only=True)
class Meta:
model = Task
fields = ['id', 'title', 'description', 'status', 'deadline', 'created_at', 'category', 'subtasks']
subtasks— имя совпадает сrelated_name='subtasks'в модели SubTaskmany=True— SubTask их может быть несколькоread_only=True— подзадачи создаются отдельно, не через TaskDetail
Задание 4: TaskCreateSerializer — валидация deadline
Связь с теорией: методы validate_<field>() для валидации отдельных полей (см. theory.html).
class TaskCreateSerializer(serializers.ModelSerializer):
"""
Задание 4: deadline не может быть в прошлом.
"""
class Meta:
model = Task
fields = ['id', 'title', 'description', 'status', 'deadline', 'category']
def validate_deadline(self, value):
"""
Валидатор для поля deadline.
DRF автоматически вызывает validate_ перед сохранением.
"""
if value and value < timezone.now().date():
raise serializers.ValidationError(
'Дата дедлайна не может быть в прошлом.'
)
return value
Задание 5: SubTaskListCreateView + SubTaskDetailUpdateDeleteView
Связь с теорией: APIView — методы get/post/put/delete (см. theory.html#apiview).
# tasks/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .models import SubTask, Task
from .serializers import SubTaskCreateSerializer, SubTaskSerializer
class SubTaskListCreateView(APIView):
"""
Задание 5a: список и создание подзадач.
GET /api/subtasks/ — список всех подзадач
POST /api/subtasks/ — создание новой подзадачи
"""
def get(self, request):
subtasks = SubTask.objects.select_related('task').all()
serializer = SubTaskSerializer(subtasks, many=True)
return Response(serializer.data)
def post(self, request):
serializer = SubTaskCreateSerializer(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)
class SubTaskDetailUpdateDeleteView(APIView):
"""
Задание 5b: получение, обновление и удаление одной подзадачи.
GET /api/subtasks/{pk}/ — одна подзадача
PUT /api/subtasks/{pk}/ — полное обновление
PATCH /api/subtasks/{pk}/ — частичное обновление
DELETE /api/subtasks/{pk}/ — удаление
"""
def get_object(self, pk):
try:
return SubTask.objects.get(pk=pk)
except SubTask.DoesNotExist:
return None
def get(self, request, pk):
subtask = self.get_object(pk)
if subtask is None:
return Response(
{'error': f'SubTask с id={pk} не найден.'},
status=status.HTTP_404_NOT_FOUND
)
serializer = SubTaskSerializer(subtask)
return Response(serializer.data)
def put(self, request, pk):
subtask = self.get_object(pk)
if subtask is None:
return Response(
{'error': f'SubTask с id={pk} не найден.'},
status=status.HTTP_404_NOT_FOUND
)
serializer = SubTaskCreateSerializer(subtask, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, pk):
subtask = self.get_object(pk)
if subtask is None:
return Response(
{'error': f'SubTask с id={pk} не найден.'},
status=status.HTTP_404_NOT_FOUND
)
serializer = SubTaskCreateSerializer(subtask, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
subtask = self.get_object(pk)
if subtask is None:
return Response(
{'error': f'SubTask с id={pk} не найден.'},
status=status.HTTP_404_NOT_FOUND
)
subtask.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Маршруты (urls.py)
# tasks/urls.py
from django.urls import path
from .views import SubTaskListCreateView, SubTaskDetailUpdateDeleteView
urlpatterns = [
path('subtasks/', SubTaskListCreateView.as_view(), name='subtask-list-create'),
path('subtasks/<int:pk>/', SubTaskDetailUpdateDeleteView.as_view(), name='subtask-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')),
]
Запуск сервера
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 или serializers.py, выполните запрос — VS Code остановится на точке и покажет значения переменных.
Тестирование через Postman
Тест 1: Создание подзадачи (Задание 5)
- Метод:
POST - URL:
http://127.0.0.1:8000/api/subtasks/ - Headers:
Content-Type: application/json - Body (raw JSON):
{ "title": "Написать тесты для API", "description": "Unit тесты на pytest", "status": "todo", "task": 1, "deadline": "2026-07-15" } - Ожидаемый ответ:
201 Created—created_atвыставляется сервером, в запросе игнорируется (Задание 1)
Тест 2: Список подзадач (GET)
- Метод:
GET - URL:
http://127.0.0.1:8000/api/subtasks/ - Ожидаемый ответ:
200 OK— массив подзадач
Тест 3: Валидация deadline в прошлом (Задание 4)
- Метод:
POST - URL:
http://127.0.0.1:8000/api/tasks/(если добавили TaskCreateSerializer в views) - Body:
{ "title": "Просроченная задача", "status": "todo", "deadline": "2020-01-01" } - Ожидаемый ответ:
400 Bad Request:{ "deadline": ["Дата дедлайна не может быть в прошлом."] }
Тест 4: Уникальность категории (Задание 2)
- Первый POST — создаём категорию "Работа" →
201 Created - Второй POST с тем же именем →
400 Bad Request:{ "name": ["Категория с названием \"Работа\" уже существует."] }
Тест 5: TaskDetailSerializer с вложенными SubTask (Задание 3)
- Метод:
GET - URL:
http://127.0.0.1:8000/api/tasks/1/(если добавили соответствующий view) - Ожидаемый ответ:
200 OK— объект Task с массивомsubtasksвнутри
Тест 6: Удаление подзадачи (DELETE)
- Метод:
DELETE - URL:
http://127.0.0.1:8000/api/subtasks/1/ - Ожидаемый ответ:
204 No Content— пустое тело
Тестирование через терминал (curl)
# Создать подзадачу
curl -X POST http://127.0.0.1:8000/api/subtasks/ ^
-H "Content-Type: application/json" ^
-d "{\"title\": \"Test subtask\", \"status\": \"todo\", \"task\": 1}"
# Список подзадач
curl http://127.0.0.1:8000/api/subtasks/
# Получить одну подзадачу
curl http://127.0.0.1:8000/api/subtasks/1/
# Удалить
curl -X DELETE http://127.0.0.1:8000/api/subtasks/1/
📚 Связь с теорией урока
| Элемент ДЗ | Тема урока | Ссылка |
|---|---|---|
| SubTaskCreateSerializer — read_only | PrimaryKeyRelatedField и управление доступом к полям | theory.html#primarykey-related-field |
| CategoryCreateSerializer — create/update | Переопределение методов сериализатора | theory.html |
| TaskDetailSerializer — вложенный SubTaskSerializer | StringRelatedField и вложенные объекты | theory.html#string-related-field |
| validate_deadline — дата не в прошлом | Валидация данных, метод validate_<field> | theory.html |
| SubTaskListCreateView / SubTaskDetailUpdateDeleteView | APIView — методы get/post/put/delete, as_view() | theory.html#apiview |
| get_object() — вспомогательный метод | Преимущества CBV — переиспользование методов | theory.html#cbv-hierarchy |