Django Rest Framework POST嵌套对象

时间:2016-12-24 10:44:15

标签: python json django rest django-rest-framework

我现在正在使用 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

来自create()的{​​{1}}:

views.py

这是来自 Postman 的回复: Postman response

我在这里看过一些关于这个问题的帖子,但我仍然坚持下去。我试图通过多种方式修复它,但它仍在返回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)

5 个答案:

答案 0 :(得分:37)

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

您的问题涉及DRF中一个复杂的问题区域,因此需要一些解释和讨论来理解序列化程序和视图集的工作原理。

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

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

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

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

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

    覆盖您的视图创建以在自由格式属性上查找相关类。另外,请查看 how DRF implements this with mixins 。您还必须覆盖update方法才能正确处理这些问题,并且如果采用此路线,则除了PATCH(更新)之外还要考虑PUT(部分更新)支持:< / p>

    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:专门用于读取和写入序列化程序并使用主键;这是惯用法:

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

    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 为您的ViewSet。这里我们返回嵌套的Subject和Class数据以进行读取操作,但只使用它们的主键进行更新操作(更简单):

    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_class实现)之上只包含3行手写代码。让框架的逻辑为您找出对象的表示,创建和更新。

    我已经看到了许多其他方法,但到目前为止,这些方法已经为我提供了最少的代码,并以干净的方式利用DRF的设计。

答案 1 :(得分:3)

不做任何额外课程的简单方法是对自己进行序列化:

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

答案 2 :(得分:0)

尝试将嵌套的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对象。

答案 3 :(得分:0)

要解决您的问题,您可以使用此软件包drf-rw-serializers

您需要做的就是使用两个序列化器(一个用于读取,一个用于写入):

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

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

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

    def create(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().create(validated_data)

    def update(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().update(validated_data)

api_views.py

from drf_rw_serializers import generics

from .models import Exam
from .serializers import WriteExamSerializer, ExamSerializer


class ExamListCreateView(generics.ListCreateAPIView):
    queryset = Exam.objects.all()
    write_serializer_class = WriteExamSerializer
    read_serializer_class = ReadExamSerializer

答案 4 :(得分:0)

我认为 SerializerMethodField 简单得多。

它看起来像@validname' 解决方案,但更具可读性。

class BlogSerializer(serializers.ModelSerializer):

    writer = serializers.SerializerMethodField()
    comments = serializers.SerializerMethodField()

    class Meta:
        model = Blog
        fields = '__all__'

    def get_comments(self, obj):
        return CommentSerializer(obj.comments.all(), many=True).data

    def get_writer(self, obj):
        return WriterSerializer(instance=obj.writer).data