💻 Примеры: Auth-блок в действии

🎯 Полные рабочие примеры К оглавлению урока

⚡ Ключевые примеры

  • TokenAuthentication: Authorization: Token abc123
  • JWT логин: POST /api/token/ → {access, refresh}
  • IsOwnerOrReadOnly: только владелец объекта может редактировать
  • @receiver(post_save, sender=User): создать токен при регистрации
  • send_mail() через консольный бэкенд для тестирования

Пример 1: TokenAuthentication — полный цикл

# settings.py
INSTALLED_APPS = [
    'rest_framework',
    'rest_framework.authtoken',
]
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# urls.py
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
    path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
    path('books/', BookListView.as_view()),
]

# views.py
from rest_framework.generics import ListAPIView
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from .models import Book
from .serializers import BookSerializer

class BookListView(ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]
Запрос через Postman: POST /api-token-auth/ с body (form-data): username, password → получаем {"token": "abc123..."}
GET /books/ с заголовком: Authorization: Token abc123...

Пример 2: JWT с httpOnly-куки (полный цикл)

# settings.py
from datetime import timedelta
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
}
MIDDLEWARE = [
    ...
    'first_app.middleware.JWTAuthenticationMiddleware',
]

# views.py — LoginView с сохранением в куки
from datetime import datetime
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.permissions import AllowAny

class LoginView(APIView):
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        user = authenticate(request, username=username, password=password)
        if user:
            refresh = RefreshToken.for_user(user)
            access_token = refresh.access_token
            response = Response(status=status.HTTP_200_OK)
            response.set_cookie(
                key='access_token',
                value=str(access_token),
                httponly=True,
                secure=False,  # True на HTTPS
                samesite='Lax',
                expires=datetime.utcfromtimestamp(access_token['exp'])
            )
            response.set_cookie(
                key='refresh_token',
                value=str(refresh),
                httponly=True,
                secure=False,
                samesite='Lax',
                expires=datetime.utcfromtimestamp(refresh['exp'])
            )
            return response
        return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)

# views.py — LogoutView
class LogoutView(APIView):
    def post(self, request, *args, **kwargs):
        response = Response(status=status.HTTP_204_NO_CONTENT)
        response.delete_cookie('access_token')
        response.delete_cookie('refresh_token')
        return response

Пример 3: Регистрация пользователя с JWT

# serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, min_length=8)

    class Meta:
        model = User
        fields = ['username', 'password', 'email']

    def create(self, validated_data):
        return User.objects.create_user(
            username=validated_data['username'],
            password=validated_data['password'],
            email=validated_data.get('email', '')
        )

# views.py
from rest_framework_simplejwt.tokens import RefreshToken

class RegisterView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            refresh = RefreshToken.for_user(user)
            response = Response(
                {'user': {'username': user.username, 'email': user.email}},
                status=status.HTTP_201_CREATED
            )
            response.set_cookie('access_token', str(refresh.access_token), httponly=True)
            response.set_cookie('refresh_token', str(refresh), httponly=True)
            return response
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Пример 4: Кастомное разрешение IsOwnerOrReadOnly

# permissions.py
from rest_framework.permissions import BasePermission

class IsOwnerOrReadOnly(BasePermission):
    """Только владелец может изменять/удалять; все могут читать."""

    def has_object_permission(self, request, view, obj):
        if request.method in ('GET', 'HEAD', 'OPTIONS'):
            return True
        return obj.owner == request.user

# Расширенный вариант: владелец ИЛИ администратор
class IsAdminOrOwner(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in ('GET', 'HEAD', 'OPTIONS'):
            return True
        return request.user.is_staff or obj.owner == request.user

# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .permissions import IsOwnerOrReadOnly

class BookDetailView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]

Пример 5: Сигналы — автосоздание токена и email-уведомление

# signals.py
import logging
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.mail import send_mail
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
from .models import Book

logger = logging.getLogger(__name__)

# Автосоздание токена при регистрации пользователя
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

# Уведомление администратора при создании книги
@receiver(post_save, sender=Book)
def notify_admin_on_book_create(sender, instance, created, **kwargs):
    if created:
        send_mail(
            subject='New Book Created',
            message=f'Book "{instance.title}" (id={instance.id}) has been created.',
            from_email='admin@example.com',
            recipient_list=['admin@example.com'],
        )

# Логирование удаления книги
@receiver(post_delete, sender=Book)
def log_book_deletion(sender, instance, **kwargs):
    logger.info(f'Book deleted: id={instance.id}, title={instance.title}')

# apps.py — обязательно для регистрации сигналов
from django.apps import AppConfig

class FirstAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'first_app'

    def ready(self):
        import first_app.signals  # регистрируем обработчики

Пример 6: Кастомные разрешения модели

# models.py
class Genre(models.Model):
    name = models.CharField(max_length=100, unique=True)

    class Meta:
        permissions = [
            ('can_get_statistic', 'Can get genres statistic'),
        ]

# permissions.py
class CanGetStatisticPermission(BasePermission):
    def has_permission(self, request, view):
        return request.user.has_perm('first_app.can_get_statistic')

# views.py
from rest_framework.permissions import DjangoModelPermissions
from .permissions import CanGetStatisticPermission

class GenreViewSet(ModelViewSet):
    queryset = Genre.objects.all()
    serializer_class = GenreSerializer
    permission_classes = [DjangoModelPermissions, CanGetStatisticPermission]
Важно: После добавления Meta.permissions обязательно выполнить python manage.py makemigrations && python manage.py migrate, иначе разрешение не появится в Django Admin.

Пример 7: Полная настройка Swagger

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

# 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='My API',
        default_version='v1',
        description='API документация',
        contact=openapi.Contact(email='contact@example.com'),
        license=openapi.License(name='MIT'),
    ),
    public=True,
    permission_classes=[permissions.AllowAny],
)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('first_app.urls')),
    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'),
]