我现在正在使用 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
我在这里看过一些关于这个问题的帖子,但我仍然坚持下去。我试图通过多种方式修复它,但它仍在返回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)
。
答案 0 :(得分:37)
您正在处理 nested serialization 的问题。请在继续之前阅读链接的文档。
您的问题涉及DRF中一个复杂的问题区域,因此需要一些解释和讨论来理解序列化程序和视图集的工作原理。
我将讨论通过使用不同HTTP方法的不同数据表示来通过同一端点表示Subject
和Class
数据的问题,因为这通常是人们希望表示的问题他们的数据是嵌套格式;他们希望为他们的用户界面提供足够的信息以便清洁使用,例如通过下拉选择器。
默认情况下,Django和Django REST Framework(DRF)通过主键引用相关对象(您的Subject
和Class
)。默认情况下,这些是使用Django自动递增整数键。如果你想通过其他方式引用它们,你必须为此编写覆盖。有几种不同的选择。
Class
模型具有由复合(数字,字母)搜索词组成的复合搜索。例如,您可以在create
视图方法(对于POST)中覆盖相关对象查找,但是您必须在update
视图方法中处理类似的查找(对于PUT和PATCH)。 选项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
您需要做的就是使用两个序列化器(一个用于读取,一个用于写入):
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)
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