JavaRush /Курсы /Модуль 3: Django /Создание кастомных разрешений

Создание кастомных разрешений

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

Для начала давайте подумаем, почему стандартных разрешений может быть недостаточно. Рассмотрим пару примеров:

  1. Роль автора: вы хотите, чтобы только пользователь, создавший статью, мог её редактировать или удалять.
  2. Специфическая логика доступа: только пользователи с активной подпиской могут оставлять комментарии.
  3. Динамические условия: доступ к ресурсу разрешён только в случае, если дата создания объекта не старше месяца.

В таких случаях проще и логичнее написать собственное разрешение, чем пытаться объединить стандартные.

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

В DRF разрешение — это класс, который наследуется от BasePermission и реализует метод has_permission() или/и метод has_object_permission(). Давайте разберём их по очереди:

  1. has_permission(self, request, view) — проверяет, может ли пользователь вообще получить доступ к эндпоинту (в рамках всего представления).
  2. has_object_permission(self, request, view, obj) — проверяет права доступа к конкретному объекту (например, к определённой статье).

Пример 1: разрешение только для авторов объекта

Допустим, вы хотите разрешить редактировать записи только тем пользователям, которые их создали.

Код разрешения будет выглядеть так:

from rest_framework.permissions import BasePermission

class IsAuthor(BasePermission):
    """
    Разрешает доступ только авторам объекта.
    """

    def has_object_permission(self, request, view, obj):
        # Проверяем, что пользователь является автором объекта
        return obj.author == request.user

Теперь применим его в нашем ViewSet:

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated
from .permissions import IsAuthor
from .models import Post
from .serializers import PostSerializer

class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

    # Композиция разрешений: пользователь должен быть аутентифицирован и являться автором объекта.
    permission_classes = [IsAuthenticated, IsAuthor]

Такой подход позволит выделить авторов среди всех аутентифицированных пользователей.

Пример 2: разрешение на основе группы

Теперь создадим разрешение, которое позволяет доступ только пользователям, принадлежащим к определённой группе, например, "Модераторы".

from rest_framework.permissions import BasePermission

class IsInModeratorGroup(BasePermission):
    """
    Разрешает доступ только для пользователей из группы "Модераторы".
    """

    def has_permission(self, request, view):
        # Проверяем, что пользователь аутентифицирован и состоит в группе "Модераторы"
        return request.user.is_authenticated and request.user.groups.filter(name="Модераторы").exists()

В этом случае мы проверяем доступ на уровне эндпоинта, а не конкретного объекта.

Применяем разрешение так же, как в предыдущем примере:

permission_classes = [IsAuthenticated, IsInModeratorGroup]

Логика и порядок применения разрешений

DRF позволяет задавать несколько разрешений одновременно, комбинируя их в список permission_classes. При этом все разрешения из списка проверяются; если хотя бы одно возвращает False — доступ будет запрещён.

Пример с комбинированием:

permission_classes = [IsAuthenticated, IsAuthor]

Чтобы запрос прошёл, пользователь должен быть аутентифицирован и являться автором объекта.

Пример 3: условие для GET и POST запросов

Теперь давайте создадим кастомное разрешение, которое:

  • Разрешает GET-запросы всем.
  • Требует аутентификации для POST-запросов.
from rest_framework.permissions import BasePermission, SAFE_METHODS

class ReadOnlyOrIsAuthenticated(BasePermission):
    """
    Разрешает доступ для всех на чтение (GET), но требует аутентификации для записи (POST).
    """

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:  # SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
            return True
        return request.user.is_authenticated

Вот так мы можем одновременно поддерживать открытый доступ для чтения данных и защищённый доступ для их создания.

Интеграция кастомных разрешений в реальном проекте

Предположим, мы работаем над блогом, где:

  • Модераторы могут удалять любые комментарии.
  • Авторы комментариев могут редактировать только свои комментарии.

Создадим два кастомных разрешения: IsModerator и IsCommentAuthor.

from rest_framework.permissions import BasePermission

class IsModerator(BasePermission):
    """
    Проверяет, является ли пользователь Модератором.
    """
    def has_permission(self, request, view):
        return request.user.groups.filter(name="Модераторы").exists()

class IsCommentAuthor(BasePermission):
    """
    Проверяет, является ли пользователь автором комментария.
    """
    def has_object_permission(self, request, view, obj):
        return obj.author == request.user

Теперь настраиваем разрешения в ViewSet:

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, SAFE_METHODS
from .permissions import IsModerator, IsCommentAuthor
from .models import Comment
from .serializers import CommentSerializer

class CommentViewSet(ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

    def get_permissions(self):
        # Открываем доступ только на чтение для всех
        if self.request.method in SAFE_METHODS:
            return [permission() for permission in []]

        # Модераторы могут удалять любые комментарии
        if self.request.method == 'DELETE':
            return [IsModerator()]

        # Любые другие действия (например, PUT) доступны только автору
        return [IsAuthenticated(), IsCommentAuthor()]

Частые ошибки при создании кастомных разрешений

  1. Забыли обработать obj в has_object_permission()
    DRF при доступе к объектам вызывает метод has_object_permission() только если он определён. Если вы ограничиваетесь has_permission(), проверка прав на уровне объектов проигнорируется.

  2. Не возвращают True/False явно
    Методы разрешений всегда должны возвращать строго True или False, иначе возникнут неожиданные ошибки.

  3. Ошибки в логике сложных разрешений
    Например, забыли учесть различные HTTP-методы. Всегда проверяйте свою логику через тесты!

Когда кастомизация — это зло?

Иногда соблазн написать сложные кастомные разрешения может сыграть злую шутку. Если ваши разрешения становятся избыточно комплексными, это затрудняет дальнейшую поддержку проекта. В таких случаях стоит пересмотреть архитектуру проекта или задуматься об использовании ролевой модели (IsInGroup), которая позволяет абстрагироваться от прямых проверок.

Практическое задание

  1. Напишите кастомное разрешение, которое позволит:

    • Пользователям из группы "Администраторы" удалять записи.
    • Остальным пользователям только просматривать записи (GET).
    • Отключите доступ на запись (POST, PUT) для всех остальных.
  2. Примените это разрешение к модели Post в вашем проекте. Напишите несколько тестов с помощью DRF тест-клиента, чтобы проверить разное поведение.

Теперь мы не просто знаем, как использовать встроенные возможности DRF, но и можем создавать собственные кастомные решения, адаптируя систему безопасности наших API к самым сложным сценариям. Кто теперь остановит вас от создания мирового господства с помощью кастомных разрешений? Никто! 🙂

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