Права доступа в Django: рабочие приёмы разграничения
Пермишены Django легко превратить в кашу из if-ов по всему коду. Разбираем, где проверять права, как не путать модельные пермишены с объектными и когда встроенного механизма уже не хватает.

Задача звучит просто: одни пользователи могут редактировать заказ, другие только смотреть, третьи вообще не должны его видеть. В Django для этого есть штатный механизм разрешений, но на практике проект быстро обрастает проверками request.user.is_staff в шаблонах, дублирующимися if во вьюхах и загадочными строками пермишенов, которые никто не помнит откуда взялись. Разберём, как раскладывать проверки прав по местам так, чтобы через полгода их можно было читать, и где встроенная модель прав перестаёт справляться.
Всё ниже проверено на Django 5.x, но модельные пермишены и PermissionRequiredMixin работают так же начиная с 2.x. Объектные права из коробки Django не даёт — для них нужна сторонняя библиотека, об этом отдельно.
Что даёт Django из коробки
При создании модели Django автоматически заводит четыре пермишена: add, change, delete, view (последний появился в 2.1). Их полное имя строится как <app_label>.<action>_<model> — например, orders.change_order. Проверка одна:
if request.user.has_perm('orders.change_order'):
# пользователю разрешено менять заказы вообще
...Ключевое слово — вообще. Модельный пермишен отвечает на вопрос «может ли этот пользователь менять заказы как класс объектов», а не «может ли он менять вот этот конкретный заказ №4172». Это первая и самая частая точка, где новички обжигаются: выдали change_order — и менеджер может редактировать чужие заказы.
Свои пермишены объявляются в Meta модели:
class Order(models.Model):
# ... поля ...
class Meta:
permissions = [
('cancel_order', 'Может отменять заказы'),
('export_orders', 'Может выгружать заказы в CSV'),
]После этого нужна миграция (makemigrations подхватит изменение Meta.permissions). Проверяются такие пермишены точно так же: request.user.has_perm('orders.cancel_order').
Модельный пермишен — это ответ на вопрос «в принципе можно?». Ответ на «можно с этим объектом?» встроенный Django не даёт вовсе. Смешивать эти два вопроса в одной проверке — источник половины багов доступа.
Где проверять права: одно место вместо десяти
Основная беда разрастающихся проверок — они расползаются: часть во вьюхе, часть в шаблоне, часть в сериализаторе DRF. Правило, которое экономит нервы: решение о доступе принимает вьюха, шаблон и модель только спрашивают.
1. Функциональные вьюхи — декоратор
Для быстрой проверки на входе:
from django.contrib.auth.decorators import permission_required
@permission_required('orders.change_order', raise_exception=True)
def edit_order(request, pk):
...Без raise_exception=True пользователя без прав редиректит на страницу логина — что нелогично, если он уже вошёл. С флагом отдаётся 403, и это почти всегда то, что нужно для авторизованного пользователя.
2. Class-based views — миксины
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import UpdateView
class OrderUpdateView(PermissionRequiredMixin, UpdateView):
model = Order
permission_required = 'orders.change_order'
# можно передать кортеж — тогда нужны ВСЕ перечисленные права
# permission_required = ('orders.change_order', 'orders.view_order')
raise_exception = TrueПорядок наследования важен: PermissionRequiredMixin идёт первым, чтобы его dispatch отработал до логики вьюхи.
3. Шаблоны — только показать или спрятать кнопку
В шаблоне доступен объект perms:
{% if perms.orders.cancel_order %}
<button>Отменить заказ</button>
{% endif %}Это не защита — это UX. Спрятанная кнопка не мешает отправить POST руками. Проверка в шаблоне без дублирующей проверки во вьюхе — дыра, а не безопасность.
Объектные права: где нужна сторонняя библиотека
Когда «может менять свои заказы, но не чужие» — задача не для модельных пермишенов. Есть два рабочих пути.
Путь без библиотек: фильтровать queryset
Часто объектное право сводится к «пользователь видит только своё». Тогда не нужны никакие объектные пермишены — достаточно сузить выборку:
class OrderUpdateView(LoginRequiredMixin, UpdateView):
model = Order
def get_queryset(self):
qs = super().get_queryset()
if self.request.user.has_perm('orders.change_all_orders'):
return qs
# остальные видят и правят только свои
return qs.filter(manager=self.request.user)Если пользователь попытается открыть чужой заказ по прямой ссылке, get_object не найдёт его в суженном queryset и вернёт 404. Это защита на уровне данных, а не только интерфейса, и она переживёт любые манипуляции с URL.
Путь с библиотекой: django-guardian
Когда права выдаются точечно на конкретные объекты (например, доступ к отдельному документу конкретному пользователю), подойдёт django-guardian. Он хранит связку «пользователь — пермишен — объект» в отдельной таблице. Точный синтаксис назначения и проверки прав сверяйте с актуальной документацией guardian для вашей версии — API там менялось, и подставлять по памяти имена функций рискованно. Ставится он в INSTALLED_APPS и требует своего authentication backend в настройках.
Цена решения: каждая объектная проверка — это запрос к таблице прав. На списках объектов это превращается в N+1 по правам, и вопрос производительности придётся решать отдельно (префетч или проверка через queryset вместо поштучной).
Как выбирать подход
| Ситуация | Что использовать | Чем платите |
|---|---|---|
| «Может / не может делать действие вообще» | Модельный или свой пермишен в Meta | Ничем, это штатный путь |
| «Видит и правит только свои объекты» | Фильтрация queryset в get_queryset | Логику доступа нужно продублировать во всех вьюхах модели |
| «Доступ выдаётся точечно на конкретный объект» | django-guardian | Зависимость, лишняя таблица, риск N+1 на списках |
| Сложные роли с иерархией | Группы Django + свои пермишены | Управление ролями через группы, но объектность всё равно решать отдельно |
Группы вместо раздачи прав поштучно
Не назначайте пермишены пользователям напрямую — это не масштабируется. Заведите группы («Менеджеры», «Бухгалтерия»), повесьте на них наборы прав, а пользователей раскладывайте по группам. has_perm учитывает права из групп автоматически. Когда роль меняется, вы правите одну группу, а не сто учёток.
Важная деталь про кеш: результат has_perm кешируется на объекте пользователя в пределах запроса. Если вы поменяли группы пользователю прямо в текущем запросе и тут же проверяете право, перечитайте пользователя из базы или сбросьте кеш прав — иначе увидите старое состояние.
Частые ошибки
- Проверка только в шаблоне. Кнопка спрятана, но эндпоинт открыт. Всегда дублируйте проверку во вьюхе.
- Модельный пермишен там, где нужен объектный. Выдали
change_order— и менеджер правит чужие заказы. Признак: пермишен есть, а «своё/чужое» не разграничено. - Забыли миграцию после правки Meta.permissions. Новый пермишен не появится в базе,
has_permвсегда вернётFalse, а ошибки не будет — тихо не работает. - Суперпользователь ломает тесты доступа. Для
is_superuser=Truehas_permвсегда возвращаетTrue. Проверяйте логику прав на обычном пользователе, иначе «зелёные» тесты ничего не гарантируют. - redirect вместо 403 для вошедшего пользователя. Без
raise_exception=Trueавторизованного без прав уносит на логин — сбивает с толку.
Что делать дальше
Сначала опишите роли проекта на бумаге и разложите их на группы и пермишены — до того, как писать вьюхи. Затем закройте базовый уровень модельными правами через миксины, а «своё/чужое» решите фильтрацией queryset — большинству проектов этого хватает и без сторонних библиотек. К django-guardian переходите только когда права реально выдаются на отдельные объекты, и сразу заложите нагрузочную проверку списков. Тестами покройте не «доступ разрешён», а «доступ запрещён»: именно эти проверки ломаются молча.
Защитник прав потребителей: Досудебные претензии на возврат товара со ссылками на ЗоЗПП.
Спросить за 15 ₽Источники: Django documentation — Permissions and Authorization, django-guardian documentation
Частые вопросы
А если у меня Django 2.x — эти приёмы сработают?
has_perm возвращает False, хотя я выдал право — почему?
Нужен ли мне django-guardian или хватит фильтрации queryset?
Почему тесты на права проходят, а в проде доступ утекает?
Проверка в шаблоне через perms — этого достаточно для защиты?
Как сбросить кеш прав, если я поменял группы прямо в запросе?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту. Instagram, Facebook и WhatsApp принадлежат компании Meta Platforms Inc., признанной в России экстремистской организацией; её деятельность на территории Российской Федерации запрещена.