DRF 嵌套序列化器 #

一、嵌套序列化器概述 #

1.1 什么是嵌套序列化器 #

嵌套序列化器允许在一个序列化器中包含另一个序列化器,用于处理复杂的数据结构和模型关系。

text
简单序列化器
┌─────────────────┐
│   Article       │
│  - id           │
│  - title        │
│  - content      │
└─────────────────┘

嵌套序列化器
┌─────────────────────────────────────┐
│   Article                           │
│  - id                               │
│  - title                            │
│  - content                          │
│  - category: ┌─────────────────┐    │
│              │ Category        │    │
│              │  - id           │    │
│              │  - name         │    │
│              └─────────────────┘    │
│  - tags: ┌─────────────────────┐   │
│          │ [Tag, Tag, ...]     │   │
│          └─────────────────────┘   │
└─────────────────────────────────────┘

1.2 应用场景 #

场景 说明
外键关系 文章所属分类
一对多关系 文章的评论列表
多对多关系 文章的标签
反向关系 用户的所有文章

二、外键关系(ForeignKey) #

2.1 基本外键序列化 #

python
from rest_framework import serializers
from .models import Category, Article

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name']

class ArticleSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'category']

输出:

json
{
    "id": 1,
    "title": "文章标题",
    "content": "文章内容",
    "category": {
        "id": 1,
        "name": "技术"
    }
}

2.2 外键写入 #

python
class ArticleSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(),
        source='category',
        write_only=True
    )

    class Meta:
        model = Article
        fields = ['id', 'title', 'content', 'category', 'category_id']

2.3 SlugRelatedField #

使用指定字段表示外键:

python
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.SlugRelatedField(
        slug_field='name',
        queryset=Category.objects.all()
    )

    class Meta:
        model = Article
        fields = ['id', 'title', 'category']

输出:

json
{
    "id": 1,
    "title": "文章标题",
    "category": "技术"
}

2.4 StringRelatedField #

使用__str__方法表示外键:

python
class ArticleSerializer(serializers.ModelSerializer):
    category = serializers.StringRelatedField()

    class Meta:
        model = Article
        fields = ['id', 'title', 'category']

三、一对多关系 #

3.1 反向关系序列化 #

python
class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ['id', 'content', 'author', 'created_at']

class ArticleSerializer(serializers.ModelSerializer):
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'comments']

输出:

json
{
    "id": 1,
    "title": "文章标题",
    "comments": [
        {
            "id": 1,
            "content": "评论内容",
            "author": "张三",
            "created_at": "2024-01-15T10:00:00Z"
        }
    ]
}

3.2 使用related_name #

在模型中定义related_name:

python
class Comment(models.Model):
    article = models.ForeignKey(
        Article,
        on_delete=models.CASCADE,
        related_name='comments'
    )
    content = models.TextField()

序列化器:

python
class ArticleSerializer(serializers.ModelSerializer):
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

3.3 嵌套创建评论 #

python
class ArticleSerializer(serializers.ModelSerializer):
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

    def create(self, validated_data):
        comments_data = validated_data.pop('comments', [])
        article = Article.objects.create(**validated_data)
        for comment_data in comments_data:
            Comment.objects.create(article=article, **comment_data)
        return article

四、多对多关系 #

4.1 基本多对多序列化 #

python
class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ['id', 'name']

class ArticleSerializer(serializers.ModelSerializer):
    tags = TagSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = ['id', 'title', 'tags']

4.2 多对多写入 #

python
class ArticleSerializer(serializers.ModelSerializer):
    tags = TagSerializer(many=True, read_only=True)
    tag_ids = serializers.PrimaryKeyRelatedField(
        queryset=Tag.objects.all(),
        many=True,
        source='tags',
        write_only=True
    )

    class Meta:
        model = Article
        fields = ['id', 'title', 'tags', 'tag_ids']

    def create(self, validated_data):
        tags = validated_data.pop('tags', [])
        article = Article.objects.create(**validated_data)
        article.tags.set(tags)
        return article

    def update(self, instance, validated_data):
        tags = validated_data.pop('tags', None)
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        if tags is not None:
            instance.tags.set(tags)
        return instance

