Django REST框架后嵌套对象

2024-03-29 14:40:52 发布

您现在位置:Python中文网/ 问答频道 /正文

我现在面临一个小问题,就是Django Rest Framework。我试图发布一个包含嵌套对象的对象。

这是我的serializers.py

class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('number', 'letter')


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('title',)


class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
        depth = 1

    def create(self, validated_data):
        return Exam.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.__dict__.update(**validated_data)
        instance.save()

        return instance

以及来自views.pycreate()

def create(self, request):
    serializer = self.serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)

    return Response(serializer.validated_data, status=status.HTTP_201_CREATED)

这里是邮递员的回应: Postman response

我在这里读过一些关于这个问题的帖子,但我仍然坚持着。我试过几种方法来修复它,但它仍在返回"This field is required."


Tags: 对象instanceselffieldsdatamodelreturndef
3条回答

我在尝试将嵌套的JSON对象发布到DRF(Django Rest Framework)时遇到了同样的问题。

一旦您正确设置了编写嵌套序列化程序(请参阅writable nested serializers上的文档),就可以使用browsable API并在其中发布/放置数据来测试它是否工作。如果这样做有效,并且您在发布/放置JSON对象时仍然在嵌套模型上得到“此字段是必需的”错误,那么您可能必须设置请求的内容类型。

This answer提供了我需要的解决方案,总结如下。

$.ajax ({
  // Other parameters e.g. url, type
  data: JSON.stringify(data),
  dataType: "json",
  contentType: "application/json; charset=utf-8",
});

我需要设置“contentType”和“stringify”我的js对象。

您正在处理nested serialization的问题。请在继续之前阅读链接的文档。

您的问题涉及DRF中问题的一个复杂领域,因此需要一些解释和讨论,以了解序列化程序和视图集是如何工作的。

我将讨论通过同一个端点通过不同HTTP方法使用不同的数据表示来表示您的SubjectClass数据的问题,因为这通常是人们希望以嵌套格式表示其数据时的问题;他们希望为用户界面提供足够的信息以供干净使用,e、 通过下拉选择。

默认情况下,Django和Django REST Framework(DRF)通过主键引用相关对象(您的SubjectClass)。默认情况下,这些是用Django自动递增整数键。如果要通过其他方式引用它们,则必须为此编写重写。有几个不同的选择。

  1. 第一个选项是专门化您的创建和更新逻辑:通过一些其他属性引用您的类,并自己手动编写创建的查找,或者将您通过引用的键设置为类的primary key。您可以将类的名称、UUID或任何其他属性设置为主数据库键,只要它是唯一的,single field(我之所以提到这一点,是因为您目前正在使用由复合(数字、字母)搜索项组成的复合搜索查找Class模型)。例如,您可以重写视图方法(对于POST)中的相关对象查找,但是您还必须处理视图方法(对于PUT和PATCH)中的类似查找。
  2. 其次,在我看来,最好的选择是专门化对象表示:通常通过主键引用类,创建一个序列化器来读取对象,并创建和更新对象。这可以通过序列化程序类继承和重写表示来轻松实现。使用POST、PUT、PATCH等请求中的主键来更新类引用和外键。

选项1:在“创建和更新”中使用任意属性查找类和主题:

将嵌套类序列化程序设置为只读:

class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer(read_only=True)
    clazz = ClassSerializer(read_only=True)

重写视图的create以查找自由形式属性上的相关类。另外,请查看how DRF implements this with mixins。您还必须重写您的update方法才能正确处理这些问题,如果您采用以下方法,除了PUT(update)之外,还必须考虑PATCH(部分更新)支持:

def create(self, request):
    # Look up objects by arbitrary attributes.
    # You can check here if your students are participating
    # the classes and have taken the subjects they sign up for.
    subject = get_object_or_404(Subject, title=request.data.get('subject'))
    clazz = get_object_or_404(
        Class, 
        number=request.data.get('clazz_number')
        letter=request.data.get('clazz_letter')
    )

    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(clazz=clazz, subject=subject)
    headers = self.get_success_headers(serializer.data)

    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

选项2:专门化读写序列化程序并使用主键;这是惯用的方法:

首先定义希望用于正常操作(POST、PUT、PATCH)的默认ModelSerializer:

class ExamSerializer(serializers.ModelSerializer)
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

然后,使用要为其提供的用于读取数据的表示形式(GET)覆盖必要的字段:

class ExamReadSerializer(ExamSerializer):
     subject = SubjectSerializer(read_only=True)
     clazz = ClassSerializer(read_only=True)

然后为您的视图集specify the serializer you wish to use for different operations。在这里,我们返回用于读取操作的嵌套主题和类数据,但只将它们的主键用于更新操作(简单得多):

class ExamViewSet(viewsets.ModelViewSet):
     queryset = Exam.objects.all()

     def get_serializer_class(self):
         # Define your HTTP method-to-serializer mapping freely.
         # This also works with CoreAPI and Swagger documentation,
         # which produces clean and readable API documentation,
         # so I have chosen to believe this is the way the
         # Django REST Framework author intended things to work:
         if self.request.method in ['GET']:
             # Since the ReadSerializer does nested lookups
             # in multiple tables, only use it when necessary
             return ExamReadSerializer
         return ExamSerializer

如您所见,选项2看起来相当不复杂,也不容易出错,它只在DRF(get_serializer_类实现)之上包含3行手写代码。只需让框架的逻辑为您计算对象的表示、创建和更新。

我见过许多其他的方法,但是到目前为止,这些方法产生了最少的代码来维护我,并且以干净的方式利用DRF的设计。

不需要执行任何其他类的更简单方法是对自己进行序列化:

class ExamSerializer(serializers.ModelSerializer):
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['subject'] = SubjectSerializer(
            Subject.objects.get(pk=data['subject'])).data
        data['clazz'] = ClassSerializer(
            Class.objects.get(pk=data['clazz'])).data
        return data

相关问题 更多 >