📖 Теория — Урок 42

← К оглавлению урока

⚡ Ключевые концепции урока

  • request.user — текущий аутентифицированный пользователь в каждом DRF-запросе
  • perform_create(serializer) — переопределяется для автоматического назначения владельца объекта
  • has_permission(request, view) — проверка на уровне представления (list, create)
  • has_object_permission(request, view, obj) — проверка на уровне конкретного объекта (retrieve, update, delete)
  • SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') — методы только для чтения
  • DjangoModelPermissions — проверяет стандартные Django-права: add, change, delete, view
  • Группы — назначение прав сразу множеству пользователей через Django Admin

Часть 1: Получение пользователя из request

В DRF каждый запрос передаёт объект request, который содержит информацию об аутентифицированном пользователе. Для извлечения пользователя используется атрибут request.user.

Это может потребоваться для:

  • управления доступом (фильтрация объектов по владельцу)
  • сохранения связей между пользователями и объектами
  • реализации бизнес-логики, зависящей от конкретного пользователя

Автоматическое назначение владельца при создании объекта

Чтобы не требовать от клиента передавать owner в теле запроса, поле помечается как read_only в сериализаторе, а значение устанавливается автоматически через perform_create:

# serializers.py
from rest_framework import serializers
from .models import Book

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
        read_only_fields = ['owner']  # клиент не передаёт owner
# views.py
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Book
from .serializers import BookSerializer

class BookListCreateView(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        # owner автоматически берётся из запроса
        serializer.save(owner=self.request.user)
Объяснение:
  • request.user — извлекает текущего аутентифицированного пользователя
  • perform_create — метод, вызываемый при создании объекта; позволяет добавить дополнительные данные перед сохранением
Теперь не нужно отправлять id пользователя при создании книги — он будет автоматически получен из запроса.

Фильтрация объектов по текущему пользователю

Чтобы возвращать только те объекты, которые принадлежат текущему пользователю, нужно переопределить get_queryset():

# views.py
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAuthenticated
from .models import Book
from .serializers import BookSerializer

class UserBookListView(ListAPIView):
    serializer_class = BookSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        # Возвращаем только книги текущего пользователя
        return Book.objects.filter(owner=self.request.user)
# urls.py
from django.urls import path
from .views import UserBookListView

urlpatterns = [
    path('user-books/', UserBookListView.as_view()),
]
Использование request.user в представлениях и фильтрация объектов на основе этого пользователя позволяют создавать гибкие и безопасные API.

Часть 2: Разрешения на уровне объектов в DRF

Object-level permissions (разрешения на уровне объектов) в DRF обеспечивают контроль доступа к конкретным объектам данных, а не только к представлениям в целом.

Это полезно, когда нужно управлять доступом к отдельным ресурсам в зависимости от прав или ролей пользователя. Например: пользователь может просматривать любую книгу, но редактировать — только свою.

Два метода проверки разрешений

Метод Когда вызывается Назначение
has_permission(self, request, view) Для каждого запроса к представлению (list, create) Проверка доступа на уровне всего представления или набора объектов
has_object_permission(self, request, view, obj) Для запросов к конкретному объекту (retrieve, update, destroy) Проверка доступа к конкретному объекту
⚠️ has_object_permission вызывается только при вызове get_object() в представлении. Для list-эндпоинтов он не вызывается автоматически — используйте has_permission или фильтрацию в get_queryset().

Класс BasePermission

Кастомные классы разрешений наследуются от BasePermission и реализуют один или оба метода. Оба метода возвращают True (доступ разрешён) или False (доступ запрещён).

Когда использовать кастомные классы

  • Требуется сложная логика, специфичная для вашего приложения
  • Нужно учитывать дополнительные условия: временные рамки, статус объекта, отношения между объектами и пользователями
  • Встроенные классы разрешений DRF не покрывают ваши потребности

Разрешение на изменение только для владельца (IsOwnerOrReadOnly)

# permissions.py
from rest_framework.permissions import BasePermission

class IsOwnerOrReadOnly(BasePermission):
    """
    Разрешает редактирование объектов только их владельцам,
    остальным — только чтение.
    """
    def has_object_permission(self, request, view, obj):
        # Все пользователи могут просматривать (SAFE_METHODS)
        if request.method in ['GET', 'HEAD', 'OPTIONS']:
            return True
        # Только владелец может изменять объект
        return obj.owner == request.user
# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Book
from .serializers import BookSerializer
from .permissions import IsOwnerOrReadOnly

class BookDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsOwnerOrReadOnly]

