Как реализована система разрешений (permissions) в Django?

Ответ

В Django реализована мощная встроенная система разрешений (permissions), которая тесно интегрирована с моделями, пользователями и группами. Она позволяет контролировать доступ к объектам и действиям в приложении.

Почему это важно? Система разрешений обеспечивает гранулярный контроль доступа, повышая безопасность приложения и позволяя гибко управлять тем, какие пользователи или группы могут выполнять определенные операции (например, создавать, изменять, удалять или просматривать данные).

Ключевые аспекты:

  • Автоматические разрешения: Для каждой модели Django автоматически создает четыре базовых разрешения: add_<model_name>, change_<model_name>, delete_<model_name>, и view_<model_name>.
  • Кастомные разрешения: Дополнительные, специфичные для приложения разрешения можно определить в классе Meta модели с помощью атрибута permissions.

    from django.db import models
    
    class Task(models.Model):
        name = models.CharField(max_length=255)
        is_closed = models.BooleanField(default=False)
    
        class Meta:
            permissions = [
                ("can_close_task", "Can close task"), # (codename, human-readable name)
                ("can_assign_task", "Can assign task to users"),
            ]
            verbose_name = "Задача"
            verbose_name_plural = "Задачи"
  • Проверка разрешений:
    • На уровне пользователя/группы: Используется метод user.has_perm('app_label.permission_codename').
    • В представлениях (views): С помощью декораторов (@permission_required, @user_passes_test) или миксинов (PermissionRequiredMixin) для классов-представлений.
    • В шаблонах: Через тег {% if perms.app_label.permission_codename %}.

Пример проверки в коде:

from django.contrib.auth.decorators import permission_required
from django.shortcuts import render, get_object_or_404
from .models import Task

@permission_required('myapp.can_close_task', raise_exception=True)
def close_task_view(request, task_id):
    task = get_object_or_404(Task, id=task_id)
    if not task.is_closed:
        task.is_closed = True
        task.save()
        return render(request, 'task_closed.html', {'task': task})
    return render(request, 'task_already_closed.html', {'task': task})

# Или прямая проверка
def some_other_view(request):
    if request.user.has_perm('myapp.can_assign_task'):
        # Логика для назначения задачи
        pass
    # ...

Дополнительные возможности: Для более детального контроля доступа на уровне отдельных объектов (object-level permissions) часто используются сторонние библиотеки, такие как django-guardian. Она позволяет назначать разрешения не только на тип модели, но и на конкретные экземпляры моделей.

Ответ 18+ 🔞

А, ну так, про джанговские разрешения, да? Система, блядь, мощная, как трактор в деревне — если правильно рулить, то всё пашет, а если нет — то сам себя ебнешь в сраку.

Зачем это вообще нужно, спросишь? А чтобы не было, как в том анекдоте: «все могут, а я что, хуже?». Нет, сука, не все. Одному — смотреть, другому — менять, третьему — удалять, а четвёртому вообще нихуя нельзя. Безопасность, понимаешь? Чтобы какой-нибудь Васян из бухгалтерии не взял и не удалил все заказы за 2023 год, потому что «ну я же просто кнопку нажал».

Что там у них есть, эти разрешения:

  • Автоматом прикрученные: Джанга, она умная, блядь. Создал модель — она тебе сразу четыре разрешения на блюдечке: добавить, изменить, удалить и посмотреть. Всё, сиди и распределяй.

  • Свои, кастомные: А если тебе мало? Если тебе нужно, чтобы только избранные могли, например, задачу закрывать или другому юзеру впендюрить? Без проблем, ёпта! Пишешь в модели и всё.

    from django.db import models
    
    class Task(models.Model):
        name = models.CharField(max_length=255)
        is_closed = models.BooleanField(default=False)
    
        class Meta:
            permissions = [
                ("can_close_task", "Can close task"), # (кодовое имя, понятное имя для админа)
                ("can_assign_task", "Can assign task to users"),
            ]
            verbose_name = "Задача"
            verbose_name_plural = "Задачи"

    Вот, добавил два своих. Теперь можно давать право не просто «менять задачу», а конкретно «закрывать» её. Точечно, блядь!

  • А как проверять-то?

    • Спросить у юзера напрямую: user.has_perm('myapp.can_close_task') — вернёт True или False. Проще пареной репы.
    • Вьюхи обернуть: Чтоб не писать в каждой функции проверку, есть декораторы. Навесил @permission_required — и если у юзера нет нужного пермишена, ему либо отказ, либо, если raise_exception=True, вообще ошибку 403 в ебало.
    • В шаблонах спрятать: Хочешь кнопку «Закрыть задачу» показывать только тем, кто может? {% if perms.myapp.can_close_task %} — и готово, хитрая жопа.

Вот тебе пример, чтоб вообще всё стало ясно, как божий день:

from django.contrib.auth.decorators import permission_required
from django.shortcuts import render, get_object_or_404
from .models import Task

@permission_required('myapp.can_close_task', raise_exception=True)
def close_task_view(request, task_id):
    task = get_object_or_404(Task, id=task_id)
    if not task.is_closed:
        task.is_closed = True
        task.save()
        return render(request, 'task_closed.html', {'task': task})
    return render(request, 'task_already_closed.html', {'task': task})

# Или вот так, по-простому, в другой вьюхе
def some_other_view(request):
    if request.user.has_perm('myapp.can_assign_task'):
        # Ну тут логика, чтоб задачу на другого перекинуть
        pass
    # ...

А что если мало? А если тебе мало этих общих разрешений на всю модель? Если нужно, чтобы Петя мог редактировать только СВОИ статьи, а чужие — нихуя? Вот тут встроенная система уже пасует, ей на всё похуй. Для такого есть, блядь, отдельные библиотеки, типа django-guardian. Эта штука позволяет привязывать права к КОНКРЕТНОМУ объекту в базе. То есть дать право «менять» не всем статьям, а только статье с id=42. Вот это уже уровень, ёпта! Но это уже совсем другая история, про которую, если захочешь, в другой раз расскажу. А пока — разберись с базовыми, чтоб не было мудацких дыр в доступе.