Django Rest Framework POST嵌套对象

wen*_*isa 20 python django rest json 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
Run Code Online (Sandbox Code Playgroud)

create()来自views.py:

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)
Run Code Online (Sandbox Code Playgroud)

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

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

Ale*_*kli 44

您正在处理嵌套序列化的问题.请在继续之前阅读链接的文档.

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

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

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

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

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

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

class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer(read_only=True)
    clazz = ClassSerializer(read_only=True)
Run Code Online (Sandbox Code Playgroud)

覆盖视图的创建以在自由格式属性上查找相关的类.另外,请查看DRF如何使用mixins实现此功能.您还必须覆盖您的update方法以正确处理这些,并且如果您采用此路线PATCH,则除了PUT(更新)之外还要考虑(部分更新)支持:

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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

class ExamReadSerializer(ExamSerializer):
     subject = SubjectSerializer(read_only=True)
     clazz = ClassSerializer(read_only=True)
Run Code Online (Sandbox Code Playgroud)

然后指定要用于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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,选项2似乎相当简单且容易出错,在DRF(get_serializer_class实现)之上只包含3行手写代码.让框架的逻辑为您找出对象的表示和创建以及更新.

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

  • 2017 年夏天的你好)感谢您的详细解释。您提出的“惯用”解决方案存在一个问题:非 GET 查询后从 API 返回的结果不包含嵌套对象。有没有办法避免它,在同一个 HTTP 查询中使用不同的序列化器进行读取和写入? (3认同)

val*_*ame 13

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

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
Run Code Online (Sandbox Code Playgroud)