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

7 浏览
0 Comments

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

我创建了一个嵌套的序列化程序,当我试图在其中发布数据时,它仍然显示外键值不能为空或需要字典的错误。我已经查阅了各种类似的问题并尝试了不同的响应,但对我来说不起作用。这是模型

##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.\"]}}。任何贡献都将不胜感激

admin 更改状态以发布 2023年5月24日
0
0 Comments

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方法,但是序列化程序可以在不覆盖create方法的情况下工作。

classes_id的值传递到POST方法的主体中,而不是classes。在反序列化数据时,验证将跳过classes并转而检查classes_id

在序列化数据时(例如执行GET请求时),classes将与嵌套的字典一起使用,而classes_id将被省略。

0
0 Comments

当使用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。 这样,就不会进行验证。 关于更多详细信息,请参见此答案

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参数,请参见此问题。 情况正好相反,

classes = models.ForeignKey(
  Classes,
  related_name='streams',
  on_delete=models.CASCADE,
)

在这种情况下,应该是streams

0