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 框架中的上下文

扩展 django rest 框架以允许在嵌套序列化程序中继承上下文

python django-rest-framework 3.3.3 更新嵌套序列化程序

django rest框架在序列化程序创建方法中获取请求

Django Rest Framework 序列化器作为表单和嵌套关系

Django REST Framework:嵌套序列化程序未出现