多对多关系类型
多对多外键的定义方式相对复杂,多对多models.ManyToManyField
目标模型类,只可以在1方定义,不可以两方同时定义
出席讲座的学生,与讲座的关系,属于多对多,一个学生可以出席多个讲座,一个讲座会有很多学生出席。这里需新增一个出席学生的表,同时把出席学生表与学生表进行关联
语法格式
class A(models.Model):
...
class B(models.Model):
...
a = models.ManyToManyField(A)
可选参数
它只有一个必填的参数即 to,与其他两个关联词在一样,用来指定与当前的 Model 关联的 Model。
- relate_name 与 ForeignKey 中的相同都用于反向查询。
- db_table 用于指定中间表的名称,如果没有提供,Django 会使用多对多字段的名称和包含这张表的 Model 的名称组合起来构成中间表的名称,当然也会包含 index 前缀。
- through 用于指定中间表,这个参数不需要设置,Django会自动生成隐式的 through Model。由于 Django可以生成自身默认的中间表,该参数可以让用户自己去控制表之间的关联关系或者增加一些额外的信息。
模型创建
如果不指定中间表, Django 会默认在数据库中生成一个中间表,命名规则为 应用名_A表名_B表名
#出席学生
class AttendStudent(models.Model):
lectureStudent = models.ManyToManyField(StudyLecture)
student = models.OneToOneField(Student, on_delete=models.CASCADE) # 关联学生表
# 姓名 唯一 最大长度64
name = models.CharField(max_length=64,unique=True)
# 手机号 长度11 唯一
phone = models.CharField(max_length=11,unique=True)
# 邮箱 -- 邮箱格式xx@yy.zz
email = models.EmailField()
def __str__(self):
return self.name
不指定中间表,若执行命令python manage.py makemigrations
生成迁移文件 python manage.py migrate
命令应用数据库迁移,Django 会默认隐式的创建了 lecture_attendstuend_studylecture
表( 应用名_模型1_模型2
),即维护关联关系的中间表。
这个表有三个字段分别是主键 id
、attendstuend_id
和 studylecture_id
。attendstuend_id
与lecture_attendstuend
表的id
关联、 studylecture_id
与 lecture_studylecture
表的id
关联,并同时为这两个关联 id 创建了外键约束(FORGIEN KEY)
这里不推荐Django默认隐式创建中间表,Django默认隐式创建中间表字段内容可能不足以应付业务功能,可以自己定义一个中间表
#出席学生
class AttendStudent(models.Model):
lectureStudent = models.ManyToManyField(StudyLecture,through='LectureAttendInfo') # 如果定义了中间表需要手动指定
student = models.OneToOneField(Student, on_delete=models.CASCADE)
# 姓名 唯一 最大长度64
name = models.CharField(max_length=64,unique=True)
# 手机号 长度11 唯一
phone = models.CharField(max_length=11,unique=True)
# 邮箱 -- 邮箱格式xx@yy.zz
email = models.EmailField()
def __str__(self):
return self.name
#定义一个中间表
class LectureAttendInfo(models.Model):
# 通过外键关联对应的数据
# 当关联的讲座或者出席学生任意一个被删除,这条对应 关系也就不存在了
lecture = models.ForeignKey(StudyLecture,on_delete=models.CASCADE)
student = models.ForeignKey(AttendStudent,on_delete=models.CASCADE)
# 加入时间 -- 自动获取创建数据的时间
join_time = models.DateTimeField(auto_now_add=True) # auto_now_add 创建的适合自动获取当前时间
# 签到状态
is_sgin = models.BooleanField(default=False)
#修改下表名
class Meta: #元类,用于设置模型元信息:表名...
db_table = 'lecture_attend_info'
执行命令python manage.py makemigrations
生成迁移文件, python manage.py migrate
命令应用数据库迁移
如果需要,可通过python manage.py sqlmigrate 迁移文件名
命令查看 Django 执行 sql 语句:python manage.py sqlmigrate lecture 0004_auto_20221003_1902
,了解创建中间表Django对数据库的操作
同样可以通过Admin后台的方式去先插入数据
多对多数据查询
正向查询
根据出席学生查询其关联的讲座数据
格式:数据对象.多方属性.all()
。
打开pycharm的Terminal
终端,输入命令python manage.py shell
打开Django自带命令行,输入指令from lecture.models import StudyLecture, AttendStudent
导入 lecture 应用下的 StudyLecture、AttendStudent 模型类。
输入命令 student= AttendStudent.objects.all().first()
查询数据,student
返回单个模型数据对象(一个对象实例):<AttendStudent: 小红>
。
在AttendStudent
模型中,外键名为lectureStudent
,输入命令lecture_list = student.lectureStudent.all()
,lecture_list
返回一个QuerySet对象(一个对象实例列表):<QuerySet [<StudyLecture: 测试讲座>,<StudyLecture: 功能测试讲座>]>
。
student.lectureStudent
会返回一个多对多管理器,用法和模型管理器一样,可以使用filter等相关的方法
反向查询
根据讲座查询其关联的出席学生数据
格式:数据对象.多方属性_set.all()
。
输入命令 lecture = StudyLecture.objects.all().first()
查询数据,lecture
返回单个模型数据对象(一个对象实例):<StudyLecture: 测试讲座>
。
讲座模型StudyLecture
关联的出席学生模型AttendStudent
,其模型对象的外键模型的小写_set
为attendstudent_set
,输入命令student_list = lecture.attendstudent_set.all()
,student_list
返回一个QuerySet对象(一个对象实例列表):<QuerySet [<AttendStudent: 小明>, <AttendStudent: 小红>]>
。
lecture.attendstudent_set
同样返回的是一个多对多管理器,用法和模型管理器一样,可以使用filter等相关的方法
路由、视图与模板
在lecture应用下的urls.py
文件中新增子路由
from django.urls import path
from lecture import views as lecture_view
# 子路由列表
urlpatterns = [
path('AttendStudents/',lecture_view.AttendStudents), #出席学生管理页面
path('AttendStudents/<int:student_id>',lecture_view.AttendStudents_detail),
]
在lecture应用目录下的views.py
文件中新增AttendStudents
和AttendStudents_detail
两个视图函数
# 出席学生
def AttendStudents(request):
# 从数据库获取出席学生数据
AttendStudent_list = AttendStudent.objects.all()
# 返回模板页面展示出席学生数据
return render(request,'AttendStudent.html',{'AttendStudent_list':AttendStudent_list})
# 出席学生详情
def AttendStudents_detail(request,student_id):
# 获取单个出席学生数据
try:
attendStudent = AttendStudent.objects.get(id=student_id)
except:
return render(request,'404.html')
return render(request,'AttendStudent_detail.html',{'attendStudent':attendStudent})
将数据返回给前端模板文件
AttendStudent.html
{% extends "base.html" %}
{% block title%}出席学生管理{% endblock %}
{% block content%}
<ul class="list-group">
{% for AttendStudent in AttendStudent_list %}
<li class="list-group-item text-center">
<a href="/lecture/AttendStudents/{{ AttendStudent.id }}">{{ AttendStudent }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}
AttendStudent_detail.html
{% extends 'base.html' %}
{% block title%}出席学生详情页{% endblock %}
{% block content%}
<div class="panel panel-info">
<div class="panel-heading"> 出席学生详情页 </div>
<div class="panel-body">
<p>名称:{{ attendStudent.name }}</p>
<p>手机号:{{ attendStudent.phone }}</p>
<p>邮箱:{{ attendStudent.email }}</p>
<p>学籍信息:{{ attendStudent.student.college }}{{ attendStudent.student.major }}{{ attendStudent.student.grade }}</p>
<p>身份证号:{{ attendStudent.student.studentinfo.identityCard }}</p>
<p>关联讲座:
{% for lecture in attendStudent.lectureStudent.all %}
<a href="/lecture/lectures/{{ lecture.id }}">{{ lecture }}</a>
{% endfor %}
</p>
<p><a href="/lecture/AttendStudents/" class="btn btn-info">返回出席学生列表</a></p>
</div>
</div>
{% endblock %}
关联讲座这里,就是通过多对多正向查询,通过for循环将学生关联的讲座信息展示到页面上;
学籍信息则是通过一对一查询,通过AttendStudent
模型中OneToOneField
中的student
查询学生表,返回college、major和grade信息
身份证号则是查询到学生表中的信息后,继续通过studentinfo
查询到学生信息表中的内容,返回到前端模板上