✅ Решения практикума 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: Настройка Swagger
Решение 5.1 — Установка библиотеки
pip install drf-yasg
Решение 5.2 — Настройка в settings.py
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'drf_yasg',
]
Объяснение:
drf_yasg нужно добавить в INSTALLED_APPS, чтобы Django нашёл статику и шаблоны библиотеки (Swagger UI — это набор JS/CSS файлов).
Решение 5.3 — Маршруты для Swagger
# urls.py
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
schema_view = get_schema_view(
openapi.Info(
title="API Documentation",
default_version='v1',
description="API documentation for the project",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@local.com"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns += [
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'),
]
Объяснение:
get_schema_view()— создаёт объект представления схемы с метаданными APIpublic=True— схема доступна без аутентификацииpermission_classes=(permissions.AllowAny,)— документация открыта для всехwith_ui('swagger')— рендерит Swagger UI (интерактивная документация)with_ui('redoc')— рендерит ReDoc (читаемая документация)cache_timeout=0— отключает кэширование схемы (удобно при разработке)
⚠️ Проверить по документации:
drf-yasg поддерживает DRF до 3.14. Для более новых версий DRF рекомендуется проверить совместимость или использовать drf-spectacular.