JavaRush /Курсы /Модуль 3: Django /Кастомизация разрешений для представлений

Кастомизация разрешений для представлений

Модуль 3: Django
18 уровень , 8 лекция
Открыта

Ну как, готовы взять под контроль свои API? Сегодня мы узнаем, как кастомизировать разрешения (permissions) в Django REST Framework (DRF). Это как дать каждому маршруту вашего API суперсилу: «Ты можешь пропустить только админов!» или «Ты открыт только для пользователей с подтвержденной электронной почтой!». Согласитесь, звучит интересно!

Из этой лекции вы узнаете:

  • Как создавать собственные классы разрешений.
  • Примеры ситуаций, где кастомизация разрешений полезна.
  • Как проверить, что наши кастомные разрешения работают корректно.

Разрешения в DRF: повторение на пальцах

DRF уже даёт нам свои стандартные классы:

  • AllowAny: вообще без фейсконтроля, пускаем всех.
  • IsAuthenticated: пускаем только зарегистрированных (аутентифицированных) пользователей.
  • IsAdminUser: внутрь проходят только администраторы.
  • IsAuthenticatedOrReadOnly: записываться могут только свои, читать — кто угодно.

Только вот иногда стандартного фейсконтроля недостаточно. Например, как ограничить API доступ только пользователям с определенным статусом (например, только подтвержденным пользователям)? Здесь в дело вступает кастомизация.

Как работают кастомные разрешения?

Кастомные разрешения в DRF — это просто Python-класс, который наследуется от базового класса BasePermission. Этот класс обязан реализовать метод has_permission() или has_object_permission().

Разберёмся с обоими:

  1. has_permission(self, request, view) — проверка на уровне представления.
    Этот метод решает, можно ли пользователю вообще обращаться к данному эндпоинту.

  2. 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)

Как это работает:

  1. Мы наследуемся от BasePermission.
  2. Переопределяем метод has_permission(), который возвращает True, если доступ разрешён, и False, если нет.
  3. Проверяем 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 так, чтобы они соответствовали абсолютно любым требованиям безопасности! 🚀

1
Задача
Модуль 3: Django, 18 уровень, 8 лекция
Недоступна
Создание базового кастомного разрешения
Создание базового кастомного разрешения
1
Задача
Модуль 3: Django, 18 уровень, 8 лекция
Недоступна
Разрешение для статуса пользователя
Разрешение для статуса пользователя
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