Django rest框架嵌套序列化程序创建方法
Posted
技术标签:
【中文标题】Django rest框架嵌套序列化程序创建方法【英文标题】:Django rest framework nested serializer create method 【发布时间】:2020-07-07 20:46:09 【问题描述】:我创建了一个嵌套序列化程序,当我尝试在其中发布数据时,它会继续显示外键值不能为空或字典预期。我已经经历了各种类似的问题并尝试了回复,但它对我不起作用。这是模型
##CLasses
class Classes(models.Model):
class_name = models.CharField(max_length=255)
class_code = models.CharField(max_length=255)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.class_name
class Meta:
ordering = ['class_code']
##Streams
class Stream(models.Model):
stream_name = models.CharField(max_length=255)
classes = models.ForeignKey(Classes,related_name="classes",on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.stream_name
class Meta:
ordering = ['stream_name']
这里是风景
class StreamViewset(viewsets.ModelViewSet):
queryset = Stream.objects.all()
serializer_class = StreamSerializer
这是序列化程序类
class StreamSerializer(serializers.ModelSerializer):
# classesDetails = serializers.SerializerMethodField()
classes = ClassSerializer()
class Meta:
model = Stream
fields = '__all__'
def create(self,validated_data):
classes = Classes.objects.get(id=validated_data["classes"])
return Stream.objects.create(**validated_data, classes=classes)
# def perfom_create(self,serializer):
# serializer.save(classes=self.request.classes)
#depth = 1
# def get_classesDetails(self, obj):
# clas = Classes.objects.get(id=obj.classes)
# classesDetails = ClassSerializer(clas).data
# return classesDetails
我尝试了几种启用 create 方法的方法,但像这样显示错误"classes":"non_field_errors":["Invalid data. Expected a dictionary, but got int."]
。任何贡献将不胜感激
【问题讨论】:
【参考方案1】:你也可以这样解决这个问题,
序列化程序类
# Classes serializer
class ClassesSerializer(ModelSerializer):
class Meta:
model = Classes
fields = '__all__'
# Stream serializer
class StreamSerializer(ModelSerializer):
classes = ClassesSerializer(read_only=True)
class Meta:
model = Stream
fields = '__all__'
查看
# Create Stream view
@api_view(['POST'])
def create_stream(request):
classes_id = request.data['classes'] # or however you are sending the id
serializer = StreamSerializer(data=request.data)
if serializer.is_valid():
classes_instance = get_object_or_404(Classes, id=classes_id)
serializer.save(classes=classes_instance)
else:
return Response(serializer.errors)
return Response(serializer.data)
【讨论】:
【参考方案2】:Kevin Languasco 很好地描述了create
方法的行为,他的解决方案是有效的。我会在解决方案 1 中添加一个变体:
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
classes_id = serializers.IntegerField(write_only=True)
def create(self,validated_data):
return Stream.objects.create(**validated_data, classes=classes)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'classes_id',
'created_date',
)
序列化程序将在不覆盖 create
方法的情况下工作,但如果您想像您的示例中那样,您仍然可以这样做。
在 POST 方法的主体中传递值 classes_id
,而不是 classes
。反序列化数据时,验证将跳过classes
,而是检查classes_id
。
在序列化数据时(例如,当您执行 GET 请求时),classes
将与嵌套字典一起使用,classes_id
将被省略。
【讨论】:
【参考方案3】:这是使用 DRF 开发 API 时非常常见的情况。
问题
在 DRF 到达 create()
方法之前,它会验证输入,我假设它的形式类似于
"classes": 3,
"stream_name": "example"
这意味着,因为它被指定
classes = ClassSerializer()
DRF 正在尝试从整数构建classes
字典。当然这样会失败,你可以从错误字典中看到
"classes":"non_field_errors":["Invalid data. Expected a dictionary, but got int."]
解决方案 1(需要一个新的可写字段 field_name_id
)
一种可能的解决方案是在您的ClassSerializer
中设置read_only=True
,并在写入时使用该字段的替代名称,通常使用field_name_id
。这样,验证将不会完成。详情请见this answer。
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
'classes_id',
)
extra_kwargs =
'classes_id': 'source': 'classes', 'write_only': True,
这是一个干净的解决方案,但需要更改用户 API。如果这不是一个选项,请继续下一个解决方案。
解决方案 2(需要覆盖 to_internal_value
)
我们在这里override the to_internal_value
method。这是嵌套的ClassSerializer
引发错误的地方。为避免这种情况,我们将该字段设置为 read_only
并在方法中管理验证和解析。
请注意,由于我们没有在可写表示中声明 classes
字段,super().to_internal_value
的默认操作是忽略字典中的值。
from rest_framework.exceptions import ValidationError
class StreamSerializer(serializers.ModelSerializer):
classes = ClassSerializer(read_only=True)
def to_internal_value(self, data):
classes_pk = data.get('classes')
internal_data = super().to_internal_value(data)
try:
classes = Classes.objects.get(pk=classes_pk)
except Classes.DoesNotExist:
raise ValidationError(
'classes': ['Invalid classes primary key'],
code='invalid',
)
internal_data['classes'] = classes
return internal_data
class Meta:
model = Stream
fields = (
'pk',
'stream_name',
'classes',
'created_date',
)
通过这个解决方案,你可以使用相同的字段名来读写,但是代码有点乱。
补充说明
您错误地使用了related_name
参数,请参阅this question。反过来,
classes = models.ForeignKey(
Classes,
related_name='streams',
on_delete=models.CASCADE,
)
在这种情况下,它应该是streams
。
【讨论】:
我已经尝试过了,它仍然显示错误"classes_id":["This field is required."]
@Andrew 刚刚更新了答案以包含一个不需要更改字段名称的方法以上是关于Django rest框架嵌套序列化程序创建方法的主要内容,如果未能解决你的问题,请参考以下文章
扩展 django rest 框架以允许在嵌套序列化程序中继承上下文
python django-rest-framework 3.3.3 更新嵌套序列化程序