Django REST Framework嵌套序列化器POST请求问题排查
问题描述
我有一个与Brand模型存在多对一关系的Product模型:
class Product(models.Model): name = models.CharField(max_length=200) description = models.CharField(max_length=500) sku = models.CharField(max_length=50) upc = models.CharField(max_length=12, blank=True) asin = models.CharField(max_length=10, blank=True) brand = models.ForeignKey( Brand, on_delete=models.CASCADE, null=True, ) # buybox = models.FloatField(blank=True) vendors = models.ManyToManyField(Vendor, blank=True) added = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def __str__(self): return self.name + ": " + self.description
Brand模型:
class Brand(models.Model): name = models.CharField(max_length=50) def __str__(self): return self.name
同时使用了嵌套序列化器:
class ProductSerializer(serializers.ModelSerializer): brand = BrandSerializer(required=False, read_only=True) class Meta: model = Product fields = "__all__"
当我发送POST请求添加新产品时持续报错,无法确定正确的JSON请求结构。目前只有发送以下JSON不会触发Axios错误,但添加到数据库的产品brand字段为NULL:
{ "name": "examplename", "description": "example desc", "sku": "SKUSKUSKU", "asin": "ASINASIN12", "price": "11.11", "brand": { "id": 1, "name": "Honeywell" } }
请求发送函数如下:
function addProduct(product) { // axios post request for a new product console.log(product) axios.post("http://127.0.0.1:8000/products/", product) .then((response) => { console.log(response); alert("Success"); reset('productForm') }) .catch((error) => { console.log(error); alert(error); }) }
补充说明:从Django后台手动创建的单个产品的GET请求返回的正确JSON:
{ "id": 9, "brand": { "id": 2, "name": "Beano" }, "vendors": [], "name": "beans", "description": "big ol can o beans", "sku": "beansbeansbeans", "upc": "", "asin": "bB00nvn", "added": "2022-12-28T19:09:30.007480Z", "updated": "2022-12-28T19:09:30.013781Z" }
请问在向存在关联关系的模型发送POST请求时,是应该发送brand的主键ID,还是发送完整的嵌套brand对象?我哪里操作出错了?
解决方案
问题根源
你的ProductSerializer里给brand字段设置了read_only=True,这意味着序列化器只会在**读取(GET)时返回嵌套的Brand数据,但写入(POST/PUT)**时会完全忽略这个字段的输入,所以你传的brand对象根本没被处理,导致数据库里brand字段为NULL。
正确处理方式
分两种场景选择:
仅关联已存在的Brand
- 不需要嵌套序列化器,直接用DRF默认的外键处理逻辑,发送Brand的ID即可。修改序列化器:
class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = "__all__" - 对应的POST请求JSON只需传brand的ID:
{ "name": "examplename", "description": "example desc", "sku": "SKUSKUSKU", "asin": "ASINASIN12", "price": "11.11", "brand": 1 }
- 不需要嵌套序列化器,直接用DRF默认的外键处理逻辑,发送Brand的ID即可。修改序列化器:
需要同时创建新Brand并关联到Product
- 如果需求是允许添加Product时同步创建新Brand,需要给
BrandSerializer移除read_only=True,并在ProductSerializer里重写create方法处理嵌套对象:class BrandSerializer(serializers.ModelSerializer): class Meta: model = Brand fields = "__all__" class ProductSerializer(serializers.ModelSerializer): brand = BrandSerializer(required=False) class Meta: model = Product fields = "__all__" def create(self, validated_data): # 取出嵌套的brand数据 brand_data = validated_data.pop('brand', None) product = Product.objects.create(**validated_data) if brand_data: # 用get_or_create避免重复创建同名Brand brand, created = Brand.objects.get_or_create(**brand_data) product.brand = brand product.save() return product - 此时可以发送完整的Brand对象来创建关联,也可以传已存在Brand的ID+名称(通过
get_or_create自动匹配已有数据)。
- 如果需求是允许添加Product时同步创建新Brand,需要给
总结
- 仅关联已有Brand时,直接传ID最简洁,序列化器无需嵌套。
- 需要同步创建新Brand时,才用嵌套序列化器并重写create方法。
- 你之前的核心错误就是把
brand字段设为read_only,导致写入时该字段被完全忽略。
内容的提问来源于stack exchange,提问作者spaceCabbage
相关产品推荐
相关产品推荐

