← Назад к вопросам

Как кастомизировать вывод данных в ModelViewSet?

2.0 Middle🔥 161 комментариев
#Django#REST API и HTTP

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кастомизация вывода данных в ModelViewSet

Django REST Framework предоставляет несколько мощных инструментов для контроля того, какие данные возвращаются API. ModelViewSet позволяет кастомизировать сериализацию в зависимости от операции и контекста.

1. Использование различных сериализаторов для операций

Основной способ — определить разные сериализаторы для разных операций:

from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Article
from .serializers import ArticleListSerializer, ArticleDetailSerializer, ArticleCreateSerializer

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    
    def get_serializer_class(self):
        """Выбираем сериализатор в зависимости от операции."""
        if self.action == 'list':
            return ArticleListSerializer  # Список - минимум данных
        elif self.action == 'retrieve':
            return ArticleDetailSerializer  # Детали - полная информация
        elif self.action in ['create', 'update', 'partial_update']:
            return ArticleCreateSerializer  # Создание - с валидацией
        return super().get_serializer_class()

2. Фильтрация полей в сериализаторе

Можно динамически исключать поля из вывода:

from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source='author.name', read_only=True)
    
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'author_name', 'created_at']

class ArticleListSerializer(ArticleSerializer):
    class Meta:
        model = Article
        fields = ['id', 'title', 'author_name']  # Только основное для списка

class ArticleDetailSerializer(ArticleSerializer):
    comments = serializers.SerializerMethodField()
    
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'author_name', 'created_at', 'comments']
    
    def get_comments(self, obj):
        comments = obj.comments.all()
        return CommentSerializer(comments, many=True).data

3. Использование SerializerMethodField

Добавляйте вычисляемые поля с собственной логикой:

class UserSerializer(serializers.ModelSerializer):
    full_name = serializers.SerializerMethodField()
    post_count = serializers.SerializerMethodField()
    is_active_recently = serializers.SerializerMethodField()
    
    class Meta:
        model = User
        fields = ['id', 'username', 'full_name', 'post_count', 'is_active_recently']
    
    def get_full_name(self, obj):
        return f"{obj.first_name} {obj.last_name}"
    
    def get_post_count(self, obj):
        return obj.posts.count()
    
    def get_is_active_recently(self, obj):
        from datetime import timedelta
        from django.utils import timezone
        cutoff = timezone.now() - timedelta(days=7)
        return obj.last_login > cutoff if obj.last_login else False

4. Контекстуальные данные при сериализации

Отправляйте дополнительный контекст из ViewSet в сериализатор:

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    def get_serializer_context(self):
        """Добавляем дополнительный контекст."""
        context = super().get_serializer_context()
        context['include_comments'] = self.request.query_params.get('include_comments', False)
        context['user'] = self.request.user
        return context

class ArticleSerializer(serializers.ModelSerializer):
    comments = serializers.SerializerMethodField()
    
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'comments']
    
    def get_comments(self, obj):
        context = self.context
        if not context.get('include_comments'):
            return None
        
        comments = obj.comments.all()
        return CommentSerializer(comments, many=True, context=context).data

5. Использование query_params для фильтрации

Позволяйте клиентам контролировать, какие поля выводить:

class DynamicFieldsSerializer(serializers.ModelSerializer):
    """Сериализатор, который фильтрует поля по запросу."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # Получаем параметр fields из запроса (?fields=id,title,author)
        request = self.context.get('request')
        if request:
            fields = request.query_params.get('fields')
            if fields:
                allowed = set(fields.split(','))
                existing = set(self.fields.keys())
                for field_name in existing - allowed:
                    self.fields.pop(field_name)

class ArticleSerializer(DynamicFieldsSerializer):
    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at']

Использование: /api/articles/?fields=id,title,author

6. Использование to_representation для трансформации

Полная контроль над выводом данных:

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['id', 'name', 'price', 'stock']
    
    def to_representation(self, instance):
        """Преобразуем данные перед выводом."""
        data = super().to_representation(instance)
        
        # Форматируем цену
        data['price'] = f"${float(data['price']):.2f}"
        
        # Добавляем статус стока
        stock = data['stock']
        data['availability'] = 'In Stock' if stock > 0 else 'Out of Stock'
        
        # Скрываем поле stock от клиента
        data.pop('stock')
        
        return data

7. Пример комплексной кастомизации

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response

class BlogViewSet(viewsets.ModelViewSet):
    queryset = Blog.objects.all()
    
    def get_serializer_class(self):
        """Разные сериализаторы для разных действий."""
        if self.action == 'list':
            return BlogListSerializer
        elif self.action == 'retrieve':
            return BlogDetailSerializer
        elif self.action == 'create':
            return BlogCreateSerializer
        return BlogSerializer
    
    def get_queryset(self):
        """Оптимизируем queryset в зависимости от операции."""
        queryset = Blog.objects.all()
        
        if self.action == 'list':
            # Для списка выбираем только нужные поля
            queryset = queryset.values('id', 'title', 'author__name')
        elif self.action == 'retrieve':
            # Для деталей добавляем связанные объекты
            queryset = queryset.prefetch_related('comments', 'tags')
        
        return queryset
    
    @action(detail=True, methods=['get'])
    def summary(self, request, pk=None):
        """Кастомный action с минимальным выводом."""
        blog = self.get_object()
        data = {
            'id': blog.id,
            'title': blog.title,
            'word_count': len(blog.content.split()),
            'comment_count': blog.comments.count()
        }
        return Response(data)

8. Лучшие практики

  • Используйте get_serializer_class() для выбора сериализатора по действию
  • Оптимизируйте queryset с prefetch_related и select_related
  • Исключайте чувствительные данные из списков (пароли, токены)
  • Используйте SerializerMethodField для вычисляемых полей
  • Документируйте доступные параметры фильтрации полей
  • Кэшируйте сложные вычисления когда возможно

Этот подход позволяет эффективно контролировать API вывод в зависимости от требований клиента.