Права доступа в Django: permissions и группы
Права доступа в Django: модель Permission, стандартные и свои права, группы, has_perm и кеш, permission_required, perms в шаблонах, админка и объектные права.
Содержание · 15 разделов
- Как устроены права: Permission и ContentType
- Стандартные права: add, change, delete и view
- Свои права в Meta.permissions
- Как выдать права пользователю
- Группы: права для ролей
- Проверка прав: has_perm и кеш
- Кеш прав
- Права в представлениях
- Декоратор @permission_required
- PermissionRequiredMixin для классов
- Права в шаблонах: переменная perms
- Права в Django Admin
- Объектные права: свой бэкенд, django-rules или django-guardian
- Свой бэкенд
- django-rules: правила в коде
- django-guardian: права на конкретные записи
- Что выбрать
- Права в Django REST Framework
- Как создать группы миграцией данных
- Частые ошибки
- Право выдано, а has_perm() возвращает False
- Вошедший пользователь попадает на страницу входа
- Группа из миграции создана без прав
- Пользователь с правами не может войти в админку
- Удалённое право по-прежнему видно в админке
- Частые вопросы
- Как проверить, состоит ли пользователь в группе?
- Как найти всех пользователей с определённым правом?
- Как открыть страницу только сотрудникам или только суперпользователю?
- Как показать пользователю только его записи?
- Как проверить права в асинхронном представлении?
- Итог
Права доступа (permissions) в Django встроены в django.contrib.auth. Для каждой модели фреймворк сам создаёт четыре права — add, change, delete и view. Свои права объявляются в Meta.permissions. Права выдают пользователю напрямую или через группу, а проверяют строкой "<приложение>.<codename>": методом user.has_perm("library.change_book"), декоратором @permission_required, миксином PermissionRequiredMixin и переменной perms в шаблоне.
Встроенная система работает на уровне модели: «может менять любые книги». Право на конкретный объект — «может менять только свои книги» — добавляют своим бэкендом, django-rules или django-guardian. Ниже — как всё это устроено и где обычно ошибаются. Примеры проверены на Django 6.1 и Python 3.12; всё, кроме отмеченного, работает и в Django 5.2 LTS.
Как устроены права: Permission и ContentType
Право — это строка в таблице auth_permission. Модель Permission хранит три поля:
| Поле | Что хранит | Пример |
|---|---|---|
name | человекочитаемое название, до 255 символов | Can change book |
content_type | ссылку на ContentType — к какой модели относится | library | book |
codename | машинное имя, до 100 символов | change_book |
ContentType из django.contrib.contenttypes — реестр всех моделей проекта: одна запись на модель с полями app_label и model. Через него право привязано к модели, поэтому в INSTALLED_APPS нужны оба приложения — django.contrib.auth и django.contrib.contenttypes. В проекте после startproject они уже подключены.
Примеры ниже используют приложение library с такой моделью:
# library/models.py
from django.conf import settings
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="books"
)
is_published = models.BooleanField(default=False)
class Meta:
permissions = [
("publish_book", "Может публиковать книги"),
("export_book", "Может выгружать каталог"),
]
Посмотреть права модели можно в python manage.py shell:
>>> from django.contrib.auth.models import Permission
>>> from django.contrib.contenttypes.models import ContentType
>>> from library.models import Book
>>> ct = ContentType.objects.get_for_model(Book)
>>> for p in Permission.objects.filter(content_type=ct).order_by("codename"):
... print(p.codename, "|", p.name)
...
add_book | Can add book
change_book | Can change book
delete_book | Can delete book
export_book | Может выгружать каталог
publish_book | Может публиковать книги
view_book | Can view book
При проверке право записывают как app_label.codename — library.change_book. Имя модели в codename — в нижнем регистре, без подчёркиваний между словами: для модели BookReview получится change_bookreview. С Django 6.1 готовую строку отдаёт свойство Permission.user_perm_str:
>>> Permission.objects.get(codename="publish_book").user_perm_str
'library.publish_book'
Права создаются не в миграции, а обработчиком сигнала post_migrate — после того, как команда migrate применит все миграции. Поэтому новая модель получает права только после migrate, а в миграциях данных их может ещё не быть (решение — в разделе про группы в миграции).
Важно: если вы пишете свою модель пользователя на
AbstractBaseUser, добавьтеPermissionsMixin— иначе у пользователя не будет полейgroups,user_permissionsи методовhas_perm(). Модель наAbstractUserвсё это уже содержит. Подробнее — в статье о своей модели пользователя.
Стандартные права: add, change, delete и view
Четыре права на модель создаются автоматически:
| Право | Действие | Где используется по умолчанию |
|---|---|---|
add_book | создание записей | кнопка «Добавить» в админке |
change_book | изменение записей | форма редактирования в админке |
delete_book | удаление записей | удаление в админке и массовое действие |
view_book | просмотр без изменения | список и карточка в админке в режиме чтения |
За пределами админки Django эти права сам не проверяет: страница сайта с формой редактирования открыта всем, пока вы не добавите проверку в представление.
Набор стандартных прав меняется опцией Meta.default_permissions. Например, для журнала, записи которого нельзя удалять:
class AuditLog(models.Model):
# ...
class Meta:
default_permissions = ("add", "view") # без change и delete
Пустой кортеж default_permissions = () отключает стандартные права совсем — так делают для служебных моделей, которые не показывают в админке.
Названия стандартных прав в базе — английские («Can add book»): Django собирает их из verbose_name модели при создании. На проверки это не влияет — в коде используется только codename.
Свои права в Meta.permissions
Права для действий, которые не сводятся к CRUD — публикация, выгрузка, модерация, — объявляют в Meta.permissions списком пар (codename, название):
class Meta:
permissions = [
("publish_book", "Может публиковать книги"),
("export_book", "Может выгружать каталог"),
]
Изменение Meta.permissions — это изменение модели, поэтому нужны обе команды:
python manage.py makemigrations library
python manage.py migrate
Первая создаст миграцию с операцией AlterModelOptions, вторая — запишет новые права в auth_permission. Как работают миграции и что делать при конфликтах, разобрано в руководстве по миграциям Django.
Если удалить право из Meta.permissions, запись в базе останется и продолжит показываться в админке. Удалите её вручную или миграцией данных:
Permission.objects.filter(
content_type__app_label="library", codename="export_book"
).delete()
С Django 6.1 при переименовании модели миграцией RenameModel права переименовываются вместе с ней. В более ранних версиях, включая 5.2, старые права (change_oldname) остаются, а новые создаются заново — группы и пользователей придётся перенастроить.
Право можно создать и без модели — через Permission.objects.create(codename=..., name=..., content_type=ct). Так делают для прав, не связанных с одной моделью, но объявление в Meta надёжнее: право попадает в миграции и одинаково появляется на всех серверах.
Как выдать права пользователю
У пользователя есть два связанных поля: user_permissions — личные права и groups — группы. Оба поля — связи «многие ко многим», поэтому работают методы add(), remove(), set() и clear():
>>> from django.contrib.auth.models import User
>>> ivan = User.objects.get(username="ivan")
>>> perm = Permission.objects.get(codename="change_book", content_type__app_label="library")
>>> ivan.user_permissions.add(perm)
>>> ivan.user_permissions.remove(perm)
>>> ivan.user_permissions.clear() # снять все личные права
>>> ivan.user_permissions.set([perm]) # заменить весь набор
Совет: ищите право по
codenameвместе сcontent_type__app_label. В разных приложениях могут быть модели с одинаковым именем, и запрос только поcodenameвернётMultipleObjectsReturned.
То же самое делается в админке: в карточке пользователя есть поля «Группы» и «Права пользователя». Если создаёте первого администратора, используйте createsuperuser — суперпользователю права не выдают, они есть у него по умолчанию.
Группы: права для ролей
Выдавать права каждому пользователю по отдельности неудобно. Группа (Group) описывает роль один раз: «Редакторы», «Авторы», «Модераторы». Пользователь получает объединение прав всех своих групп и личных прав.
>>> from django.contrib.auth.models import Group
>>> editors = Group.objects.create(name="Редакторы")
>>> editors.permissions.add(
... *Permission.objects.filter(
... content_type__app_label="library",
... codename__in=["view_book", "publish_book"],
... )
... )
>>> ivan.groups.add(editors)
>>> ivan = User.objects.get(pk=ivan.pk) # перечитать пользователя, см. ниже про кеш
>>> ivan.get_user_permissions()
{'library.change_book'}
>>> ivan.get_group_permissions()
{'library.publish_book', 'library.view_book'}
>>> sorted(ivan.get_all_permissions())
['library.change_book', 'library.publish_book', 'library.view_book']
Запрещающих прав в Django нет: право можно только добавить. Если роль «Стажёр» должна видеть меньше, чем «Редактор», соберите для неё отдельную группу с меньшим набором прав.
Кто входит в группу, показывает обратная связь user_set:
>>> editors.user_set.all()
<QuerySet [<User: ivan>]>
Проверка прав: has_perm и кеш
Основные методы пользователя:
| Метод | Что возвращает |
|---|---|
has_perm("app.codename") | True, если право есть |
has_perms([...]) | True, если есть все права из списка |
has_module_perms("app") | True, если есть хотя бы одно право в приложении |
get_all_permissions() | множество строк app.codename — личные и групповые |
get_user_permissions() | только личные права |
get_group_permissions() | только права из групп |
>>> ivan.has_perms(["library.view_book", "library.publish_book"])
True
>>> ivan.has_perms(["library.view_book", "library.delete_book"])
False
>>> ivan.has_module_perms("library")
True
>>> ivan.has_perm("change_book") # без app_label — всегда False
False
>>> ivan.has_perm("library.fly_book") # несуществующее право — False, без ошибки
False
Опечатка в имени права не вызывает исключения — has_perm() просто вернёт False. Проверяйте строки прав в тестах.
Правила проверки:
- активный суперпользователь получает
Trueна любое право, даже несуществующее; - неактивный пользователь (
is_active=False) получаетFalse, в том числе суперпользователь; - анонимный пользователь получает
False.
Кеш прав
ModelBackend при первой проверке загружает все права пользователя одним запросом и сохраняет их в атрибутах объекта. Следующие вызовы has_perm() в рамках этого объекта в базу не ходят. Обратная сторона: право, выданное после первой проверки, этот объект не увидит.
>>> anna = User.objects.create_user("anna", password="...")
>>> anna.has_perm("library.change_book")
False
>>> anna.user_permissions.add(perm)
>>> anna.has_perm("library.change_book")
False # права уже закешированы
>>> anna.refresh_from_db()
>>> anna.has_perm("library.change_book")
False # refresh_from_db() кеш прав не сбрасывает
>>> anna = User.objects.get(pk=anna.pk)
>>> anna.has_perm("library.change_book")
True
В обычной работе сайта это незаметно: в каждом запросе пользователь загружается заново, и выданное право начинает действовать со следующего запроса — перелогиниваться не нужно. Проблема возникает в тестах, в shell и в коде, который выдаёт право и тут же его проверяет. Решение — перечитать пользователя через User.objects.get(pk=...).
В асинхронном коде используйте парные методы ahas_perm(), ahas_perms() и aget_all_permissions() (появились в Django 5.2).
Права в представлениях
Декоратор @permission_required
Для функций-представлений:
# library/views.py
from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import get_object_or_404, redirect
from .models import Book
@permission_required("library.publish_book", raise_exception=True)
def publish_book(request, pk):
book = get_object_or_404(Book, pk=pk)
book.is_published = True
book.save(update_fields=["is_published"])
return redirect("book_list")
@login_required
@permission_required(["library.view_book", "library.export_book"], raise_exception=True)
def export_books(request):
...
Параметры:
perm— строка или список строк; со списком нужны все права;raise_exception=True— отдать 403 вместо перенаправления;login_url— куда перенаправлять, еслиraise_exceptionне задан.
Без raise_exception пользователь без права попадает на страницу входа — даже если он уже вошёл. Для вошедшего пользователя это выглядит как ошибка. С raise_exception=True анонимный посетитель получит 403 вместо формы входа. Поэтому обычно ставят оба декоратора: @login_required снаружи отправляет гостя на вход, @permission_required(..., raise_exception=True) отдаёт 403 вошедшему без права.
Декоратор работает и с асинхронными представлениями — внутри он вызывает ahas_perms().
PermissionRequiredMixin для классов
В представлениях-классах используют миксин. Он должен стоять в списке родителей первым:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView
from .models import Book
class BookListView(PermissionRequiredMixin, ListView):
model = Book
permission_required = "library.view_book" # строка или кортеж прав
template_name = "library/book_list.html"
Миксин сам различает ситуации: анонимного посетителя перенаправляет на вход, вошедшему без права отдаёт 403. Атрибут raise_exception = True включает 403 и для анонимов, permission_denied_message задаёт текст исключения.
Что получает посетитель в каждом случае (проверено тест-клиентом Django 6.1):
| Способ проверки | Аноним | Вошёл, права нет |
|---|---|---|
@permission_required(perm) | 302 на страницу входа | 302 на страницу входа |
@permission_required(perm, raise_exception=True) | 403 | 403 |
@login_required + @permission_required(..., raise_exception=True) | 302 на вход | 403 |
PermissionRequiredMixin | 302 на страницу входа | 403 |
Для проверки, которая зависит от объекта, переопределите has_permission(). Например, менять книгу могут её автор и редакторы с правом публикации:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import UpdateView
class BookUpdateView(PermissionRequiredMixin, UpdateView):
model = Book
fields = ["title"]
permission_required = "library.change_book"
success_url = "/books/"
def has_permission(self):
if not super().has_permission(): # сначала право на модель
return False
book = self.get_object()
return book.author == self.request.user or self.request.user.has_perm(
"library.publish_book"
)
Для произвольных условий без прав есть UserPassesTestMixin с методом test_func() и декоратор @user_passes_test. Как устроены вход и login_required, описано в руководстве по аутентификации в Django.
Права в шаблонах: переменная perms
Контекстный процессор django.contrib.auth.context_processors.auth (подключён в настройках по умолчанию) передаёт в каждый шаблон переменную perms с правами текущего пользователя:
{% for book in book_list %}
<h2>{{ book.title }}</h2>
{% if perms.library.change_book %}
<a href="{% url 'book_edit' book.pk %}">Редактировать</a>
{% endif %}
{% if perms.library.publish_book and not book.is_published %}
<form method="post" action="{% url 'book_publish' book.pk %}">
{% csrf_token %}<button>Опубликовать</button>
</form>
{% endif %}
{% endfor %}
{% if perms.library %}
<a href="{% url 'book_list' %}">Каталог</a> {# есть хоть одно право в приложении #}
{% endif %}
{% if "library.export_book" in perms %}
<a href="{% url 'book_export' %}">Выгрузить</a>
{% endif %}
perms.library.change_book вызывает has_perm(), а perms.library — has_module_perms(). Запись через in удобна, когда имя права лежит в переменной.
Важно: скрытая кнопка не защищает действие. Проверка в шаблоне только убирает ссылку из интерфейса — сам адрес можно открыть вручную, поэтому то же право обязательно проверяйте в представлении. Синтаксис тегов
ifиurlразобран в статье о шаблонах Django.
Права в Django Admin
Админка целиком построена на этой системе прав:
- войти в админку может только пользователь с
is_staff=True; без флага он попадёт на форму входа, даже имея права; - сотрудник без прав на модель не увидит её в меню, а прямой адрес списка вернёт 403;
- с одним
view_bookкарточка открывается только для чтения; - суперпользователь видит и может всё.
Тонкая настройка делается методами ModelAdmin: has_view_permission(), has_add_permission(), has_change_permission(), has_delete_permission() и has_module_permission(). Методы с параметром obj вызываются и без объекта (obj=None — например, для страницы списка), и с конкретной записью. Учитывайте None, иначе получите AttributeError на странице списка.
Пример: автор правит только свои книги, редакторы с правом публикации — любые, удаляет только суперпользователь:
# library/admin.py
from django.contrib import admin
from .models import Book
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published"]
exclude = ["author"]
def save_model(self, request, obj, form, change):
if not change:
obj.author = request.user # автор — тот, кто создал
super().save_model(request, obj, form, change)
def has_change_permission(self, request, obj=None):
if not super().has_change_permission(request, obj):
return False
if obj is None or request.user.has_perm("library.publish_book"):
return True
return obj.author == request.user
def has_delete_permission(self, request, obj=None):
return request.user.is_superuser
Сотрудник с правами view_book и change_book откроет свою книгу для редактирования, а чужую — только для просмотра; POST-запрос на изменение чужой книги вернёт 403. Чтобы чужие записи не попадали даже в список, дополнительно переопределите get_queryset(). Остальные настройки админки — в руководстве по Django Admin.
Объектные права: свой бэкенд, django-rules или django-guardian
Встроенный ModelBackend объектные права не поддерживает: если передать объект, has_perm() вернёт False даже при наличии права на модель.
>>> ivan.has_perm("library.change_book")
True
>>> ivan.has_perm("library.change_book", book)
False
Вызов has_perm(perm, obj) при этом существует: Django по очереди опрашивает все бэкенды из AUTHENTICATION_BACKENDS и вернёт True, если хотя бы один ответил «да». Объектные права добавляют дополнительным бэкендом.
Свой бэкенд
Когда правило одно и простое — «автор управляет своими записями», — хватит десятка строк:
# library/backends.py
from django.contrib.auth.backends import BaseBackend
class AuthorPermissionBackend(BaseBackend):
"""Автор может менять и удалять свои книги."""
def has_perm(self, user_obj, perm, obj=None):
if obj is None or not user_obj.is_active:
return False
if perm in {"library.change_book", "library.delete_book"}:
return obj.author_id == user_obj.pk
return False
# settings.py
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"library.backends.AuthorPermissionBackend",
]
Теперь ivan.has_perm("library.change_book", book) вернёт True для автора книги. Проверку is_active в своём бэкенде пишите сами — BaseBackend её не делает.
django-rules: правила в коде
django-rules описывает права предикатами — функциями, которые комбинируются операторами &, | и ~. Таблиц в базе нет, правила живут в коде.
pip install rules
# settings.py
INSTALLED_APPS += ["rules.apps.AutodiscoverRulesConfig"] # подхватит rules.py приложений
AUTHENTICATION_BACKENDS = [
"rules.permissions.ObjectPermissionBackend",
"django.contrib.auth.backends.ModelBackend",
]
# library/rules.py
import rules
@rules.predicate
def is_book_author(user, book):
return book is not None and book.author_id == user.pk
is_editor = rules.is_group_member("Редакторы")
rules.add_perm("library.edit_book", is_book_author | is_editor)
Проверка has_perm("library.edit_book") без объекта передаёт в предикат None — без проверки book is not None получите AttributeError. Для представлений в пакете есть свой rules.contrib.views.PermissionRequiredMixin: он передаёт в проверку объект из get_object(), и автор получает 200, а чужой пользователь — 403. Для админки — rules.contrib.admin.ObjectPermissionsModelAdmin.
Последний релиз — 3.5 от сентября 2024 года; в наших проверках на Django 6.1 он работает без замечаний.
django-guardian: права на конкретные записи
django-guardian хранит объектные права в базе: отдельная запись на каждую пару «пользователь или группа — объект — право». Подходит, когда права раздают вручную: «Ольга может редактировать вот этот документ».
pip install django-guardian
# settings.py
INSTALLED_APPS += ["guardian"]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
После migrate guardian создаёт свои таблицы и служебного пользователя AnonymousUser для прав анонимов.
>>> from guardian.shortcuts import assign_perm, get_objects_for_user, remove_perm
>>> assign_perm("change_book", petr, book)
>>> petr.has_perm("library.change_book", book)
True
>>> petr.has_perm("library.change_book", other_book)
False
>>> petr.has_perm("library.change_book") # права на модель это не даёт
False
>>> list(get_objects_for_user(petr, "library.change_book"))
[<Book: Мастер и Маргарита>]
Права можно выдавать и группам (assign_perm("library.change_book", group, book)), а get_objects_for_user() сразу фильтрует список объектов. Для админки есть GuardedModelAdmin с экраном управления объектными правами. Актуальная версия — 3.5.0 (сентябрь 2026), поддерживает Django 5.2 и 6.0, на 6.1 примеры выше работают.
Что выбрать
| Критерий | has_permission() во view | Свой бэкенд | django-rules | django-guardian |
|---|---|---|---|---|
| Где хранятся правила | в коде представления | в коде | в коде, предикатами | в базе, запись на объект |
Работает has_perm(perm, obj) | нет | да | да | да |
| Выдать право на одну запись вручную | нет | нет | нет | да, assign_perm() |
| Отфильтровать список доступных записей | get_queryset() | get_queryset() | get_queryset() | get_objects_for_user() |
| Дополнительные таблицы | нет | нет | нет | да, растут с числом объектов |
| Админка | has_*_permission() | через has_perm | ObjectPermissionsModelAdmin | GuardedModelAdmin |
Правило выбора простое. Если доступ вычисляется из данных («автор», «участник проекта», «тот же отдел») — пишите проверку во view, свой бэкенд или django-rules: ничего не нужно синхронизировать. Если доступ выдают вручную на отдельные записи — нужен django-guardian. Учитывайте, что в guardian число записей о правах растёт как произведение пользователей и объектов, а при удалении объектов их надо чистить.
Права в Django REST Framework
В API права проверяют классы из permission_classes. Два из них используют систему прав Django:
DjangoModelPermissionsсопоставляет методы HTTP с правами:POST→add,PUTиPATCH→change,DELETE→delete. НаGETпо умолчанию право не требуется — чтобы проверятьview, переопределитеperms_map. Представлению нужен атрибутqueryset.DjangoObjectPermissionsдополнительно проверяет права на объект черезhas_perms(perms, obj)и работает в паре с бэкендом объектных прав, например django-guardian. Проверку прав на модель он наследует, поэтому пользователю нужны оба уровня прав. Если вperms_mapзадано право на чтение, а у пользователя его нет, он получит 404 вместо 403 — так API не раскрывает, что объект существует.
Свои классы с has_permission() и has_object_permission() разобраны в разделе о правах руководства по Django REST Framework.
Как создать группы миграцией данных
Группы, созданные вручную в админке, есть только в одной базе. Чтобы роли одинаково появлялись на локальной машине, тестовом и рабочем сервере, создайте их миграцией данных:
python manage.py makemigrations library --empty -n create_groups
# library/migrations/0002_create_groups.py
from django.contrib.auth.management import create_permissions
from django.db import migrations
GROUPS = {
"Редакторы": ["view_book", "change_book", "publish_book"],
"Авторы": ["view_book", "add_book"],
}
def create_groups(apps, schema_editor):
# На пустой базе права ещё не созданы: Django делает это после всех миграций.
app_config = apps.get_app_config("library")
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
Group = apps.get_model("auth", "Group")
Permission = apps.get_model("auth", "Permission")
for name, codenames in GROUPS.items():
group, _ = Group.objects.get_or_create(name=name)
perms = Permission.objects.filter(
content_type__app_label="library", codename__in=codenames
)
group.permissions.set(perms)
def remove_groups(apps, schema_editor):
Group = apps.get_model("auth", "Group")
Group.objects.filter(name__in=GROUPS).delete()
class Migration(migrations.Migration):
dependencies = [
("library", "0001_initial"),
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [migrations.RunPython(create_groups, remove_groups)]
Зачем нужен вызов create_permissions(): права создаются сигналом post_migrate уже после всех миграций. На существующей базе они есть, и миграция без этого вызова сработает. На пустой базе — новый разработчик, CI, тестовая база — запрос к Permission вернёт пустой набор, и группы создадутся без прав. Мы проверили оба варианта на Django 6.1: без вызова группы получили 0 прав, с вызовом — нужные три и два.
Ещё три правила для такой миграции:
- модели берите через
apps.get_model(), а не импортом — так миграция видит схему на момент своего применения; get_or_create()иset()делают миграцию безопасной для повторного запуска;- вторая функция в
RunPythonпозволяет откатить миграцию командойpython manage.py migrate library 0001.
Частые ошибки
Право выдано, а has_perm() возвращает False
Объект пользователя закешировал права до выдачи. Перечитайте его через User.objects.get(pk=...) — refresh_from_db() не поможет. Ещё проверьте формат строки ("library.change_book", а не "change_book"), is_active пользователя и то, что право выдано для нужного приложения.
Вошедший пользователь попадает на страницу входа
Это @permission_required без raise_exception=True: при отсутствии права он перенаправляет на вход всех, включая вошедших. Добавьте raise_exception=True и, если нужно отправлять гостей на вход, поставьте сверху @login_required.
Группа из миграции создана без прав
Миграция выполнилась на пустой базе, где права ещё не созданы. Вызовите create_permissions() в начале функции миграции, как в примере выше.
Пользователь с правами не может войти в админку
У него не стоит is_staff=True. Права на модели определяют, что сотрудник увидит в админке, но вход в неё открывает только этот флаг.
Удалённое право по-прежнему видно в админке
Django не удаляет записи Permission, когда право убирают из Meta.permissions. Удалите запись вручную или миграцией данных.
Частые вопросы
Как проверить, состоит ли пользователь в группе?
user.groups.filter(name="Редакторы").exists(). В шаблоне такой проверки нет, а при переименовании группы код сломается. Надёжнее проверять право, которое группа даёт: has_perm("library.publish_book"). Тогда роль можно перестроить в админке без правки кода.
Как найти всех пользователей с определённым правом?
Методом менеджера with_perm():
>>> list(User.objects.with_perm("library.publish_book"))
[<User: ivan>, <User: root>]
>>> list(User.objects.with_perm("library.publish_book", include_superusers=False))
[<User: ivan>]
Он учитывает и личные права, и права из групп.
Как открыть страницу только сотрудникам или только суперпользователю?
Для сотрудников есть @staff_member_required из django.contrib.admin.views.decorators: без is_staff он перенаправляет на вход в админку. Для суперпользователя — @user_passes_test(lambda u: u.is_superuser) или UserPassesTestMixin. Если доступ зависит от роли, лучше завести отдельное право — его можно выдать и не-суперпользователю.
Как показать пользователю только его записи?
Права решают, можно ли выполнить действие, но не фильтруют записи. Список ограничивают фильтром в get_queryset() представления: Book.objects.filter(author=self.request.user). В django-guardian для этого есть get_objects_for_user().
Как проверить права в асинхронном представлении?
Декоратор @permission_required работает с async def без изменений. Внутри представления получите пользователя через await request.auser() и вызывайте await user.ahas_perm("library.publish_book"). Обращение к request.user в асинхронном коде вызовет SynchronousOnlyOperation.
Итог
- Django создаёт права
add,change,deleteиviewдля каждой модели; свои права объявляются вMeta.permissionsи появляются послеmigrate. - Право — запись
Permission, привязанная к модели черезContentType; проверяется строкой"приложение.codename". - Права выдавайте через группы: группа описывает роль, пользователь получает объединение прав.
has_perm()кеширует права в объекте пользователя — после выдачи права перечитывайте пользователя.- В представлениях используйте
@permission_required(..., raise_exception=True)вместе с@login_requiredилиPermissionRequiredMixin; в шаблонах —perms, но только как дополнение к проверке во view. - В админке права уточняются методами
has_*_permission(), а вход открывает толькоis_staff. - Объектные права не встроены: для правил из данных подходят свой бэкенд или django-rules, для ручной выдачи на записи — django-guardian.
- Группы создавайте миграцией данных с вызовом
create_permissions(), чтобы роли одинаково работали на любой базе.
- #Django
- #Права доступа
- #Permission
- #Группы
- #django.contrib.auth