🏠 Домашнее задание 16 — Урок 36
⚡ Суть задания
Задание 1: создать CategoryViewSet с CRUD через ModelViewSet + кастомный @action count_tasks для подсчёта задач в категории.
Задание 2: добавить в модель Category поля is_deleted и deleted_at, переопределить delete() и менеджер.
Оформление: ссылка на Git + скриншоты из Postman.
Текст задания из LMS
Домашнее задание 16: Реализация CRUD для категорий с использованием ModelViewSet, мягкое удаление
Цель: Реализовать полный CRUD для модели категорий (Categories) с помощью ModelViewSet, добавить кастомный метод для подсчёта количества задач в каждой категории. Реализовать систему мягкого удаления для категорий.
Задание 1: Реализация CRUD для категорий с использованием ModelViewSet
Шаги для выполнения:
- Создайте
CategoryViewSet, используяModelViewSetдля CRUD операций. - Добавьте маршрут для
CategoryViewSet. - Добавьте кастомный метод
count_tasksиспользуя декоратор@actionдля подсчёта количества задач, связанных с каждой категорией.
Задание 2: Реализация мягкого удаления категорий
Шаги для выполнения:
- Добавьте два новых поля в вашу модель
Category, если таких ещё не было:- В модели
Categoryдобавьте поляis_deleted(Boolean, default False) иdeleted_at(DateTime, null=true) - Переопределите метод удаления, чтобы он обновлял новые поля к соответствующим значениям:
is_deleted=Trueи дата и время на момент «удаления» записи
- В модели
- Переопределите менеджера модели
Category:- В менеджере модели переопределите метод
get_queryset(), чтобы он по умолчанию выдавал только те записи, которые не «удалены» из базы.
- В менеджере модели переопределите метод
Оформление ответа
- Предоставьте решение: прикрепите ссылку на Git.
- Скриншоты тестирования: приложите скриншоты из браузера или Postman, подтверждающие успешное создание, обновление, получение и удаление данных через API.
Подготовка окружения
1. Активация виртуального окружения
# Windows PowerShell
cd your_project
python -m venv venv
.\venv\Scripts\activate
# Установить зависимости (если requirements.txt есть)
pip install django djangorestframework
2. Убедиться, что DRF подключён
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'tasks', # ваше приложение с Category и Task
]
3. Создать приложение (если ещё нет)
python manage.py startapp tasks
Пошаговое решение
Шаг 1: Модель Category
Предполагается, что у вас уже есть модель Task с полем category (ForeignKey на Category). Добавляем поля soft deletion.
# 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)
description = models.TextField(blank=True, default='')
is_deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
objects = CategoryManager() # только не удалённые
all_objects = models.Manager() # все (для администрирования)
def delete(self, *args, **kwargs):
self.is_deleted = True
self.deleted_at = timezone.now()
self.save()
def __str__(self):
return self.name
class Task(models.Model):
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE,
null=True, blank=True,
related_name='tasks')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Шаг 2: Менеджер модели
# tasks/managers.py
from django.db import models
class CategoryManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
Добавьте импорт в models.py:
from .managers import CategoryManager
Шаг 3: Сериализатор
# tasks/serializers.py
from rest_framework import serializers
from .models import Category, Task
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'description', 'is_deleted', 'deleted_at']
read_only_fields = ['is_deleted', 'deleted_at']
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
Шаг 4: ViewSet с @action
# tasks/views.py
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Category
from .serializers import CategorySerializer
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all() # SoftDeleteManager: только не удалённые
serializer_class = CategorySerializer
@action(detail=True, methods=['get'], url_path='count-tasks')
def count_tasks(self, request, pk=None):
"""Кастомный endpoint: GET /categories/{id}/count-tasks/"""
category = self.get_object()
task_count = category.tasks.count()
return Response({
'category': category.name,
'task_count': task_count
})
Шаг 5: Router и URLs
# tasks/urls.py
from rest_framework.routers import DefaultRouter
from .views import CategoryViewSet
router = DefaultRouter()
router.register(r'categories', CategoryViewSet, basename='category')
urlpatterns = router.urls
# project/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 makemigrations tasks
python manage.py migrate
Проверка в VS Code
Запуск сервера
python manage.py runserver
launch.json для F5
{
"version": "0.2.0",
"configurations": [
{
"name": "Django Server",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver", "--noreload"],
"django": true,
"justMyCode": true
}
]
}
Точки останова (breakpoints)
Кликните слева от номера строки в VS Code для установки точки останова:
- В
views.pyна строкеcategory = self.get_object()— убедиться, что объект получен - В
models.pyна строкеself.is_deleted = True— проверить вызов мягкого удаления - В
managers.pyна строкеreturn super().get_queryset().filter(is_deleted=False)
Тестирование через Postman
1. Создать категорию (POST)
POST http://127.0.0.1:8000/api/categories/
Content-Type: application/json
{
"name": "Work",
"description": "Work-related tasks"
}
# Ответ 201:
{
"id": 1,
"name": "Work",
"description": "Work-related tasks",
"is_deleted": false,
"deleted_at": null
}
2. Получить список (GET)
GET http://127.0.0.1:8000/api/categories/
# Возвращает только is_deleted=false
3. Обновить категорию (PATCH)
PATCH http://127.0.0.1:8000/api/categories/1/
Content-Type: application/json
{
"description": "Updated description"
}
4. Подсчитать задачи (@action)
GET http://127.0.0.1:8000/api/categories/1/count-tasks/
# Ответ:
{
"category": "Work",
"task_count": 5
}
5. Мягкое удаление (DELETE)
DELETE http://127.0.0.1:8000/api/categories/1/
# Ответ: 204 No Content
# Запись в базе:
# is_deleted = True
# deleted_at = "2024-06-01T10:30:00Z"
6. Убедиться, что удалённая категория недоступна
GET http://127.0.0.1:8000/api/categories/1/
# Ответ: 404 Not Found
# (SoftDeleteManager исключает is_deleted=True)
GET http://127.0.0.1:8000/api/categories/
# Категория "Work" в списке не появляется
Связь с теорией и примерами
- Шаги реализации soft deletion → Теория, Часть 1: Мягкое удаление
- Менеджер
SoftDeleteManager→ Шаг 3: Кастомный менеджер - ModelViewSet и @action → Урок 33: GenericAPIViews
- Полный пример кода → Примеры, Пример 1
- Типичные ошибки → Ошибки, Раздел 1