2026, Jan 10 00:03
Комбинируем разрешения в Django REST framework: OR вместо AND
Как в Django REST framework комбинировать permission-классы через OR: админам — CRUD, аутентифицированным — чтение и DELETE, анонимным — чтение без ловушки AND.
Тонкая настройка прав доступа в Django REST framework кажется простой, пока не смешиваются разные типы пользователей и HTTP‑методы. Частая задача: дать администраторам полный CRUD, аутентифицированным — чтение и удаление, а анонимным — только чтение. Подвох в том, как DRF обрабатывает несколько классов разрешений: если просто перечислить их списком, они объединяются неявной операцией AND, что блокирует законный доступ для не‑админов. Правильный подход — составлять разрешения через OR.
Постановка задачи
Рассмотрим ModelViewSet, зарегистрированный через DefaultRouter. Цель проста: администраторы (is_staff) могут всё; аутентифицированные пользователи — читать и удалять; анонимные — только читать. Первая мысль — сложить несколько классов разрешений в список permission_classes, ожидая, что DRF будет трактовать их как альтернативы.
from rest_framework import permissions
class AuthUserCanViewAndDelete(permissions.BasePermission):
def has_permission(self, request, view):
allowed = ("GET", "HEAD", "OPTIONS", "DELETE")
return bool(
request.method in allowed and
request.user and
request.user.is_authenticated
)
class ReadOnlyOnly(permissions.BasePermission):
def has_permission(self, request, view):
return bool(request.method in permissions.SAFE_METHODS)
class FeatureApi(viewsets.ModelViewSet):
# ...
permission_classes = [
permissions.IsAdminUser,
AuthUserCanViewAndDelete,
ReadOnlyOnly,
]
# ...
Что на самом деле идет не так
DRF воспринимает список классов разрешений как операцию AND. Это означает, что запрос должен одновременно удовлетворять IsAdminUser, AuthUserCanViewAndDelete и ReadOnlyOnly. Например, DELETE от аутентифицированного не‑админа не пройдёт, потому что ReadOnlyOnly отклоняет небезопасные методы. А GET от анонимного пользователя не пройдёт, потому что AuthUserCanViewAndDelete требует аутентификацию. В итоге почти все операции проходят только у админов — вразрез с задуманной политикой.
Решение: комбинировать разрешения через OR
Решение — объединять классы разрешений через OR, чтобы прохождение любого из них давало доступ. Часть нужного уже есть из коробки (IsAdminUser), остальное легко выразить простыми кастомными разрешениями. Ниже — композиция, которая прямо следует этой идее.
from rest_framework import permissions
class AuthOnlyReadAndDelete(permissions.BasePermission):
"""
Аутентифицированные пользователи могут выполнять GET, HEAD, OPTIONS и DELETE.
"""
def has_permission(self, request, view):
user_ops = ("GET", "HEAD", "OPTIONS", "DELETE")
return bool(
request.method in user_ops and
request.user and
request.user.is_authenticated
)
class JustRead(permissions.BasePermission):
"""
Анонимные и любые пользователи могут выполнять только операции чтения.
"""
def has_permission(self, request, view):
return bool(request.method in permissions.SAFE_METHODS)
class FeatureEndpoint(viewsets.ModelViewSet):
# ...
permission_classes = [
permissions.IsAdminUser | AuthOnlyReadAndDelete | JustRead
]
# ...
Эта композиция ёмко фиксирует политику. Если запрос делает администратор — разрешены все операции. Если пользователь аутентифицирован — доступны безопасные методы плюс DELETE. Если пользователь анонимен — только безопасные методы.
Почему это важно
Опора на поведение списка по умолчанию может незаметно пережать ваш API или привести к запутанным моделям доступа. Когда правила различаются по типам пользователей и HTTP‑методам, важно выражать их как альтернативы, а не как накопительные ограничения. Использование OR делает намерение явным, сохраняет читаемость viewset и согласует проверки прав с реальной моделью доступа.
Практические рекомендации
Если в одном эндпоинте нужны разные уровни доступа, оформляйте их отдельными классов разрешений и объединяйте через OR. Держите логику read‑only простой, чтобы анонимный доступ оставался прозрачным, а дополнительные послабления для аутентифицированных пользователей ограничивайте строго необходимыми методами. При этом администраторов покрывает IsAdminUser — структура остаётся компактной и удобной для аудита.
В результате поведение предсказуемо: администраторы управляют всем, аутентифицированные пользователи могут читать и удалять, анонимные — читать — именно так, как и планировалось.