📖 Теория — Урок 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
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— метод, вызываемый при создании объекта; позволяет добавить дополнительные данные перед сохранением
Фильтрация объектов по текущему пользователю
Чтобы возвращать только те объекты, которые принадлежат текущему пользователю, нужно переопределить 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
Это полезно, когда нужно управлять доступом к отдельным ресурсам в зависимости от прав или ролей пользователя. Например: пользователь может просматривать любую книгу, но редактировать — только свою.
Два метода проверки разрешений
| Метод | Когда вызывается | Назначение |
|---|---|---|
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
owner по умолчанию not null, при создании миграции Django предложит два варианта:
- Указать для существующих объектов дефолтное значение (id пользователя)
- Исправить модель вручную (добавить
null=True)
Проверка разрешения: 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."
}
Часть 3: Разрешения моделей Django
Четыре базовых разрешения для каждой модели
Django автоматически создаёт четыре разрешения при создании новой модели:
| Разрешение | Кодовое имя | Действие |
|---|---|---|
| view | app.view_modelname | Просмотр объектов |
| add | app.add_modelname | Добавление объектов |
| change | app.change_modelname | Изменение объектов |
| delete | app.delete_modelname | Удаление объектов |
Зачем нужны разрешения моделей
- Контроль доступа: точное определение, кто может выполнять какие действия с данными
- Разделение обязанностей: разные пользователи имеют разные роли — редакторы, менеджеры, читатели
- Соблюдение нормативных требований: защита персональных данных, аудит действий
Назначение разрешений через Django Admin
- Перейдите в административную панель Django
- Откройте раздел Users и выберите нужного пользователя
- В разделе Permissions выберите разрешения для модели Book
- Например: разрешить добавлять и изменять книги, но запретить удаление
- Сохраните изменения
Группы разрешений
Зачем нужны группы:
- Упрощение управления: вместо назначения прав каждому пользователю создаётся группа с нужными правами
- Организация ролей: группы «Администраторы», «Редакторы», «Читатели» с соответствующими правами
- Масштабируемость: в крупных приложениях управление через группы удобнее индивидуального назначения
Создание группы в Django Admin
- Перейдите в административную панель Django
- Откройте раздел Groups (Группы)
- Нажмите Add Group (Добавить группу)
- Введите название группы, например «Editors»
- В разделе Permissions выберите разрешения:
Can add book,Can change book,Can view book - Сохраните группу
- Добавьте пользователей в группу — они автоматически получат все разрешения группы
Часть 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]
view, add, change, delete на уровне модели. Он проверит, что у конкретного пользователя есть доступ к взаимодействию с моделью в зависимости от HTTP-метода запроса.
Соответствие HTTP-методов и Django-разрешений
| HTTP-метод | Требуемое разрешение |
|---|---|
| GET, HEAD, OPTIONS | view_<model> |
| POST | add_<model> |
| PUT, PATCH | change_<model> |
| DELETE | delete_<model> |
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]
Комбинирование классов разрешений
# Несколько классов разрешений — И (AND) логика
class BookView(RetrieveUpdateDestroyAPIView):
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
# Пользователь должен быть аутентифицирован И является владельцем
| между классами: permission_classes = [IsAuthenticated | IsAdminUser]. Это современный подход Django 5.x / DRF 3.15+. Устаревший способ через rest_framework.permissions.OR не использовался официально — см. документацию DRF.