4.3 SlugRelatedField多对多 #

python
class ArticleSerializer(serializers.ModelSerializer):
    tags = serializers.SlugRelatedField(
        slug_field='name',
        many=True,
        queryset=Tag.objects.all()
    )

    class Meta:
        model = Article
        fields = ['id', 'title', 'tags']

五、深层嵌套 #

5.1 多层嵌套 #

python
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'email']

class CommentSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)

    class Meta:
        model = Comment
        fields = ['id', 'content', 'author']

class ArticleSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

输出:

json
{
    "id": 1,
    "title": "文章标题",
    "author": {
        "id": 1,
        "username": "admin",
        "email": "admin@example.com"
    },
    "comments": [
        {
            "id": 1,
            "content": "评论内容",
            "author": {
                "id": 2,
                "username": "user",
                "email": "user@example.com"
            }
        }
    ]
}

5.2 使用depth参数 #

python
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'
        depth = 2

depth参数自动展开嵌套关系:

  • depth=0:只显示ID
  • depth=1:展开一层
  • depth=2:展开两层

六、嵌套创建和更新 #

6.1 嵌套创建 #

python
class OrderItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrderItem
        fields = ['product', 'quantity', 'price']

class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id', 'customer', 'items', 'total']

    def create(self, validated_data):
        items_data = validated_data.pop('items')
        order = Order.objects.create(**validated_data)
        for item_data in items_data:
            OrderItem.objects.create(order=order, **item_data)
        return order

6.2 嵌套更新 #

python
class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id', 'customer', 'items', 'total']

    def update(self, instance, validated_data):
        items_data = validated_data.pop('items', None)
        
        instance.customer = validated_data.get('customer', instance.customer)
        instance.total = validated_data.get('total', instance.total)
        instance.save()
        
        if items_data is not None:
            instance.items.all().delete()
            for item_data in items_data:
                OrderItem.objects.create(order=instance, **item_data)
        
        return instance

6.3 部分更新嵌套 #

python
class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id', 'customer', 'items', 'total']

    def update(self, instance, validated_data):
        items_data = validated_data.pop('items', None)
        
        for attr, value in validated_data.items():
            setattr(instance, attr, value)
        instance.save()
        
        if items_data is not None:
            existing_ids = set(instance.items.values_list('id', flat=True))
            updated_ids = set()
            
            for item_data in items_data:
                item_id = item_data.get('id')
                if item_id:
                    updated_ids.add(item_id)
                    OrderItem.objects.filter(id=item_id, order=instance).update(**item_data)
                else:
                    OrderItem.objects.create(order=instance, **item_data)
            
            instance.items.exclude(id__in=updated_ids).delete()
        
        return instance

七、动态嵌套序列化 #

7.1 条件嵌套 #

python
class ArticleSerializer(serializers.ModelSerializer):
    comments = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = '__all__'

    def get_comments(self, obj):
        request = self.context.get('request')
        if request and request.query_params.get('include_comments') == 'true':
            return CommentSerializer(obj.comments.all(), many=True).data
        return None

7.2 按需展开 #

python
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'

    def to_representation(self, instance):
        data = super().to_representation(instance)
        request = self.context.get('request')
        
        if request and request.query_params.get('expand') == 'category':
            data['category'] = CategorySerializer(instance.category).data
        
        if request and request.query_params.get('expand') == 'comments':
            data['comments'] = CommentSerializer(
                instance.comments.all(), many=True
            ).data
        
        return data

7.3 可配置嵌套 #