Обновление модели — добавление поля owner

Чтобы проверить, кто создал объект, модель должна содержать поле owner:

# models.py
from django.contrib.auth.models import User

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=100)
    published_date = models.DateField()
    publisher = models.ForeignKey('Publisher', on_delete=models.CASCADE, null=True, blank=True)
    created_at = models.DateTimeField(null=True, blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    discounted_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
    is_bestseller = models.BooleanField(default=False)
    genres = models.ManyToManyField('Genre', related_name='books')
    is_banned = models.BooleanField(default=False)
    is_deleted = models.BooleanField(default=False)
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='books')
python manage.py makemigrations
python manage.py migrate
Замечание об отсутствии default-значения: поскольку поле owner по умолчанию not null, при создании миграции Django предложит два варианта:
  1. Указать для существующих объектов дефолтное значение (id пользователя)
  2. Исправить модель вручную (добавить null=True)
Выберите первый вариант и укажите id пользователя, который станет владельцем предыдущих объектов.

Проверка разрешения: GET-запрос

Запрос пользователя, который не является владельцем:

GET /books/1/

# Ответ: успешно (все пользователи могут просматривать)
{
    "id": 1,
    "title": "Sample Book",
    "author": "Author Name",
    "published_date": "2023-01-01",
    "owner": 1
}

Проверка разрешения: PUT-запрос не от владельца

PUT /books/1/
{
    "title": "Updated Book Title",
    "author": "Updated Author Name",
    "published_date": "2023-01-01",
    "owner": 1
}

# Ответ (если не владелец): 403 Forbidden
{
    "detail": "You do not have permission to perform this action."
}
Изменения в DRF Browsable API: если пользователь не является владельцем объекта, то в браузере поля для редактирования исчезнут, а кнопки сохранения и удаления станут недоступны.

Часть 3: Разрешения моделей Django

Разрешения моделей (Model Permissions) — механизм контроля доступа в Django для определения, какие действия пользователь может выполнять в отношении определённых объектов или моделей.

Четыре базовых разрешения для каждой модели

Django автоматически создаёт четыре разрешения при создании новой модели:

РазрешениеКодовое имяДействие
viewapp.view_modelnameПросмотр объектов
addapp.add_modelnameДобавление объектов
changeapp.change_modelnameИзменение объектов
deleteapp.delete_modelnameУдаление объектов

Зачем нужны разрешения моделей

  • Контроль доступа: точное определение, кто может выполнять какие действия с данными
  • Разделение обязанностей: разные пользователи имеют разные роли — редакторы, менеджеры, читатели
  • Соблюдение нормативных требований: защита персональных данных, аудит действий

Назначение разрешений через Django Admin

  1. Перейдите в административную панель Django
  2. Откройте раздел Users и выберите нужного пользователя
  3. В разделе Permissions выберите разрешения для модели Book
  4. Например: разрешить добавлять и изменять книги, но запретить удаление
  5. Сохраните изменения

Группы разрешений

Группы (Groups) — совокупность разрешений, которые могут быть присвоены множеству пользователей одновременно. Позволяют назначать права для всех пользователей группы, вместо индивидуального назначения.

