Django ORM多对多关系实战指南
一、Django 多对多关系的原理
在关系型数据库中,多对多关系通常需要 第三张中间表 来维护两张表之间的对应关系。
在 Django 中,你只需要定义 ManyToManyField
,Django 会自动帮你创建这张中间表。
特点:
- 可以双向查询(正向 + 反向)
- 支持添加、删除、清空操作(
add()
、remove()
、clear()
) - 可以通过
through
参数自定义中间表,添加额外字段(例如时间戳、角色等)
二、实战案例:学生和课程系统
假设我们有一个 学生(Student) 和 课程(Course) 的关系:
- 一个学生可以选多门课程
- 一门课程可以被多个学生选
1. 定义模型
# models.py
from django.db import modelsclass Course(models.Model):name = models.CharField(max_length=100) # 课程名称teacher = models.CharField(max_length=100) # 授课老师def __str__(self):return self.nameclass Student(models.Model):name = models.CharField(max_length=100) # 学生姓名age = models.IntegerField()# 多对多关系courses = models.ManyToManyField(Course, related_name="students")def __str__(self):return self.name
执行 python manage.py makemigrations && python manage.py migrate
后,Django 会自动生成一张中间表:
appname_student_courses # student_id, course_id
2. 数据操作示例
创建数据
# 创建课程
math = Course.objects.create(name="数学", teacher="张老师")
english = Course.objects.create(name="英语", teacher="李老师")# 创建学生
s1 = Student.objects.create(name="小明", age=18)
s2 = Student.objects.create(name="小红", age=19)# 关联课程
s1.courses.add(math, english) # 小明选了数学、英语
s2.courses.add(math) # 小红只选了数学
查询操作
# 查询小明选了哪些课程
student = Student.objects.get(name="小明")
student.courses.all() # <QuerySet [<Course: 数学>, <Course: 英语>]># 查询选了数学的学生
math = Course.objects.get(name="数学")
math.students.all() # <QuerySet [<Student: 小明>, <Student: 小红>]>
删除和清空
# 小明退选英语
student.courses.remove(english)# 小红退选所有课程
s2.courses.clear()
3. 自定义中间表(through)
如果我们希望记录 学生选课的时间,就需要手动定义中间表:
class StudentCourse(models.Model):student = models.ForeignKey(Student, on_delete=models.CASCADE)course = models.ForeignKey(Course, on_delete=models.CASCADE)selected_at = models.DateTimeField(auto_now_add=True) # 选课时间class Meta:unique_together = ("student", "course") # 防止重复选课class Student(models.Model):name = models.CharField(max_length=100)age = models.IntegerField()courses = models.ManyToManyField(Course, through="StudentCourse", related_name="students")
使用时:
# 小明选数学,并记录时间
StudentCourse.objects.create(student=s1, course=math)# 查询小明所有选课记录(带时间)
StudentCourse.objects.filter(student=s1)
三、总结
ManyToManyField
简化了多对多关系的操作,不需要手动建中间表。- 可以使用
add()
、remove()
、clear()
来维护关系。 - 如果需要额外字段,可以通过
through
自定义中间表。