python
class ArticleSerializer(serializers.ModelSerializer):
    def __init__(self, *args, **kwargs):
        expand = kwargs.pop('expand', [])
        super().__init__(*args, **kwargs)
        
        if 'category' not in expand:
            self.fields['category'] = serializers.PrimaryKeyRelatedField(read_only=True)
        if 'comments' not in expand:
            self.fields.pop('comments', None)

    class Meta:
        model = Article
        fields = '__all__'

使用:

python
serializer = ArticleSerializer(article, expand=['category', 'comments'])

八、嵌套验证 #

8.1 嵌套字段验证 #

python
class OrderItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrderItem
        fields = ['product', 'quantity', 'price']

    def validate_quantity(self, value):
        if value <= 0:
            raise serializers.ValidationError('数量必须大于0')
        return value

class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id', 'items']

    def validate_items(self, value):
        if not value:
            raise serializers.ValidationError('订单必须包含至少一个商品')
        return value

8.2 跨字段验证 #

python
class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id', 'items', 'total']

    def validate(self, data):
        items = data.get('items', [])
        total = data.get('total', 0)
        
        calculated_total = sum(item.get('price', 0) * item.get('quantity', 0) for item in items)
        
        if total != calculated_total:
            raise serializers.ValidationError({
                'total': f'订单总价{total}与商品总价{calculated_total}不一致'
            })
        
        return data

九、性能优化 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.prefetch_related('comments', 'tags')
    serializer_class = ArticleSerializer
python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related('category', 'author')
    serializer_class = ArticleSerializer

9.3 组合使用 #

python
class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.select_related(
        'category', 'author'
    ).prefetch_related(
        'comments__author', 'tags'
    )
    serializer_class = ArticleSerializer

9.4 限制嵌套深度 #

python
class ArticleSerializer(serializers.ModelSerializer):
    comments = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = '__all__'

    def get_comments(self, obj):
        comments = obj.comments.all()[:10]
        return CommentSerializer(comments, many=True).data

十、实际应用示例 #

10.1 博客文章完整序列化 #

python
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'avatar']

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name', 'slug']

class TagSerializer(serializers.ModelSerializer):
    class Meta:
        model = Tag
        fields = ['id', 'name']

class CommentSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    replies = serializers.SerializerMethodField()

    class Meta:
        model = Comment
        fields = ['id', 'content', 'author', 'created_at', 'replies']

    def get_replies(self, obj):
        if obj.replies.exists():
            return CommentSerializer(obj.replies.all(), many=True).data
        return []

class ArticleDetailSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)
    category = CategorySerializer(read_only=True)
    tags = TagSerializer(many=True, read_only=True)
    comments = CommentSerializer(many=True, read_only=True)
    comment_count = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = '__all__'

    def get_comment_count(self, obj):
        return obj.comments.count()

10.2 订单系统序列化 #

python
class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['id', 'name', 'price', 'image']

class OrderItemSerializer(serializers.ModelSerializer):
    product = ProductSerializer(read_only=True)
    subtotal = serializers.SerializerMethodField()

    class Meta:
        model = OrderItem
        fields = ['id', 'product', 'quantity', 'price', 'subtotal']

    def get_subtotal(self, obj):
        return obj.quantity * obj.price

class OrderSerializer(serializers.ModelSerializer):
    items = OrderItemSerializer(many=True, read_only=True)
    status_display = serializers.CharField(source='get_status_display', read_only=True)

    class Meta:
        model = Order
        fields = ['id', 'order_no', 'items', 'total', 'status', 'status_display', 'created_at']

十一、总结 #

本章学习了嵌套序列化器的使用:

  • 外键关系:处理ForeignKey关系
  • 一对多关系:处理反向关系
  • 多对多关系:处理ManyToMany关系
  • 深层嵌套:多层嵌套序列化
  • 嵌套创建更新:实现嵌套数据的CRUD
  • 性能优化:减少数据库查询

嵌套序列化器是处理复杂数据结构的关键,让我们继续学习自定义序列化!

最后更新:2026-03-28