⚖️ Старый vs Новый: Auth-блок

🎯 Устаревшие паттерны → Django 5.x / DRF 3.15+ / SimpleJWT 5.x К оглавлению урока

⚡ Главные изменения

  • Сигналы: post_save.connect()@receiver декоратор
  • Токены вручную: Token.objects.create() → сигнал автоматически
  • JWT через сессии (устарело) → SimpleJWT + httpOnly-куки
  • Разрешения через if request.user.is_authenticated: в view → permission_classes
  • datetime.utcnow() (deprecated Python 3.12) → datetime.now(timezone.utc)

1. Регистрация обработчиков сигналов

Старый способ (из лекции)

# Ручное подключение через .connect()
from django.db.models.signals import post_save
from .models import Book

def book_saved(sender, instance, created, **kwargs):
    if created:
        print(f'New book: {instance.title}')

# Подключение вне декоратора
post_save.connect(book_saved, sender=Book)

Современный способ (Django 5.x)

# Декоратор @receiver — чище и явнее
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Book

@receiver(post_save, sender=Book)
def book_saved(sender, instance, created, **kwargs):
    if created:
        print(f'New book: {instance.title}')

Оба варианта рабочие, но @receiver — идиоматичный Python-стиль и проще читать.

2. JWT: хранение токена в localStorage (устарело) vs httpOnly-куки

Старый подход (небезопасный)

// Клиентский JavaScript
// НЕБЕЗОПАСНО: localStorage уязвим для XSS
const response = await fetch('/api/token/', {...});
const data = await response.json();
localStorage.setItem('access_token', data.access);

// При каждом запросе вручную
fetch('/api/books/', {
  headers: {
    'Authorization': 'Bearer ' + localStorage.getItem('access_token')
  }
});

Современный подход (Django 5.x / SimpleJWT 5.x)

# Сервер сохраняет токены в httpOnly-куки
response.set_cookie(
    key='access_token',
    value=str(access_token),
    httponly=True,   # JS не имеет доступа → защита от XSS
    secure=True,     # только HTTPS в продакшн
    samesite='Lax',  # защита от CSRF
)
# JWTAuthenticationMiddleware автоматически
# добавляет токен из куки в заголовок Authorization

3. Авторизация: ручная проверка в view vs permission_classes

Старый способ (антипаттерн)

# Ручная проверка в каждом методе — DRY нарушен
class BookView(APIView):
    def get(self, request):
        if not request.user.is_authenticated:
            return Response({'error': 'Not authenticated'}, status=401)
        # бизнес-логика
        ...

    def post(self, request):
        if not request.user.is_authenticated:
            return Response({'error': 'Not authenticated'}, status=401)
        # бизнес-логика
        ...

Современный (DRF 3.15+)

# Декларативный подход через permission_classes
class BookView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        # DRF уже проверил аутентификацию
        ...

    def post(self, request):
        # DRF уже проверил аутентификацию
        ...

4. Создание токена при регистрации

Старый способ (из лекции)

# В view при создании пользователя вручную
user = User.objects.create_user(...)
token = Token.objects.create(user=user)
return Response({'token': token.key})

Современный (через сигнал)

# signals.py — автоматически при любом создании User
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)
# View больше не знает о токене — разделение ответственности

5. datetime.utcnow() — устаревший API Python 3.12+

Из лекции (deprecated с Python 3.12)

from datetime import datetime

# Предупреждение: DeprecationWarning в Python 3.12+
access_expiry = datetime.utcfromtimestamp(access_token['exp'])
if datetime.utcnow() > expiry:
    raise TokenError('expired')

Современный (Python 3.x aware datetime)

from datetime import datetime, timezone

# Timezone-aware datetime — правильный подход
access_expiry = datetime.fromtimestamp(access_token['exp'], tz=timezone.utc)
if datetime.now(tz=timezone.utc) > access_expiry:
    raise TokenError('expired')
⚠️ Проверить по документации: поведение SimpleJWT при работе с временными метками может зависеть от версии. Проверьте актуальную документацию SimpleJWT 5.x.

6. Регистрация сигналов в apps.py

Старый способ (не надёжный)

# Импорт signals.py в models.py или __init__.py
# Вызывает проблемы с порядком импортов

# models.py — ПЛОХО
from . import signals  # circular imports!

Современный (Django 5.x)

# apps.py — правильное место для импорта сигналов
class FirstAppConfig(AppConfig):
    name = 'first_app'

    def ready(self):
        import first_app.signals  # безопасно, вызывается после инициализации