DjangoMiddleTechnical

Как работает система прав доступа 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-сценарий с ожидаемым поведением.
  • Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.

Sources

Related topics