Django rest framework嵌套序列化器的创建方法

4
我创建了一个嵌套序列化器,当我尝试在其中发布数据时,它会一直显示无法为外键值设置空值或者期望字典。 我查看了各种类似的问题并尝试了回答,但对我不起作用。以下是模型:
##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":["数据无效。预期是字典,但却得到了整数。"]}}。非常感谢您的贡献。

3个回答

9

在使用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

在这里,我们覆盖了to_internal_value方法。这就是嵌套的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
@Andrew 刚刚更新了答案,包括一种不需要更改字段名称的方法。 - Kevin Languasco

1

Kevin Languasco很好地描述了create方法的行为,他的解决方案也是有效的。我想在第一种解决方案中添加一个变化:

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将被省略。

0

你也可以以这种方式解决这个问题,

序列化器类

# 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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接