Ну как, готовы взять под контроль свои API? Сегодня мы узнаем, как кастомизировать разрешения (permissions) в Django REST Framework (DRF). Это как дать каждому маршруту вашего API суперсилу: «Ты можешь пропустить только админов!» или «Ты открыт только для пользователей с подтвержденной электронной почтой!». Согласитесь, звучит интересно!
Из этой лекции вы узнаете:
- Как создавать собственные классы разрешений.
- Примеры ситуаций, где кастомизация разрешений полезна.
- Как проверить, что наши кастомные разрешения работают корректно.
Разрешения в DRF: повторение на пальцах
DRF уже даёт нам свои стандартные классы:
AllowAny: вообще без фейсконтроля, пускаем всех.IsAuthenticated: пускаем только зарегистрированных (аутентифицированных) пользователей.IsAdminUser: внутрь проходят только администраторы.IsAuthenticatedOrReadOnly: записываться могут только свои, читать — кто угодно.
Только вот иногда стандартного фейсконтроля недостаточно. Например, как ограничить API доступ только пользователям с определенным статусом (например, только подтвержденным пользователям)? Здесь в дело вступает кастомизация.
Как работают кастомные разрешения?
Кастомные разрешения в DRF — это просто Python-класс, который наследуется от базового класса BasePermission. Этот класс обязан реализовать метод has_permission() или has_object_permission().
Разберёмся с обоими:
has_permission(self, request, view)— проверка на уровне представления.
Этот метод решает, можно ли пользователю вообще обращаться к данному эндпоинту.has_object_permission(self, request, view, obj)— проверка на уровне объекта.
Здесь уже проверяется доступ к определённому объекту. Например, можно настроить так, чтобы пользователь смог редактировать только свои записи.
Создаём первое кастомное разрешение
Начнем с простого. Мы создадим кастомное разрешение, которое будет допускать к эндпоинту только пользователей с подтвержденной электронной почтой.
Шаг 1. Простейший пример кастомного разрешения
Создаём файл permissions.py внутри любого приложения. Например, в папке вашего основного приложения.
from rest_framework.permissions import BasePermission
class IsEmailVerified(BasePermission):
"""
Разрешение позволяет доступ только пользователям с подтвержденным email.
"""
def has_permission(self, request, view):
# Берем пользователя из запроса и проверяем, подтвержден ли его email
return bool(request.user and request.user.is_authenticated and request.user.is_email_verified)
Как это работает:
- Мы наследуемся от
BasePermission. - Переопределяем метод
has_permission(), который возвращаетTrue, если доступ разрешён, иFalse, если нет. - Проверяем
request.user.is_email_verified— флаг email-подтверждения.
Шаг 2. Подключаем разрешение в представлении
Теперь подключим наше новое разрешение к какому-нибудь APIView.
from rest_framework.views import APIView
from rest_framework.response import Response
from .permissions import IsEmailVerified
class VerifiedUserView(APIView):
permission_classes = [IsEmailVerified]
def get(self, request):
return Response({"message": "Вы прошли в суперсекретный клуб!"})
Что произойдет:
- Если пользователь не подтвердил email, он получит стандартный ответ с ошибкой 403 Forbidden.
- Если email подтверждён, пользователь увидит сообщение о своём успехе.
Проверка разрешений на уровне объектов
Иногда нужно ограничить доступ не только в целом, но и к конкретным записям. Например, разрешить пользователю редактировать только свои комментарии.
Шаг 1. Разрешение для объектов
Определим кастомное разрешение, которое позволит редактировать только свои записи.
from rest_framework.permissions import BasePermission
class IsOwner(BasePermission):
"""
Доступ разрешён только владельцам объекта.
"""
def has_object_permission(self, request, view, obj):
# Проверяем, что объект имеет поле user, и оно совпадает с request.user
return obj.user == request.user
Шаг 2. Подключаем в ViewSet
Теперь применим это разрешение в ViewSet.
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated
from .models import Comment
from .serializers import CommentSerializer
from .permissions import IsOwner
class CommentViewSet(ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [IsAuthenticated, IsOwner]
def get_permissions(self):
# has_object_permission применяется только к "безопасным" методам, таким как GET
if self.action in ['update', 'partial_update', 'destroy']:
return [IsOwner()]
return [IsAuthenticated()]
Полезные трюки с кастомными разрешениями
Сложные проверки с несколькими условиями
Иногда нужно учесть несколько факторов. Например: «Пользователь должен быть администратором или владельцем объекта».
from rest_framework.permissions import BasePermission, SAFE_METHODS
class IsAdminOrOwner(BasePermission):
"""
Доступ только для админов или владельцев объекта.
"""
def has_object_permission(self, request, view, obj):
return bool(
request.user and
(
request.user.is_staff or obj.user == request.user
)
)
Логика с SAFE_METHODS
Иногда можно разрешить просмотр всем, но редактирование только избранным.
class ReadOnlyOrAdmin(BasePermission):
"""
Доступ на чтение для всех, но изменения могут делать только админы.
"""
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and request.user.is_staff
)
Тестируем наши кастомные разрешения
Чтобы убедиться в корректной работе кастомных разрешений, важно протестировать их. Можно это сделать с помощью встроенного DRF тест-клиента или pytest.
Пример теста разрешения
Используем Django TestCase для проверки.
from django.test import TestCase
from rest_framework.test import APIClient
from django.contrib.auth.models import User
from .models import Post
class PermissionTests(TestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_user(username="testuser", password="password", email="test@example.com")
self.admin = User.objects.create_superuser(username="admin", password="adminpass", email="admin@example.com")
self.post = Post.objects.create(title="Test Post", content="Some content", user=self.user)
def test_owner_permission(self):
self.client.force_authenticate(user=self.user)
response = self.client.delete(f"/posts/{self.post.id}/")
self.assertEqual(response.status_code, 204) # Успешное удаление своим владельцем
def test_admin_permission(self):
self.client.force_authenticate(user=self.admin)
response = self.client.delete(f"/posts/{self.post.id}/")
self.assertEqual(response.status_code, 204) # Админ может удалять любой пост
def test_no_permission(self):
another_user = User.objects.create_user(username="another", password="password", email="another@example.com")
self.client.force_authenticate(user=another_user)
response = self.client.delete(f"/posts/{self.post.id}/")
self.assertEqual(response.status_code, 403) # Другой пользователь не может удалить пост
Типичные ошибки и грабли при настройке разрешений
Когда работаете с кастомными разрешениями, помните:
- Разрешения проверяются сверху вниз. Если у вас несколько классов разрешений, хотя бы одно из них может «завалить» запрос.
- Не забывайте настроить
IsAuthenticatedвDEFAULT_PERMISSION_CLASSES, если аутентификация обязательна по умолчанию. - Часто забывают проверять, что
request.userможет бытьAnonymousUser(это происходит, если пользователь не аутентифицирован).
Теперь, когда у вас есть мощный набор инструментов для работы с кастомными разрешениями, вы можете настроить свои API так, чтобы они соответствовали абсолютно любым требованиям безопасности! 🚀
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