actions в Django REST Framework — это специальные методы, которые можно добавить в ваш ViewSet, чтобы расширить его функциональность. Под стандартным функционалом ViewSet мы понимаем такие базовые операции, как create, retrieve, update, delete и list. Но что, если вам нужно что-то совершенно особенное, вроде возврата определенного ответа или сложной бизнес-логики, связанной с вашим ресурсом? Вот тут на сцену выходят кастомные действия.
Если вы помните, APIView требовал написания отдельных методов для каждой операции (например, get, post, put и т.д.). Однако ViewSet, кроме стандартных методов CRUD, позволяет добавлять кастомные действия, которые могут выполняться либо по определенному HTTP-методу, либо быть доступными на своих собственных URL.
Типы кастомных действий
Действия, работающие только с одним объектом Эти действия выполняются над конкретным объектом модели. Обычно они вызываются с дополнительным идентификатором в URL. Например:
/users/{id}/reset_password.Действия, работающие со всем списком Такие действия применяются к набору данных, например:
/users/active.
Чтобы добавить кастомное действие в ViewSet, мы используем декоратор @action, предоставляемый DRF. С помощью него мы можем:
- Указывать, какой именно HTTP-метод обрабатывает действие (по умолчанию —
GET). - Настраивать маршрут для кастомного действия.
- Указывать, является ли действие для конкретного объекта или списка.
Давайте посмотрим, как этим пользоваться. В примерах мы будем работать с моделью Book, представляющей книги в библиотечной системе.
Пример: кастомное действие для списка объектов
Предположим, мы хотим получить список всех книг, которые находятся в статусе "бестселлер". Вот как это может выглядеть:
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from .models import Book
from .serializers import BookSerializer
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# Кастомное действие для списка объектов
@action(detail=False, methods=['get'])
def bestsellers(self, request):
# Получаем книги со статусом "бестселлер"
bestsellers = Book.objects.filter(is_bestseller=True)
serializer = self.get_serializer(bestsellers, many=True)
return Response(serializer.data)
- Декоратор
@actionс параметромdetail=Falseозначает, что действие работает с всеми объектами, а не с одним конкретным. - Метод
bestsellersвозвращает отфильтрованный набор данных. - Маршрут для этого эндпойнта будет выглядеть так:
/books/bestsellers/.
Попробуйте отправить GET-запрос на /books/bestsellers/, и вы получите список всех "бестселлеров".
Пример: кастомное действие для одного объекта
Теперь представим, что нам нужно добавить действие для изменения статуса конкретной книги на "продано". Например, это может случиться, когда пользователь покупает книгу.
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework import status
from .models import Book
from .serializers import BookSerializer
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# Кастомное действие для одного объекта
@action(detail=True, methods=['post'])
def mark_as_sold(self, request, pk=None):
try:
book = self.get_object()
book.is_sold = True
book.save()
return Response({'status': 'Marked as sold'}, status=status.HTTP_200_OK)
except:
return Response({'error': 'Could not update book'}, status=status.HTTP_400_BAD_REQUEST)
@actionсdetail=Trueозначает, что действие относится к конкретному объекту.- Мы используем метод
get_object()для получения книги по ееpk. - После обновления объекта, отправляется ответ с сообщением об успешной операции.
Запрос выглядит так: POST /books/5/mark_as_sold/, где 5 — это ID книги, которую мы хотим пометить как "проданную".
Кастомизация маршрута для действий
Иногда стандартный маршрут не подходит. Вы хотите использовать другое имя для URL или добавить свой параметр. Это тоже легко настраивается с помощью @action.
Допустим, мы хотим добавить кастомное действие для поиска книги по названию:
@action(detail=False, methods=['get'], url_path='search/(?P<title>[^/.]+)')
def search_by_title(self, request, title=None):
books = Book.objects.filter(title__icontains=title)
serializer = self.get_serializer(books, many=True)
return Response(serializer.data)
- Параметр
url_pathпозволяет кастомизировать маршрут. - В URL мы явно задаем регулярное выражение для извлечения параметра
title. - Теперь можно отправить запрос вида
/books/search/harry/, чтобы искать книги, содержащие в названии "harry".
Особенности и тонкости кастомных действий
Права доступа
Не забывайте про безопасность! Добавляя кастомные действия, применяйте permissions. Например, только администраторы могут использовать действие mark_as_sold:
from rest_framework.permissions import IsAdminUser
@action(detail=True, methods=['post'], permission_classes=[IsAdminUser])
def mark_as_sold(self, request, pk=None):
...
Методы HTTP
Указывайте, какие HTTP-методы поддерживает ваше действие. Например, если действие должно обрабатывать только POST запросы, явно укажите это в параметре methods. Забудете — и у вашего API будет "шведский стол" из любых методов.
Вложенные сериализаторы
Если ваше кастомное действие возвращает сложную структуру данных, подумайте о создании специализированного сериализатора. Это сделает код чище, а возвратимые данные — структурированнее.
Реальный сценарий: применение actions
Предположим, у вас есть библиотечный API, где пользователи могут "бронировать" книги. Действие для бронирования может быть реализовано через ViewSet:
@action(detail=True, methods=['post'])
def reserve(self, request, pk=None):
book = self.get_object()
if book.is_reserved:
return Response({'status': 'Book already reserved'}, status=status.HTTP_400_BAD_REQUEST)
book.is_reserved = True
book.save()
return Response({'status': 'Book reserved successfully'}, status=status.HTTP_200_OK)
На этом этапе вы должны понимать, как и зачем использовать actions в ViewSet, когда нужно расширить стандартный функционал CRUD. Теперь руки чешутся написать что-то свое? Отлично! Вперед к практике! 🚀
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