Django Rest Framework嵌套序列化器创建更新产品时415错误如何解决
问题根因
你碰到的415错误完全是请求格式不对导致的:
- JSON(application/json)本身不支持传输二进制文件,你在JSON里塞File字段本身就不符合规范,DRF自然不认这种请求
- 带文件上传的请求必须用
multipart/form-data格式提交,还要确保你的视图类配置了对应的解析器:
from rest_framework.parsers import MultiPartParser, FormParser class ProductViewSet(viewsets.ModelViewSet): parser_classes = (MultiPartParser, FormParser) # 其余配置不变
下一步修改步骤
1. 调整请求参数格式
multipart/form-data不支持直接传嵌套数组,有两种兼容方案:
方案一(推荐,前后端对接更简单):把数组字段按索引展开提交:
# 普通字段直接传 name: first product category: tops brand: first ...其余普通字段 image_1: [文件对象] image_2: [文件对象] # 变体按索引传 variants[0].price: 23000 variants[0].quantity:4 variants[0].image: [第一个变体的文件对象] variants[0].size: m ...第一个变体其余字段 variants[1].price:44 variants[1].quantity:44 ...以此类推
方案二:把variants的非文件字段序列化成JSON字符串传,文件单独传,后端自己解析重组,相对麻烦一点。
2. 修复序列化器的现有bug
你现在的序列化器有几个明显的错误会导致后续运行失败:
- 所有
validate_方法都没有返回校验后的值,DRF校验完会把返回值放到validated_data里,你没return的话对应字段会丢失:
def validate_category(self, value): if not Category.objects.filter(slug=value).exists(): raise serializers.ValidationError({'category': 'category does not exist!'}) return value # 必须加 def validate_store(self, value): request = self.context.get('request') if not Store.objects.filter(id=value, user=request.user).exists(): raise serializers.ValidationError({'store': 'Store does not exist!'}) return value # 必须加
- category是ForeignKey不是多对多字段,不能用
product.category.set(category),直接赋值就行:
# 把这两行 category = get_object_or_404(Category, slug=category) product.category.set(category) # 改成 product.category = get_object_or_404(Category, slug=category)
- 上传image_2的时候你误用了image_1的文件,导致两张图一模一样:
# 把这行 image_2_data = cloudinary.uploader.upload(image_1, ...) # 改成 image_2_data = cloudinary.uploader.upload(image_2, ...)
- 创建变体的时候没必要重复调用VariantSerializer校验,已经在父序列化器校验过了,直接创建对象就行:
for variant in variants: image = variant.pop("image") new_variant = Variant.objects.create(product=product, **variant) # 后面的上传逻辑不变
3. 新增更新逻辑
要实现更新时同时修改关联变体,重写序列化器的update方法即可:
def update(self, instance, validated_data): variants = validated_data.pop('variants', None) # 先更新Product本身的字段 for attr, value in validated_data.items(): # 如果是图片文件就走上传逻辑,这里可以抽成公共方法复用create里的上传代码 if attr in ['image_1', 'image_2']: # 调用你写的cloudinary上传逻辑,返回url赋值给instance对应字段 pass else: setattr(instance, attr, value) instance.save() if variants is not None: # 软删除不在请求里的旧变体 existing_variant_ids = [v['id'] for v in variants if 'id' in v] instance.variants.exclude(id__in=existing_variant_ids).update(is_deleted=True) for variant_data in variants: # 传了id的更新现有变体,没传的新建 if 'id' in variant_data: variant = instance.variants.get(id=variant_data['id']) for attr, value in variant_data.items(): if attr == 'image' and isinstance(value, UploadedFile): # 走图片上传逻辑 pass else: setattr(variant, attr, value) variant.save() else: # 走新建变体的逻辑,和create方法里的逻辑一致 pass return instance
内容的提问来源于stack exchange,提问作者Damiisdandy
相关产品推荐
相关产品推荐

