✅ Решения практикума 9

Полный разбор всех 13 задач с объяснением по источнику PrfS9

⚡ Все решения — быстрые ссылки

Задание 1: BasicAuthentication

Решение 1.1 — Настройка BasicAuthentication

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}
Объяснение: Здесь настраивается глобальный механизм аутентификации. Все представления, у которых не переопределён authentication_classes, будут требовать BasicAuthentication. DEFAULT_PERMISSION_CLASSES с IsAuthenticated означает, что неаутентифицированные запросы получают 403/401.

Решение 1.2 — Применение BasicAuthentication к представлению

# views.py
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated

class ProductListCreateView(ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductSerializer
        return ProductCreateUpdateSerializer
Объяснение: Атрибут authentication_classes переопределяет глобальную настройку из settings.py для конкретного представления. Это даёт гибкость: разные представления могут использовать разные механизмы аутентификации. get_serializer_class() выбирает разные сериализаторы для GET и POST.

Решение 1.3 — Получение доступа через Postman

# Вариант 1: credentials в URL (только для тестирования!)
http://username:password@127.0.0.1:8000/products/

# Вариант 2: заголовок Authorization (правильно)
# В Postman: вкладка Authorization -> Basic Auth
# Ввести Username и Password — Postman сам сформирует заголовок:
# Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Объяснение: Basic Auth кодирует строку username:password в Base64. Это не шифрование — Base64 тривиально декодируется. Поэтому BasicAuthentication безопасно только с HTTPS. Для тестирования локально Postman делает Base64-кодирование автоматически.

Задание 2: TokenAuthentication

Решение 2.1 — Настройка TokenAuthentication

# settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework.authtoken',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

# Выполните миграции
# python manage.py migrate
Объяснение: Пакет rest_framework.authtoken добавляет модель Token в базу данных. После добавления в INSTALLED_APPS обязательно нужно выполнить python manage.py migrate — без этого таблица authtoken_token не создастся, и при первом запросе токена будет ошибка.

Решение 2.2 — Получение токена (obtain_auth_token)

# urls.py
from django.urls import path
from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
]
Объяснение: obtain_auth_token — встроенное в DRF представление для получения токена. Принимает POST-запрос с username и password в теле, возвращает {"token": "..."}. Токен привязывается к пользователю и хранится в БД. При повторном запросе возвращает тот же токен.

Решение 2.3 — Применение TokenAuthentication к представлению

# views.py
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

class ProductListCreateView(ListCreateAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductSerializer
        return ProductCreateUpdateSerializer
Объяснение: Структура аналогична BasicAuthentication — только класс меняется с BasicAuthentication на TokenAuthentication. Остальная логика (queryset, permission, get_serializer_class) не меняется. Это демонстрирует, что DRF разделяет аутентификацию и бизнес-логику.

Решение 2.4 — Получение доступа с помощью токена

# Шаг 1: получить токен
# POST http://127.0.0.1:8000/api-token-auth/
# Body: {"username": "admin", "password": "your_password"}
# Ответ: {"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}

# Шаг 2: использовать токен в запросе
# GET http://127.0.0.1:8000/products/
Authorization: Token your_token
Объяснение: Обратите внимание на формат заголовка: ключевое слово — Token (с большой буквы), затем пробел, затем значение токена. Не Bearer, не Basic — именно Token. В Postman: вкладка Authorization → Bearer Token (вводим токен без слова Token) или Headers → Authorization → "Token ваш_токен".

Задание 3: JWT-аутентификация

Решение 3.1 — Настройка JWTAuthentication

# Установка Simple JWT
# pip install djangorestframework-simplejwt

# settings.py
from datetime import timedelta

INSTALLED_APPS = [
    # ...
    'rest_framework_simplejwt',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
}
Объяснение: Simple JWT — сторонний пакет, требует отдельной установки. В SIMPLE_JWT настраивается время жизни токенов. Access-токен — короткоживущий (5 минут), чтобы снизить риск при перехвате. Refresh-токен — долгоживущий (1 день), позволяет получить новый access без повторного ввода пароля.

Решение 3.2 — Маршруты для JWT

# urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Объяснение: TokenObtainPairView принимает логин/пароль и возвращает пару: access + refresh токены. TokenRefreshView принимает refresh-токен и возвращает новый access-токен. Оба маршрута должны быть открыты (AllowAny), иначе пользователи не смогут получить токены.

Решение 3.3 — Применение JWT к представлению Order

# views.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated

class OrderListCreateView(ListCreateAPIView):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAuthenticated]

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return OrderSerializer
        return OrderCreateUpdateSerializer
