如何在Django REST mixins中使用嵌套序列化器实现多模型CRUD
问题说明
- 目标是在Django REST Framework中实现多模型关联CRUD,单次API请求可同步完成Student、Course、Homework三张关联表的增删改查操作
- 当前故障:接口无法正确返回Course、Homework关联模型的字段数据,API交互界面也无法展示关联模型字段
- 问题根源集中在三类:模型关联定义冗余冲突、序列化器查询逻辑和语法错误、视图方法传参错误
现有代码错误点
- 模型层问题
- Course、Homework模型同时定义了指向Student的外键和多对多字段,关联关系重复冗余,反向查询逻辑混乱
- 模型配置的多对多反向访问名
related_name和序列化器里写的obj.courses_set.all()、obj.homework_set.all()不匹配,根本查不到关联数据
- 序列化器问题
- HomeworkSerializer中
fields = __all__漏写引号,会直接触发语法错误 - 用
SerializerMethodField定义的关联字段默认只读,完全不支持写入、更新操作,没法实现关联表的新增/修改 - All_Serializer中定义了
students字段但Meta类的fields列表未包含该字段,属于无效代码
- HomeworkSerializer中
- 视图层问题
- put方法调用update时手动传
student_id=None,会覆盖路由传入的真实学生ID,还传入了未定义的homeworks、courses变量,请求直接报错 - 查询集未预加载关联数据,会产生N+1查询问题,接口性能差
- put方法调用update时手动传
修正后实现代码
修正后的models.py
删除冗余的外键字段,统一关联关系配置:
import uuid from django.db import models class Student(models.Model): student_id = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False) firstName = models.CharField(max_length=20) age = models.IntegerField(default=18) class Course(models.Model): courseName = models.CharField(max_length=20) courseYear = models.IntegerField(default=2021) students = models.ManyToManyField(Student, related_name='courses') class Homework(models.Model): hwName = models.CharField(max_length=20) hwPossScore = models.IntegerField(default=100) course = models.ForeignKey(Course, related_name='homeworks', on_delete=models.CASCADE, null=True, blank=True) students = models.ManyToManyField(Student, related_name='homeworks')
修正后的serializers.py
用可写嵌套序列化器替代只读的SerializerMethodField,实现关联数据的同步写入:
from rest_framework import serializers from .models import Student, Course, Homework class HomeworkSerializer(serializers.ModelSerializer): class Meta: model = Homework fields = "__all__" class CourseSerializer(serializers.ModelSerializer): homeworks = HomeworkSerializer(many=True, required=False) class Meta: model = Course fields = "__all__" class StudentSerializer(serializers.ModelSerializer): class Meta: model = Student fields = "__all__" class All_Serializer(serializers.ModelSerializer): courses = CourseSerializer(many=True, required=False) homeworks = HomeworkSerializer(many=True, required=False) def create(self, validated_data): courses_data = validated_data.pop('courses', []) homeworks_data = validated_data.pop('homeworks', []) student = Student.objects.create(**validated_data) # 创建关联课程及课程下作业 for course_data in courses_data: course_homeworks = course_data.pop('homeworks', []) course = Course.objects.create(**course_data) course.students.add(student) for hw_data in course_homeworks: homework = Homework.objects.create(course=course, **hw_data) homework.students.add(student) # 创建直接关联学生的作业 for hw_data in homeworks_data: homework = Homework.objects.create(**hw_data) homework.students.add(student) return student def update(self, instance, validated_data): # 更新学生基础字段 instance.firstName = validated_data.get('firstName', instance.firstName) instance.age = validated_data.get('age', instance.age) instance.save() # 更新关联课程(示例为全量替换逻辑,可按需改为增量更新) if 'courses' in validated_data: instance.courses.clear() for course_data in validated_data.pop('courses'): course_homeworks = course_data.pop('homeworks', []) course = Course.objects.create(**course_data) course.students.add(instance) for hw_data in course_homeworks: Homework.objects.create(course=course, **hw_data) # 更新关联作业(示例为全量替换逻辑,可按需改为增量更新) if 'homeworks' in validated_data: instance.homeworks.clear() for hw_data in validated_data.pop('homeworks'): homework = Homework.objects.create(**hw_data) homework.students.add(instance) return instance class Meta: model = Student fields = ('student_id','firstName','age','homeworks','courses')
修正后的views.py
修复传参错误,添加关联数据预加载:
from rest_framework import generics, mixins from .models import Student from .serializers import All_Serializer class Main_mixins(generics.GenericAPIView, mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin): serializer_class = All_Serializer queryset = Student.objects.prefetch_related('courses__homeworks', 'homeworks').all() lookup_field = 'student_id' def get(self, request, student_id=None): if student_id: return self.retrieve(request, student_id) return self.list(request) def post(self, request): return self.create(request) def put(self, request, student_id=None): return self.update(request, student_id) def delete(self, request, student_id=None): return self.destroy(request, student_id)
注意:上述代码中关联数据的更新为全量替换逻辑,如果业务需要支持关联数据的增量修改、编辑已有课程/作业而非每次新建,可在create/update方法中增加主键判断,查询到已有记录后执行更新操作即可。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

