JavaRush /Курси /Модуль 3: Django /Робота з діями (actions) у ViewSet

Робота з діями (actions) у ViewSet

Модуль 3: Django
Рівень 20 , Лекція 8
Відкрита

actions у Django REST Framework — це спеціальні методи, які можна додати у ваш ViewSet, щоб розширити його функціональність. Під стандартним функціоналом ViewSet ми розуміємо такі базові операції, як create, retrieve, update, delete та list. Але що, якщо вам потрібно щось зовсім особливе, на кшталт повернення певної відповіді чи складної бізнес-логіки, пов’язаної з вашим ресурсом? Ось тут на сцену виходять кастомні дії.

Якщо ви пам’ятаєте, APIView вимагав написання окремих методів для кожної операції (наприклад, get, post, put тощо). Однак ViewSet, окрім стандартних методів CRUD, дозволяє додавати кастомні дії, які можуть виконуватися або по певному HTTP-методу, або бути доступними на своїх власних URL.

Типи кастомних дій

  1. Дії, що працюють лише з одним об'єктом Ці дії виконуються над конкретним об'єктом моделі. Зазвичай вони викликаються з додатковим ідентифікатором в URL. Наприклад: /users/{id}/reset_password.

  2. Дії, що працюють з усім списком Такі дії застосовуються до набору даних, наприклад: /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': 'Помічено як продане'}, status=status.HTTP_200_OK)
        except:
            return Response({'error': 'Не вдалося оновити книгу'}, 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': 'Книга вже заброньована'}, status=status.HTTP_400_BAD_REQUEST)
        book.is_reserved = True
        book.save()
        return Response({'status': 'Книгу успішно заброньовано'}, status=status.HTTP_200_OK)

На цьому етапі ви маєте розуміти, як і навіщо використовувати actions у ViewSet, коли потрібно розширити стандартний функціонал CRUD. Тепер руки сверблять написати щось своє? Чудово! Вперед до практики! 🚀

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