【问题标题】:Django rest framework nested serializer create methodDjango rest框架嵌套序列化程序创建方法
【发布时间】: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."]}}。任何贡献将不胜感激

【问题讨论】:

    标签: python django django-rest-framework


    【解决方案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 刚刚更新了答案以包含一个不需要更改字段名称的方法
        猜你喜欢
        • 2021-06-23
        • 2019-10-13
        • 2019-12-23
        • 2016-05-14
        • 2021-12-15
        • 1970-01-01
        • 2018-12-13
        • 1970-01-01
        • 2015-02-17
        相关资源
        最近更新 更多