下载工作台
Django Web 开发

DRF Serializer 进阶与嵌套校验

试读上半部分 · 解锁后可读全文

第 13 章 · DRF Serializer 进阶与嵌套校验

本章目标:在 ch07 基础 ModelSerializer 之上,掌握 嵌套序列化writable nestedSerializerMethodField跨字段校验;实现列表/详情不同 Serializer;使用 ListSerializer 批量创建与 partial update;与 Flask ch13 Marshmallow 对照,在 shop-demo 商品 API 上完成进阶重构。

学时建议:4~5 小时(含 2 小时嵌套与批量实操)

前置:本模块 ch01~ch12;重点复习 ch05 Product/Category 模型、ch07 ProductListSerializerProductViewSet.get_serializer_class()


13.1 从 ch07 基础到进阶

ch07 已用 ModelSerializer 完成商品列表与写入。接口增多后出现新需求:

需求ch07 做法ch13 进阶
详情含分类对象category_name = CharField(source=...)嵌套 CategorySerializer
创建时同时建分类前端两次请求writable nested
列表要缩略图 URL视图里拼字符串SerializerMethodField
价格与库存联动分散在视图validate() 跨字段校验
批量导入 SKU循环 create自定义 ListSerializer
PATCH 只改部分字段partial_update 已支持明确 partial=True 语义
Request JSON
    → Serializer(字段级 + 对象级校验)
    → validated_data
    → Model.save() / bulk_create
    → Serializer(嵌套/MethodField 输出)
    → Response JSON
教学项目统一使用 shop-demoshopdemoapi.example.comuser-demo,不引用任何企业内部仓库或私有路径。

13.2 ModelSerializer 进阶配置

模型沿用 ch05 Product / Category(含 coverdescription、外键 category)。catalog/serializers.py 进阶片段:

from decimal import Decimal
from rest_framework import serializers
from .models import Product, Category


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


class CategoryWriteSerializer(serializers.ModelSerializer):
    """创建商品时内联新建分类(writable nested 简版)"""

    class Meta:
        model = Category
        fields = ["name", "slug"]

    def validate_slug(self, value):
        if Category.objects.filter(slug=value).exists():
            raise serializers.ValidationError("分类 slug 已存在")
        return value
Meta 选项作用
fields / exclude白名单 / 黑名单(推荐白名单)
read_only_fields输出只读,创建时忽略
extra_kwargs单字段 required/validators 覆盖
validators模型级唯一等校验
class ProductWriteSerializer(serializers.ModelSerializer):
    category = CategoryWriteSerializer(required=False)  # 嵌套写入
    category_id = serializers.PrimaryKeyRelatedField(
        queryset=Category.objects.all(), source="category", write_only=True, required=False
    )

    class Meta:
        model = Product
        fields = [
            "name", "slug", "price", "stock", "is_published",
            "description", "category", "category_id",
        ]

    def validate_price(self, value):
        if value <= Decimal("0"):
            raise serializers.ValidationError("价格必须大于 0")
        return value

    def validate(self, attrs):
        """跨字段:上架时库存必须 > 0"""
        is_pub = attrs.get("is_published", getattr(self.instance, "is_published", False))
        stock = attrs.get("stock", getattr(self.instance, "stock", 0))
        if is_pub and stock <= 0:
            raise serializers.ValidationError({"stock": "上架商品库存必须大于 0"})
        return attrs

    def create(self, validated_data):
        category_data = validated_data.pop("category", None)
        if category_data and "category" not in validated_data:
            category, _ = Category.objects.get_or_create(
                slug=category_data["slug"],
                defaults={"name": category_data["name"]},
            )
            validated_data["category"] = category
        return super().create(validated_data)

13.3 SerializerMethodField 与嵌套只读

class ProductListSerializer(serializers.ModelSerializer):
    category = CategorySerializer(read_only=True)
    cover_thumb = serializers.SerializerMethodField()

    class Meta:
        model = Product
        fields = [
            "id", "name", "slug", "price", "stock",
            "is_published", "category", "cover_thumb", "created_at",
        ]

    def get_cover_thumb(self, obj):
        if not obj.cover:
            return None
        request = self.context.get("request")
        url = obj.cover.url
        return request.build_absolute_uri(url) if request else url


class ProductDetailSerializer(ProductListSerializer):
    description = serializers.CharField()
    related_count = serializers.SerializerMethodField()

    class Meta(ProductListSerializer.Meta):
        fields = ProductListSerializer.Meta.fields + ["description", "related_count"]

    def get_related_count(self, obj):
        return obj.category.products.filter(is_published=True).exclude(pk=obj.pk).count()

SerializerMethodField 只读,方法名 get_<field_name>context["request"] 用于拼完整封面 URL(ViewSet 默认已注入)。


13.4 列表/详情不同 Serializer

# catalog/views.py — ch07 已介绍,本章强化字段划分
class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.select_related("category").all()

    def get_serializer_class(self):
        if self.action == "list":
            return ProductListSerializer
        if self.action == "retrieve":
            return ProductDetailSerializer
        if self.action in ("create", "update", "partial_update"):
            return ProductWriteSerializer
        return ProductDetailSerializer
actionSerializer说明
listProductListSerializer无长描述,含缩略图
retrieveProductDetailSerializerdescription、关联数
create / updateProductWriteSerializer含校验与嵌套写入

13.5 ListSerializer 与批量创建

class ProductBulkCreateSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        objs = [Product(**item) for item in validated_data]
        return Product.objects.bulk_create(objs, batch_size=100)

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。