Объяснение: Паттерн тот же — меняется только импорт и класс аутентификации. Здесь используется представление для Order вместо Product. DRF проверяет JWT, декодируя его подпись — без обращения к БД для access-токена.

Решение 3.4 — Получение доступа с помощью JWT

# Шаг 1: получить JWT
# POST http://127.0.0.1:8000/api/token/
# Body: {"username": "admin", "password": "your_password"}
# Ответ: {"access": "eyJhbGci...", "refresh": "eyJhbGci..."}

# Шаг 2: использовать access-токен
# GET http://127.0.0.1:8000/orders/
Authorization: Bearer your_token

# Шаг 3: обновить access-токен когда истёк
# POST http://127.0.0.1:8000/api/token/refresh/
# Body: {"refresh": "eyJhbGci..."}
# Ответ: {"access": "новый_access_token"}
Объяснение: Ключевое отличие от Token: здесь Bearer вместо Token в заголовке. Это стандарт OAuth 2.0 / RFC 6750. JWT не требует хранения в БД при проверке — сервер просто проверяет подпись и срок жизни токена.

Задание 4: Разрешения на уровне представления

Решение 4.1 — IsAuthenticatedOrReadOnly для ProductDetail

# views.py
from rest_framework.permissions import IsAuthenticatedOrReadOnly

class ProductDetailDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = ProductDetail.objects.all()
    serializer_class = ProductDetailSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductDetailSerializer
        return ProductDetailCreateUpdateSerializer
Объяснение: IsAuthenticatedOrReadOnly разрешает GET/HEAD/OPTIONS для всех (включая анонимов), но требует аутентификации для PUT/PATCH/DELETE. Это классический паттерн для публичных API с защищёнными операциями записи.

Решение 4.2 — IsAdminUser для SupplierViewSet

# views.py
from rest_framework.permissions import IsAdminUser

class SupplierViewSet(viewsets.ModelViewSet):
    queryset = Supplier.objects.all()
    serializer_class = SupplierSerializer
    permission_classes = [IsAdminUser]
Объяснение: IsAdminUser проверяет флаг is_staff=True у пользователя. Управление поставщиками — административная операция, поэтому логично ограничить доступ только для персонала. Обычные аутентифицированные пользователи получат 403 Forbidden.

Решение 4.3 — Разрешения для остальных представлений

Задание 4.3 открытое — каждая команда обсуждает и выбирает разрешения исходя из логики приложения. Типичный вариант для интернет-магазина:

# Возможный вариант для интернет-магазина

# Категории: публичное чтение, только авторизованные пишут
class CategoryViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticatedOrReadOnly]

# Продукты: публичное чтение, только авторизованные пишут
class ProductListCreateView(ListCreateAPIView):
    permission_classes = [IsAuthenticatedOrReadOnly]

# Заказы: только авторизованные (покупатель видит свои заказы)
class OrderListCreateView(ListCreateAPIView):
    permission_classes = [IsAuthenticated]

# Покупатели: только авторизованные
class CustomerListCreateView(ListCreateAPIView):
    permission_classes = [IsAuthenticated]

# Адреса: только авторизованные
class AddressViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
⚠️ Проверить по документации: выбор permission-классов зависит от бизнес-требований конкретного приложения. Данный пример — один из возможных вариантов, не единственно правильный.
← К оглавлению урока    Ошибки →