在Django REST Framework中实现动态嵌套序列化及答题完整性校验
解决方案:校验测验提交的完整性
首先得修正你当前序列化器的设计问题,否则会导致创建QuizCompletion时出现逻辑错误:
- 现在的
QuizSerializer是嵌套序列化,提交数据时如果传完整的Quiz信息,DRF会尝试新建一个Quiz,而不是关联已有的测验,这不符合需求 QuizAnswerSerializer里用QuizQuestionSerializer同理,应该用问题ID来关联已有问题,而非传递问题文本
第一步:修正序列化器
from rest_framework import serializers from .models import Quiz, QuizQuestion, QuizCompletion, QuizAnswer class QuizAnswerSerializer(serializers.ModelSerializer): # 接收quizquestion的ID,输出时可按需嵌套序列化 quizquestion = serializers.PrimaryKeyRelatedField(queryset=QuizQuestion.objects.all()) class Meta: model = QuizAnswer fields = ["quizquestion", "answer"] class QuizCompletionSerializer(serializers.ModelSerializer): answers = QuizAnswerSerializer(many=True) # 关联已有Quiz,仅需传入ID quiz = serializers.PrimaryKeyRelatedField(queryset=Quiz.objects.all()) class Meta: model = QuizCompletion fields = ["completed_by", "quiz", "answers"]
第二步:自定义validate方法实现完整性校验
你的方案a(在QuizCompletionSerializer中自定义validate()方法)是正确选择——这个校验需要同时用到quiz和answers两个字段的数据,属于跨字段校验,方案b的自定义字段校验器无法获取其他字段的值,不适用此场景。
在序列化器中添加validate方法:
def validate(self, data): quiz = data.get('quiz') submitted_answers = data.get('answers') # 获取当前测验的所有问题ID集合 quiz_question_ids = set(quiz.questions.values_list('id', flat=True)) # 获取提交答案对应的问题ID集合 submitted_question_ids = set(answer['quizquestion'].id for answer in submitted_answers) # 校验1:提交的问题是否都属于当前测验 invalid_question_ids = submitted_question_ids - quiz_question_ids if invalid_question_ids: raise serializers.ValidationError(f"问题ID {invalid_question_ids} 不属于当前测验") # 校验2:是否提交了所有问题的答案 missing_question_ids = quiz_question_ids - submitted_question_ids if missing_question_ids: raise serializers.ValidationError(f"缺少问题ID {missing_question_ids} 的答案") # 校验3:是否重复提交同一个问题的答案 if len(submitted_question_ids) != len(submitted_answers): raise serializers.ValidationError("存在重复提交的问题答案") return data
第三步:重写create方法处理嵌套创建
因为QuizAnswer依赖QuizCompletion,需要先创建QuizCompletion实例,再批量创建关联的答案,所以要重写序列化器的create方法:
def create(self, validated_data): answers_data = validated_data.pop('answers') # 创建QuizCompletion主实例 quiz_completion = QuizCompletion.objects.create(**validated_data) # 批量创建关联的QuizAnswer for answer_data in answers_data: QuizAnswer.objects.create(quizcompletion=quiz_completion, **answer_data) return quiz_completion
方案选择说明
DRF的自定义字段校验器仅能针对单个字段内容校验,无法访问序列化器中其他字段的数据(比如这里需要拿到quiz的问题列表做对比)。而validate()方法可以获取所有已校验的字段数据,正好适配这种跨字段关联的校验场景,所以方案a是最优解。
内容的提问来源于stack exchange,提问作者bodger
相关产品推荐
相关产品推荐

