Как в Django REST Framework переопределить метод retrieve у ModelViewSet?

Ответ

Для кастомизации логики получения одного объекта в ModelViewSet необходимо переопределить его метод retrieve в дочернем классе. Это позволяет добавить дополнительную логику, изменить данные перед отправкой или выполнить проверку прав.

Зачем это может понадобиться:

  • Добавить в ответ связанные данные, которые не входят в стандартный сериализатор.
  • Увеличить счетчик просмотров объекта.
  • Выполнить сложную проверку прав доступа перед отдачей объекта.

Пример: Добавление дополнительного поля в ответ

from rest_framework import viewsets, status
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def retrieve(self, request, *args, **kwargs):
        # 1. Получаем объект стандартным способом
        instance = self.get_object()

        # 2. Увеличиваем счетчик просмотров (кастомная логика)
        instance.views_count += 1
        instance.save()

        # 3. Сериализуем данные
        serializer = self.get_serializer(instance)
        data = serializer.data

        # 4. Добавляем в ответ дополнительную информацию
        data['is_author'] = instance.author == request.user

        # 5. Возвращаем кастомизированный Response
        return Response(data, status=status.HTTP_200_OK)

Ключевые компоненты:

  • self.get_object(): Стандартный метод DRF для получения объекта по pk из URL.
  • self.get_serializer(instance): Получение экземпляра сериализатора для конкретного объекта.
  • Response(data): Стандартный объект ответа DRF, который необходимо вернуть в конце.

Ответ 18+ 🔞

А, ну вот, смотри, смотри! Опять этот твой ModelViewSet, который как будто бы всё умеет, а на деле — нихуя не умеет, если надо что-то своё, нестандартное. Как будто тебе дали машину, а руль прикручен наглухо, только прямо ехать. А если надо в соседний переулок? Вот для этого и нужен метод retrieve.

Зачем это, спрашиваешь? Да похуй, причины могут быть какие угодно! Например, ты хочешь, чтобы при каждом просмотре статьи счётчик подскакивал. Или тебе надо в ответ сунуть какую-нибудь хитрожопую проверку, типа «а ты, сука, автор этой статьи или просто зритель?». Или вообще подгрузить кучу связанных данных, которые в обычный сериализатор не влезли. Короче, когда стандартного поведения — как дохлой кобыле под хвост.

Смотри, как это делается. Берёшь свой вьюсет и просто пишешь свой метод. Главное — не сломать то, что уже работает.

from rest_framework import viewsets, status
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def retrieve(self, request, *args, **kwargs):
        # 1. Получаем объект стандартным способом
        instance = self.get_object()

        # 2. Увеличиваем счетчик просмотров (кастомная логика)
        instance.views_count += 1
        instance.save()

        # 3. Сериализуем данные
        serializer = self.get_serializer(instance)
        data = serializer.data

        # 4. Добавляем в ответ дополнительную информацию
        data['is_author'] = instance.author == request.user

        # 5. Возвращаем кастомизированный Response
        return Response(data, status=status.HTTP_200_OK)

Вот, видишь? Всё просто, как три копейки. Сначала вытаскиваем объект через self.get_object() — это чтобы не изобретать велосипед и все проверки пермишенов остались. Потом делаем свою магию — в нашем случае накручиваем счётчик. Потом сериализуем, как обычно. А потом — о, смотри, самое интересное! — лепим в ответ какое-нибудь своё поле, например, is_author. И вуаля, в рот меня чих-пых, у тебя кастомный ответ!

Главное — не забудь в конце вернуть Response. А то настолько увлечёшься своей логикой, что забудешь, зачем вообще всё это затеял. И будет тебе вместо статьи — тишина, блядь, и статус 500.