✅ Решения практикума 10
Полный разбор всех 5 заданий с кодом и объяснением по источнику PrfS10
⚡ Все решения — быстрые ссылки
Задание 1: Извлечение пользователя из объекта запроса
Решение 1.1 — Автоматическое добавление клиента при оформлении заказа
# views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Order
from .serializers import OrderSerializer
class OrderListCreateView(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(customer=self.request.user)
Объяснение: Метод
perform_create() вызывается после валидации данных и до сохранения в БД. Передача customer=self.request.user в serializer.save() переопределяет значение поля — пользователь не может подставить чужой customer, даже если отправит его в теле запроса.
Решение 1.2 — Извлечение объектов, где пользователь является владельцем
# views.py
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from .models import Order
from .serializers import OrderSerializer
class UserOrderListView(ListAPIView):
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(customer=self.request.user)
Объяснение: Переопределение
get_queryset() гарантирует, что пользователь видит только свои заказы. Без этой фильтрации пользователь мог бы запросить чужой заказ по ID напрямую — это нарушение приватности данных.
Решение 1.3 — Обновление маршрутов
# urls.py
from django.urls import path
from .views import UserOrderListView, OrderListCreateView
urlpatterns = [
path('user-orders/', UserOrderListView.as_view(), name='user-orders'),
path('orders/', OrderListCreateView.as_view({'get': 'list', 'post': 'create'}), name='orders'),
]
Объяснение:
UserOrderListView — только список заказов текущего пользователя. OrderListCreateView — создание заказа (customer берётся из запроса автоматически). Два разных маршрута решают разные задачи.
Задание 2: Создание кастомных классов разрешений
Решение 2.1 — IsCustomerOrReadOnly
# permissions.py
from rest_framework.permissions import BasePermission
class IsCustomerOrReadOnly(BasePermission):
"""
Разрешает редактирование объектов только их владельцам (customer),
остальным — только чтение.
"""
def has_object_permission(self, request, view, obj):
# Все пользователи могут просматривать
if request.method in ['GET', 'HEAD', 'OPTIONS']:
return True
# Только владелец может изменять объект
return obj.customer == request.user
Объяснение:
has_object_permission вызывается для каждого конкретного объекта (при retrieve/update/destroy). Метод возвращает True для безопасных HTTP-методов (чтение) и проверяет владельца для небезопасных (запись/удаление). Это классический паттерн "IsOwnerOrReadOnly".
Решение 2.2 — Применение IsCustomerOrReadOnly к представлению
# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Order
from .serializers import OrderSerializer
from .permissions import IsCustomerOrReadOnly
class OrderDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
permission_classes = [IsCustomerOrReadOnly]
Объяснение:
RetrieveUpdateDestroyAPIView обрабатывает GET (один объект), PUT/PATCH (обновление), DELETE (удаление). При PUT/PATCH/DELETE DRF вызывает has_object_permission — там происходит проверка владельца. Анонимный пользователь и аутентифицированный не-владелец получат 403.
Задание 3: Добавление эндпоинта для статистики
Решение 3.1 — Пользовательское разрешение в Meta
# models.py
from django.db import models
class Order(models.Model):
order_date = models.DateTimeField(auto_now_add=True)
customer = models.ForeignKey(Customer, on_delete=models.PROTECT,
related_name='orders')
class Meta:
permissions = [
("can_view_statistics", "Can view statistics"),
]
Объяснение:
Meta.permissions регистрирует нестандартное разрешение Django. Формат: кортеж ("codename", "Human-readable name"). После выполнения миграций разрешение появляется в таблице auth_permission и доступно в Admin.
Решение 3.2 — Кастомное разрешение CanViewStatistics
# permissions.py
from rest_framework.permissions import BasePermission
class CanViewStatistics(BasePermission):
"""
Разрешает доступ к статистике только пользователям с соответствующим
разрешением.
"""
def has_permission(self, request, view):
return request.user.has_perm('store.can_view_statistics')
Объяснение:
has_perm('store.can_view_statistics') проверяет разрешение вида 'app_label.codename'. Здесь store — это название приложения Django, can_view_statistics — codename из Meta.permissions. Разрешение может быть присвоено пользователю напрямую или через группу.
Решение 3.3 — Эндпоинт для статистики
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .permissions import CanViewStatistics
from .models import Order
class OrderStatisticsView(APIView):
permission_classes = [CanViewStatistics]
def get(self, request, *args, **kwargs):
total_orders = Order.objects.count()
data = {
'total_orders': total_orders,
}
return Response(data)
Объяснение:
APIView — базовый класс DRF для произвольных эндпоинтов без привязки к модели. Метод get() обрабатывает GET-запросы. permission_classes = [CanViewStatistics] защищает эндпоинт: без нужного разрешения пользователь получит 403.
Решение 3.4 — Маршрут для статистики
# urls.py
from .views import OrderStatisticsView
urlpatterns += [
path('order-statistics/', OrderStatisticsView.as_view(), name='order-statistics'),
]
Решение 3.5 — Миграции
python manage.py makemigrations
python manage.py migrate
Обязательно! После добавления
Meta.permissions необходимо создать и применить миграции. Без этого разрешение не появится в БД и в Django Admin.
Решение 3.6 — Группа в Django Admin
- Зайдите в
http://127.0.0.1:8000/admin/ - Перейдите в раздел Groups (Группы)
- Создайте группу "Statistic"
- Добавьте в группу разрешение "Can view statistics" (ищите в фильтре)
- Сохраните группу
- Перейдите в раздел Users (Пользователи)
- Найдите нужного пользователя (первый admin)
- В разделе Groups добавьте группу "Statistic"
- Сохраните пользователя
Задание 4: Управление базой данных
Решение 4.1 — Создание дампа базы данных
# Создать дамп всех объектов проекта
python manage.py dumpdata --indent=4 > db_backup.json
Объяснение:
dumpdata экспортирует все данные из всех приложений в формате JSON. Флаг --indent=4 делает файл читаемым (4 пробела). Результат сохраняется в db_backup.json в корне проекта.
Решение 4.2 — Восстановление базы данных из дампа
# После удаления БД: применить миграции (создать структуру таблиц)
python manage.py migrate
# Загрузить данные из дампа
python manage.py loaddata db_backup.json
Объяснение:
migrate создаёт пустые таблицы согласно миграциям. loaddata наполняет их данными из JSON-файла. Порядок важен: сначала структура, потом данные.
Задание 5: Настройка OpenAPI 3 документации
Решение 5.1 — Установка библиотеки
pip install drf-spectacular
Решение 5.2 — Настройка в settings.py
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'drf_spectacular',
]
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'API Documentation',
'DESCRIPTION': 'API documentation for the project',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
Объяснение:
drf_spectacular подключается как Django-приложение, а DEFAULT_SCHEMA_CLASS говорит DRF использовать OpenAPI 3 генератор вместо встроенной схемы.
Решение 5.3 — Маршруты для схемы и UI
# urls.py
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView,
)
urlpatterns += [
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]
Объяснение:
SpectacularAPIView— отдаёт машинно-читаемую OpenAPI 3 схемуSpectacularSwaggerView— рендерит Swagger UI поверх этой схемыSpectacularRedocView— рендерит ReDoc поверх той же схемыurl_name='schema'— связывает UI с маршрутом схемыSPECTACULAR_SETTINGS— хранит title, description и version API