Django-Admin.ru

Права доступа в Django: permissions и группы

Права доступа в Django: модель Permission, стандартные и свои права, группы, has_perm и кеш, permission_required, perms в шаблонах, админка и объектные права.

Редакция Django-Admin.ru
· 20 мин чтения

Содержание · 15 разделов
  1. Как устроены права: Permission и ContentType
  2. Стандартные права: add, change, delete и view
  3. Свои права в Meta.permissions
  4. Как выдать права пользователю
  5. Группы: права для ролей
  6. Проверка прав: has_perm и кеш
  7. Кеш прав
  8. Права в представлениях
  9. Декоратор @permission_required
  10. PermissionRequiredMixin для классов
  11. Права в шаблонах: переменная perms
  12. Права в Django Admin
  13. Объектные права: свой бэкенд, django-rules или django-guardian
  14. Свой бэкенд
  15. django-rules: правила в коде
  16. django-guardian: права на конкретные записи
  17. Что выбрать
  18. Права в Django REST Framework
  19. Как создать группы миграцией данных
  20. Частые ошибки
  21. Право выдано, а has_perm() возвращает False
  22. Вошедший пользователь попадает на страницу входа
  23. Группа из миграции создана без прав
  24. Пользователь с правами не может войти в админку
  25. Удалённое право по-прежнему видно в админке
  26. Частые вопросы
  27. Как проверить, состоит ли пользователь в группе?
  28. Как найти всех пользователей с определённым правом?
  29. Как открыть страницу только сотрудникам или только суперпользователю?
  30. Как показать пользователю только его записи?
  31. Как проверить права в асинхронном представлении?
  32. Итог

Права доступа (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)403403
@login_required + @permission_required(..., raise_exception=True)302 на вход403
PermissionRequiredMixin302 на страницу входа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-rulesdjango-guardian
Где хранятся правилав коде представленияв кодев коде, предикатамив базе, запись на объект
Работает has_perm(perm, obj)нетдадада
Выдать право на одну запись вручнуюнетнетнетда, assign_perm()
Отфильтровать список доступных записейget_queryset()get_queryset()get_queryset()get_objects_for_user()
Дополнительные таблицынетнетнетда, растут с числом объектов
Админкаhas_*_permission()через has_permObjectPermissionsModelAdminGuardedModelAdmin

Правило выбора простое. Если доступ вычисляется из данных («автор», «участник проекта», «тот же отдел») — пишите проверку во 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
↑ ↓ выборEnter открытьEsc закрыть