Зачем нужны группы:

  • Упрощение управления: вместо назначения прав каждому пользователю создаётся группа с нужными правами
  • Организация ролей: группы «Администраторы», «Редакторы», «Читатели» с соответствующими правами
  • Масштабируемость: в крупных приложениях управление через группы удобнее индивидуального назначения

Создание группы в Django Admin

  1. Перейдите в административную панель Django
  2. Откройте раздел Groups (Группы)
  3. Нажмите Add Group (Добавить группу)
  4. Введите название группы, например «Editors»
  5. В разделе Permissions выберите разрешения: Can add book, Can change book, Can view book
  6. Сохраните группу
  7. Добавьте пользователей в группу — они автоматически получат все разрешения группы

Часть 4: Использование разрешений моделей в DRF

Разрешения, настроенные через Django Admin, можно использовать в DRF для контроля доступа к API через класс DjangoModelPermissions.

DjangoModelPermissions

# views.py
from rest_framework.permissions import DjangoModelPermissions
from rest_framework import viewsets
from .models import Genre
from .serializers import GenreSerializer

class GenreViewSet(viewsets.ModelViewSet):
    queryset = Genre.objects.all()
    serializer_class = GenreSerializer
    permission_classes = [DjangoModelPermissions]
Как работает DjangoModelPermissions: этот класс проверяет стандартные Django-разрешения view, add, change, delete на уровне модели. Он проверит, что у конкретного пользователя есть доступ к взаимодействию с моделью в зависимости от HTTP-метода запроса.

Соответствие HTTP-методов и Django-разрешений

HTTP-методТребуемое разрешение
GET, HEAD, OPTIONSview_<model>
POSTadd_<model>
PUT, PATCHchange_<model>
DELETEdelete_<model>
⚠️ Проверить по документации: в DRF 3.14+ DjangoModelPermissions проверяет разрешение view для GET-запросов только если оно явно настроено. Поведение по умолчанию может отличаться от описанного. См. docs.djangorestframework.org.

Часть 5: Другие примеры кастомных разрешений

IsAdminOrOwner — администраторы или владельцы

# permissions.py
from rest_framework.permissions import BasePermission

class IsAdminOrOwner(BasePermission):
    """
    Разрешает изменение объектов администраторам или их владельцам,
    остальным — только чтение.
    """
    def has_object_permission(self, request, view, obj):
        if request.method in ['GET', 'HEAD', 'OPTIONS']:
            return True
        # Администратор или владелец могут изменять
        return request.user.is_staff or obj.owner == request.user
# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Book
from .serializers import BookSerializer
from .permissions import IsAdminOrOwner

class BookDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsAdminOrOwner]

IsWorkHour — ограничение по времени суток

# permissions.py
from rest_framework.permissions import BasePermission
from datetime import datetime

class IsWorkHour(BasePermission):
    """
    Разрешает доступ только в рабочие часы (9:00–18:00).
    """
    def has_object_permission(self, request, view, obj):
        current_hour = datetime.now().hour
        return 9 <= current_hour < 18
# views.py
from rest_framework.generics import RetrieveAPIView
from .models import Book
from .serializers import BookSerializer
from .permissions import IsWorkHour

class BookDetailView(RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = [IsWorkHour]
Создание кастомных классов разрешений в DRF позволяет реализовывать сложную логику доступа, ориентированную на особенности бизнес-логики и требования к безопасности данных.

Комбинирование классов разрешений

# Несколько классов разрешений — И (AND) логика
class BookView(RetrieveUpdateDestroyAPIView):
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    # Пользователь должен быть аутентифицирован И является владельцем
⚠️ Проверить по документации: в DRF 3.9+ для реализации OR-логики (ИЛИ) используйте оператор | между классами: permission_classes = [IsAuthenticated | IsAdminUser]. Это современный подход Django 5.x / DRF 3.15+. Устаревший способ через rest_framework.permissions.OR не использовался официально — см. документацию DRF.