🏠 Домашнее задание 19 — Урок 42
⚡ Суть ДЗ 19
Цель: реализовать авторизацию с извлечением пользователя из запроса, разрешения на уровне объектов, интегрировать Swagger.
- Добавить поле
ownerв Task и SubTask, настроитьperform_create, создать endpoint для задач текущего пользователя - Создать кастомный permission
IsOwnerOrReadOnly, применить к Task и SubTask - Установить
drf-yasg, настроить/swagger/и/redoc/
Задание из LMS
Домашнее задание: Реализация JWT (SimpleJWT) аутентификации и пермишенов
Цель: Реализовать авторизацию с извлечением текущего пользователя из запроса и применение разрешений на уровне объектов. Настроить и интегрировать Swagger для автоматической генерации документации API.
Цель: Реализовать авторизацию с извлечением текущего пользователя из запроса и применение разрешений на уровне объектов. Настроить и интегрировать Swagger для автоматической генерации документации API.
Задание 1: Извлечение текущего пользователя из запроса
Шаги для выполнения:
-
Обновите модели, чтобы включить поле owner.
Обновите моделиTaskиSubTaskдля включения поляowner. -
Измените сериализаторы.
Измените сериализаторы для моделейTaskиSubTaskдля работы с новым полем. -
Переопределите метод perform_create в представлениях.
Обновите представления для автоматического добавления владельца объекта. -
Создайте представления для получения задач текущего пользователя.
Реализуйте представление для получения задач, принадлежащих текущему пользователю.
Задание 2: Реализация пермишенов для API
Шаги для выполнения:
-
Создайте пользовательские пермишены.
Реализуйте пользовательский пермишен для проверки, что пользователь является автором задачи или подзадачи. -
Примените пермишены к API представлениям.
Добавьте пермишены к представлениям для задач и подзадач, чтобы только владельцы могли их изменять или удалять.
Задание 3: Swagger
Шаги для выполнения:
- Установите
drf-yasg. - Добавьте
drf_yasgвsettings. - Настройте маршруты для Swagger в
urls.py. -
Просмотр документации.
Перейдите по URL/swagger/или/redoc/, чтобы увидеть документацию для вашего API.
Оформление ответа
- Предоставьте решение: прикрепите ссылку на git.
-
Скриншоты тестирования: приложите скриншоты из консоли или Postman, подтверждающие:
- успешное извлечение текущего пользователя из запроса
- соблюдение пермишенов при работе с задачами
- реализованную документацию
Подготовка окружения
1. Создание виртуального окружения
# Windows PowerShell
cd your-django-project
python -m venv venv
.\venv\Scripts\Activate.ps1
2. Установка зависимостей
pip install django djangorestframework djangorestframework-simplejwt drf-yasg
pip freeze > requirements.txt
3. Структура проекта
myproject/
myproject/
settings.py
urls.py
tasks/
models.py
serializers.py
views.py
permissions.py
urls.py
manage.py
requirements.txt
4. Git инициализация
git init
git add .
git commit -m "initial: project setup"
Пошаговое решение
Шаг 1: Настройка settings.py
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_simplejwt',
'drf_yasg', # Swagger
'tasks', # ваше приложение
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'AUTH_HEADER_TYPES': ('Bearer',),
}
Связь с теорией: см. раздел «request.user» и Урок 40: JWT-аутентификация.
Шаг 2: Обновление models.py — добавление owner
# tasks/models.py
from django.db import models
from django.contrib.auth.models import User
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)
owner = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='tasks'
# Не используем null=True — новые записи обязаны иметь владельца
)
def __str__(self):
return f"{self.title} ({self.status})"
class SubTask(models.Model):
STATUS_CHOICES = Task.STATUS_CHOICES
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
task = models.ForeignKey(Task, on_delete=models.CASCADE, related_name='subtasks')
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)
owner = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='subtasks'
)
def __str__(self):
return f"{self.title} ({self.status})"
python manage.py makemigrations tasks
python manage.py migrate
При миграции Django предложит задать дефолтное значение для поля
owner в существующих строках. Введите 1 (или id любого существующего пользователя). Если у вас нет пользователей — создайте суперпользователя: python manage.py createsuperuser.
Шаг 3: Сериализаторы с read_only owner
# tasks/serializers.py
from rest_framework import serializers
from .models import Task, SubTask
class SubTaskSerializer(serializers.ModelSerializer):
class Meta:
model = SubTask
fields = '__all__'
read_only_fields = ['owner', 'created_at']
class TaskSerializer(serializers.ModelSerializer):
subtasks = SubTaskSerializer(many=True, read_only=True)
class Meta:
model = Task
fields = '__all__'
read_only_fields = ['owner', 'created_at']
Связь с теорией: Теория — автоматическое назначение владельца объясняет, зачем owner должен быть read_only.
Шаг 4: Кастомный permission
# tasks/permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsOwnerOrReadOnly(BasePermission):
"""
Разрешает изменение объектов только их владельцам.
Остальным — только чтение (SAFE_METHODS).
"""
message = "Изменять или удалять объект может только его владелец."
def has_object_permission(self, request, view, obj):
# Чтение разрешено всем аутентифицированным
if request.method in SAFE_METHODS:
return True
# Изменение/удаление — только владелец
return obj.owner == request.user
Связь с теорией: Теория — разрешения на уровне объектов.
Шаг 5: Представления с perform_create и get_queryset
# tasks/views.py
from rest_framework import viewsets
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from .models import Task, SubTask
from .serializers import TaskSerializer, SubTaskSerializer
from .permissions import IsOwnerOrReadOnly
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def perform_create(self, serializer):
# Автоматически назначаем owner из запроса
serializer.save(owner=self.request.user)
class SubTaskViewSet(viewsets.ModelViewSet):
queryset = SubTask.objects.all()
serializer_class = SubTaskSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class UserTaskListView(ListAPIView):
"""Возвращает только задачи текущего пользователя."""
serializer_class = TaskSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Task.objects.filter(owner=self.request.user)
class UserSubTaskListView(ListAPIView):
"""Возвращает только подзадачи текущего пользователя."""
serializer_class = SubTaskSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return SubTask.objects.filter(owner=self.request.user)
Шаг 6: Настройка Swagger (drf-yasg)
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from rest_framework import permissions
from tasks.views import TaskViewSet, SubTaskViewSet, UserTaskListView, UserSubTaskListView
# Swagger схема
schema_view = get_schema_view(
openapi.Info(
title="Tasks API",
default_version='v1',
description="API для управления задачами с JWT-аутентификацией",
contact=openapi.Contact(email="admin@example.com"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
router = DefaultRouter()
router.register(r'tasks', TaskViewSet, basename='task')
router.register(r'subtasks', SubTaskViewSet, basename='subtask')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('api/my-tasks/', UserTaskListView.as_view(), name='my-tasks'),
path('api/my-subtasks/', UserSubTaskListView.as_view(), name='my-subtasks'),
# JWT токены
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
# Swagger документация
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
Проверка в VS Code
1. Запуск сервера в терминале
# В терминале VS Code (Ctrl+`)
.\venv\Scripts\Activate.ps1
python manage.py runserver
2. Запуск через F5 (launch.json)
Создайте файл .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Django: Run Server",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver"],
"django": true,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
}
]
}
Нажмите F5 для запуска. Точки останова (F9) можно ставить в любой файл проекта.
3. Точки останова для отладки permissions
Поставьте точку останова на строку return obj.owner == request.user в permissions.py. В VS Code в режиме отладки (F5) при PUT-запросе точка сработает и можно проверить переменные obj.owner, request.user.
4. Проверка в Postman
# Шаг 1: Получить JWT токен
POST http://127.0.0.1:8000/api/token/
Body: {"username": "your_user", "password": "your_password"}
# Response: {"access": "...", "refresh": "..."}
# Шаг 2: Создать задачу (owner автоматически = текущий user)
POST http://127.0.0.1:8000/api/tasks/
Header: Authorization: Bearer <access_token>
Body: {"title": "My Task", "description": "Test"}
# Response: {"id": 1, "title": "My Task", "owner": 1, ...}
# Шаг 3: Попробовать изменить задачу другим пользователем
PUT http://127.0.0.1:8000/api/tasks/1/
Header: Authorization: Bearer <токен_другого_пользователя>
# Response 403: {"detail": "Изменять или удалять объект может только его владелец."}
# Шаг 4: Получить только свои задачи
GET http://127.0.0.1:8000/api/my-tasks/
Header: Authorization: Bearer <мой_токен>
# Response: [только мои задачи]
# Шаг 5: Открыть Swagger
# Браузер: http://127.0.0.1:8000/swagger/
# Браузер: http://127.0.0.1:8000/redoc/
Связь с материалами урока
| Задача | Теория | Примеры |
|---|---|---|
| Поле owner + perform_create | Теория: Часть 1 — request.user | Примеры: Пример 1 |
| IsOwnerOrReadOnly | Теория: Часть 2 — object-level | Примеры: Пример 1 |
| get_queryset для /my-tasks/ | Теория: Часть 1 — фильтрация | Примеры: UserBookListView |
| Swagger / drf-yasg | Ресурсы: drf-yasg | — |