Как работает система прав доступа Django и как реализовать кастомные разрешения?
Django создаёт 4 разрешения (add/change/delete/view) для каждой модели. Кастомные права задаются через Meta.permissions. Объектные разрешения реализуются через кастомный authentication backend или библиотеку django-guardian.
Система прав доступа Django
Django поставляется с встроенной системой разрешений, основанной на трёх понятиях: Permission (разрешение), Group (группа) и методах проверки на объекте пользователя.
Встроенные разрешения
При выполнении migrate Django автоматически создаёт 4 разрешения для каждой модели: add_modelname, change_modelname, delete_modelname, view_modelname.
# Проверка разрешений на объекте пользователя:
user.has_perm("myapp.view_article") # bool
user.has_perms(["myapp.add_article", "myapp.change_article"])
# В шаблоне:
# {% if perms.myapp.delete_article %}
# <button>Удалить</button>
# {% endif %}
Назначение разрешений
from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
from myapp.models import Article
content_type = ContentType.objects.get_for_model(Article)
perm = Permission.objects.get(content_type=content_type, codename="change_article")
user = User.objects.get(username="editor")
user.user_permissions.add(perm)
# Через группу:
from django.contrib.auth.models import Group
editors = Group.objects.get(name="Editors")
editors.permissions.add(perm)
user.groups.add(editors)
Декораторы и миксины во View
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
# FBV:
@login_required
@permission_required("myapp.change_article", raise_exception=True)
def edit_article(request, pk):
...
# CBV:
class ArticleUpdateView(PermissionRequiredMixin, UpdateView):
permission_required = "myapp.change_article"
model = Article
fields = ["title", "body"]
Кастомные разрешения
Объявляйте через Meta.permissions:
class Article(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
"auth.User", on_delete=models.CASCADE
)
class Meta:
permissions = [
("publish_article", "Can publish article"),
("feature_article", "Can mark article as featured"),
]
После makemigrations + migrate разрешения доступны как myapp.publish_article.
Объектные разрешения (object-level permissions)
Встроенная система проверяет разрешения на уровне модели, не объекта. Для объектных разрешений используйте django-guardian:
pip install django-guardian
# settings.py
INSTALLED_APPS += ["guardian"]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
]
# Назначение:
from guardian.shortcuts import assign_perm, remove_perm, get_objects_for_user
article = Article.objects.get(pk=1)
assign_perm("myapp.change_article", user, article) # только для этого объекта
user.has_perm("myapp.change_article", article) # True
user.has_perm("myapp.change_article") # False (нет общего права)
# QuerySet объектов, на которые есть право:
articles = get_objects_for_user(user, "myapp.change_article")
Кастомный бэкенд аутентификации для своей логики
# myapp/backends.py
class ArticleOwnerBackend:
def authenticate(self, request, **kwargs):
return None # не аутентифицируем, только авторизуем
def has_perm(self, user_obj, perm, obj=None):
if perm == "myapp.change_article" and obj is not None:
return obj.author_id == user_obj.pk
return False
# settings.py
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"myapp.backends.ArticleOwnerBackend",
]
Подводные камни
- Разрешения пользователя кэшируются на объекте
userвнутри запроса. Послеuser.user_permissions.add()вызовитеuser = User.objects.get(pk=user.pk)или очиститеuser._perm_cache, иначе проверка вернёт старое значение. - Superuser (
is_superuser=True) получает все разрешения без проверки — не полагайтесь на ограничения для суперпользователя в тестах. has_perm()с объектом всегда возвращает False с дефолтным бэкендом — нужен кастомный бэкенд или django-guardian.- Встроенные 4 разрешения создаются только при первом
migrateпосле появления модели — если модель добавлена в обход миграций, разрешений нет. permission_requiredпо умолчанию редиректит на страницу логина, а не бросает 403 — передавайтеraise_exception=True.- DRF использует свою систему permissions (
IsAuthenticated,DjangoModelPermissions) — не путайте с Django permissions; их нужно явно связывать. - django-guardian добавляет дополнительные таблицы и JOIN при
get_objects_for_user()— на больших данных нужен тщательный анализ производительности.
Common mistakes
- Описывать permissions только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Django и реальной эксплуатацией.
What the interviewer is testing
- Объясняет permissions через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.