Python Django REST Framework POST 嵌套对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41312558/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 00:46:35  来源:igfitidea点击:

Django REST Framework POST nested objects

pythonjsondjangorestdjango-rest-framework

提问by wencakisa

I'm facing a little problem right now with Django Rest Framework. I'm trying to post an object with nested objects in it.

我现在在使用Django Rest Framework 时遇到了一个小问题。我正在尝试发布一个包含嵌套对象的对象。

Here are my serializers.py:

这是我的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

And create()from views.py:

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)

And here it is the response from Postman: Postman response

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

I've read some posts here about this problem but I'm still stuck with it. I've tried to fix it in several ways but it is still returning "This field is required.".

我在这里阅读了一些关于这个问题的帖子,但我仍然坚持下去。我尝试以多种方式修复它,但它仍然返回"This field is required."

回答by Aleksi H?kli

You are dealing with the problem of nested serialization. Please read the linked documentation before proceeding.

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

Your question relates to a complex area of problems in DRF and hence requires some explanation and discussion for understanding how serializers and viewsets work.

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

I will discuss the problem of representing your Subjectand Classdata via the same endpoint by using a different representation of data for different HTTP methods, because this is commonly the problem when people wish to represent their data in nested formats; they wish to provide their user interfaces enough information for clean use, e.g. through the dropdown selectors.

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

By default Django and Django REST Framework (DRF) refer to related objects (your Subjectand Class) by their primary keys. These, by default, are auto-incrementing integer keys with Django. If you want to refer to them by other ways you have to write overrides for this. There are a few different options.

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

  1. First option is to specialize your creation and update logic:Refer to your class via some other attribute(s) and manually write the lookups for creation yourself, or set the key you are referring through as the primary keyof your class. You can set your class' name, UUID or any other attribute as the primary database key, as long as it is a unique, single field(the reason I am mentioning this is because you are, at the moment, looking your Classmodels up with a composite search that consists of a composite (number, letter) search term). You can override related object lookups in your createview method (for POST), for example, but then you will have to handle similar lookups in your updateview method as well (for PUT and PATCH).
  2. Second, in my opinion the preferable option, is to specialize your object representations:Refer to your classes normally via primary key and create one serializer for readingthe object and one for creating and updatingit. This can be easily achieved by serializer class inheritance and overriding your representations. Use the primary key in your POST, PUT, PATCH, etc. requests to update your class references and foreign keys.
  1. 第一个选项是专门化您的创建和更新逻辑:通过一些其他属性引用您的类并自己手动编写用于创建的查找,或者将您引用的键设置为类的主键。您可以将类的名称、UUID 或任何其他属性设置为主数据库键,只要它是唯一的单个字段(我提到这一点的原因是因为您目前正在查找Class模型由复合(数字、字母)搜索词组成的复合搜索)。例如,您可以在create视图方法中覆盖相关的对象查找(对于 POST),但随后您也必须在update视图方法中处理类似的查找(对于 PUT 和 PATCH)。
  2. 其次,在我看来,更好的选择是专门化您的对象表示:通常通过主键引用您的类并创建一个序列化程序来读取对象,另一个序列化程序用于创建和更新它。这可以通过序列化器类继承和覆盖您的表示轻松实现。在 POST、PUT、PATCH 等请求中使用主键来更新类引用和外键。


Option 1: Look Class and Subject up with an arbitrary attribute in create and update:

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

Set your nested class serializers as read-only:

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

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

Override your view's create to look up the related classes on free-form attributes. Also, check out how DRF implements this with mixins. You will also have to override your updatemethod to handle these correctly, and take into account PATCH(partial update) support in addition to PUT(update) if you take this route:

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

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)


Option 2: Specialize your serializers for read and write and use primary keys; This is the idiomatic approach:

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

First define a default ModelSerializer you wish to use for normal operations (POST, PUT, PATCH):

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

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

Then override the necessary fields with the kind of representation you want to give to them for reading the data (GET):

然后使用您想要提供给它们以读取数据 (GET) 的表示类型覆盖必要的字段:

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

Then specify the serializer you wish to use for different operationsfor your ViewSet. Here we return the nested Subject and Class data for read operations, but only use their primary keys for update operations (far simpler):

然后为您的 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

As you can see, option 2 seems fairly less complex and error-prone, containing only 3 lines of hand-written code on top of DRF (the get_serializer_class implementation). Just let the framework's logic figure out the representations and creation and updates of objects for you.

如您所见,选项 2 似乎不那么复杂且容易出错,仅包含 3 行基于 DRF(get_serializer_class 实现)的手写代码。只需让框架的逻辑为您计算出对象的表示、创建和更新。

I have seen many other approaches, but so far these have been the ones that have produced the least code to maintain for me and take advantage of the design of DRF in a clean manner.

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

回答by validname

An easier approach without doing any additional classes is to take serialization on yourself:

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

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

回答by Greg Eremeev

To solve your problem you can use this package drf-rw-serializers

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

All you need to do is use two serializers (one to read and one to write):

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

serializers.py

序列化程序.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

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

回答by Keoni Mahelona

I had the same issue when trying to post a nested JSON object to DRF (Django Rest Framework).

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

Once you've properly set up writing nested serializers (see the docs on writable nested serializers), you can test that it works by using the browsable APIand posting/putting data there. If that works, and you're still getting "This field is required" errors on your nested models when posting/putting JSON objects, you may have to set the content-type of your request.

正确设置编写嵌套序列化程序后(请参阅有关可写嵌套序列化程序的文档),您可以使用可浏览的 API并在其中发布/放置数据来测试它是否有效。如果这样做有效,并且您在发布/放置 JSON 对象时仍然在嵌套模型上收到“此字段是必需的”错误,则您可能需要设置请求的内容类型。

This answerprovided the solution I needed, and it's summarised below.

这个答案提供了我需要的解决方案,总结如下。

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

I needed to set the "contentType" as well as "stringify" my js object.

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