64、ORM学员管理系统-----联合查询
老师信息管理
思考
三种方式创建多对多外键方式及其优缺点。
通过外键创建
class Class(models.Model): id = models.AutoField(primary_key=True) # 主键 cname = models.CharField(max_length=32) # 班级名称 first_day = models.DateField() # 开班时间 class Teacher(models.Model): tname = models.CharField(max_length=32) # 自定义第三张表,通过外键关联上面两张表 class Teacher2Class(models.Model): teacher = models.ForeignKey(to="Teacher") the_class = models.ForeignKey(to="Class") class Meta: unique_together = ("teacher", "the_class")
通过ManyToManyField创建
class Class(models.Model): id = models.AutoField(primary_key=True) # 主键 cname = models.CharField(max_length=32) # 班级名称 first_day = models.DateField() # 开班时间 class Teacher(models.Model): tname = models.CharField(max_length=32) # 通过ManyToManyField自动创建第三张表 cid = models.ManyToManyField(to="Class", related_name="teachers")
通过外键和ManyToManyField创建
class Class(models.Model): id = models.AutoField(primary_key=True) # 主键 cname = models.CharField(max_length=32) # 班级名称 first_day = models.DateField() # 开班时间 class Teacher(models.Model): tname = models.CharField(max_length=32) # 通过ManyToManyField和手动创建第三张表 cid = models.ManyToManyField(to="Class", through="Teacher2Class", through_fields=("teacher", "the_class")) class Teacher2Class(models.Model): teacher = models.ForeignKey(to="Teacher") the_class = models.ForeignKey(to="Class") class Meta: unique_together = ("teacher", "the_class")
表结构设计
class Teacher(models.Model): tname = models.CharField(max_length=32) cid = models.ManyToManyField(to="Class", related_name="teachers")
老师信息列表
URL部分
url(r'^teacher_list/$', app01_views.teacher_list, name="teacher_list"),
视图部分
def teacher_list(request): teacher_list = models.Teacher.objects.all() return render(request, "teacher_list.html", {"teacher_list": teacher_list})
前端部分
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>老师信息页面</title> </head> <body> <a href="{% url 'add_teacher' %}">添加新老师</a> <table border="1"> <thead> <tr> <th>#</th> <th>id</th> <th>老师姓名</th> <th>授课班级</th> <th>操作</th> </tr> </thead> <tbody> {% for teacher in teacher_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ teacher.id }}</td> <td>{{ teacher.tname }}</td> <td> {% for class in teacher.cid.all %} {% if forloop.last %} {{ class.cname }} {% else %} {{ class.cname }}, {% endif %} {% endfor %} </td> <td> <a href="{% url 'delete_teacher' teacher.id %}">删除</a> <a href="{% url 'edit_teacher' teacher.id %}">编辑</a> </td> </tr> {% endfor %} </tbody> </table> </body> </html>
删除老师信息
URL部分
url(r'^delete_teacher/(?P<tid>\d+)$', app01_views.delete_teacher, name="delete_teacher"),
视图部分
def delete_teacher(request, tid): models.Teacher.objects.filter(id=tid).delete() return redirect(reverse("teacher_list"))
前端部分
在老师列表页面添加一个删除的链接。
<a href="{% url 'delete_teacher' teacher.id %}">删除</a>
添加老师信息
URL部分
url(r'^add_teacher/$', app01_views.add_teacher, name="add_teacher"),
视图部分
def add_teacher(request): if request.method == "POST": tname = request.POST.get("tname") class_ids = request.POST.getlist("class_id") new_teacher = models.Teacher.objects.create(tname=tname) # 查询出所有被选中的班级信息 class_objs = models.Class.objects.filter(id__in=class_ids) # 将老师的授课班级设置为选中的班级, 以下四种都可以,注意什么时候加* new_teacher.cid.set(class_objs) # new_teacher.cid.add(*class_objs) # new_teacher.cid.add(*class_ids) # new_teacher.cid.set(class_ids) new_teacher.save() return redirect(reverse("teacher_list")) class_list = models.Class.objects.all() return render(request, "add_teacher.html", {"class_list": class_list})
前端部分
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>添加老师</title> </head> <body> <form action="{% url 'add_teacher' %}" method="post"> {% csrf_token %} <p>老师姓名:<input type="text" name="tname"></p> <label for="class_id">授课班级:</label> <select name="class_id" id="class_id" multiple> {% for class in class_list %} <option value="{{ class.id }}">{{ class.cname }}</option> {% endfor %} </select> <p><input type="submit" value="提交"></p> </form> </body> </html>
编辑老师信息
URL部分
url(r'^edit_teacher/(?P<tid>\d+)$', app01_views.edit_teacher, name="edit_teacher"),
视图部分
def edit_teacher(request, tid): teacher_obj = models.Teacher.objects.get(id=tid) class_list = models.Class.objects.all() if request.method == "POST": tname = request.POST.get("tname") class_ids = request.POST.getlist("class_id") # 更新老师相关信息 teacher_obj.tname = tname teacher_obj.cid.set(class_ids) teacher_obj.save() # 一定记得更新完要保存 return redirect(reverse("teacher_list")) return render(request, "edit_teacher.html", {"class_list": class_list, "teacher": teacher_obj})
前端部分
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>编辑老师信息</title> </head> <body> <form action="{% url 'edit_teacher' teacher.id %}" method="post"> {% csrf_token %} <p>老师姓名:<input type="text" name="tname" value="{{ teacher.tname }}"></p> <label for="class_id">授课班级:</label> <select name="class_id" id="class_id" multiple> {% for class in class_list %} {% if class in teacher.cid.all %} <option value="{{ class.id }}" selected>{{ class.cname }}</option> {% else %} <option value="{{ class.id }}">{{ class.cname }}</option> {% endif %} {% endfor %} </select> <p><input type="submit" value="提交"></p> </form> </body> </html>
多对多操作
正向查询(由老师表查询班级表)
>>> teacher_obj = models.Teacher.objects.first() >>> teacher_obj.cid.all() # 查询该老师授课的所有班级 <QuerySet [<Class: Class object>, <Class: Class object>]>
反向查询(由班级表反向查询老师表)
>>> class_obj = models.Class.objects.first() >>> class_obj.teachers.all() # 此处用到的是related_name,如果不设置的话就用默认的表名_set <QuerySet [<Teacher: Teacher object>, <Teacher: Teacher object>, <Teacher: Teacher object>]>
class RelatedManager
"关联管理器"是在一对多或者多对多的关联上下文中使用的管理器。
它存在于下面两种情况:
- 外键关系的反向查询
- 多对多关联关系
简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法。
方法
create()
创建一个新的对象,保存对象,并将它添加到关联对象集之中,返回新创建的对象。
>>> import datetime >>> teacher_obj.cid.create(cname="9班", first_day=datetime.datetime.now())
创建一个新的班级对象,保存对象,并将它添加到关联对象集之中。返回新创建的对象:
>>> class_obj = models.Class.objects.first() >>> class_obj.student_set.create(sname="小明")
多对多如何操作呢?
>>> class_obj = models.Class.objects.first() >>> class_obj.teacher_set.create(tname="老师X")
add()
把指定的model
对象添加到关联对象集中。
添加对象
>>> class_objs = models.Class.objects.filter(id__lt=3) >>> models.Teacher.objects.first().cid.add(*class_objs)
添加id
>>> models.Teacher.objects.first().cid.add(*[1, 2])
set()
更新model对象的关联对象。
>>> teacher_obj = models.Teacher.objects.first() >>> teacher_obj.cid.set([2, 3])
remove()
从关联对象集中移除执行的model对象
>>> teacher_obj = models.Teacher.objects.first() >>> teacher_obj.cid.remove(3)
clear()
从关联对象集中移除一切对象。
>>> teacher_obj = models.Teacher.objects.first() >>> teacher_obj.cid.clear()
注意:
对于ForeignKey对象,clear()和remove()方法仅在null=True时存在。
举个例子:
ForeignKey字段没设置null=True时,
class Student(models.Model): sname = models.CharField(max_length=32) cid = models.ForeignKey(to=Class, to_field="id")
没有clear()和remove()方法:
>>> models.Class.objects.first().student_set.clear() Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'RelatedManager' object has no attribute 'clear'
当ForeignKey字段设置null=True时,
class Student(models.Model): sname = models.CharField(max_length=32) cid = models.ForeignKey(to=Class, to_field="id", null=True)
此时就有clear()和remove()方法:
>>> models.Class.objects.first().student_set.clear()
注意:
- 对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。
了不起的双下划线
在这之前我们所有的跨表查询都是基于对象的查询。
比如:
>>> models.Class.objects.first().student_set.values("sname") <QuerySet [{'sname': '张三'}, {'sname': '李四'}, {'sname': '小明'}]>
Django还提供了一种直观而高效的方式在查询中表示数据表之间的关联关系,它能自动确认 SQL JOIN 关系。
需要做跨关系查询时,就可以使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的 model 为止。
>>> models.Class.objects.filter(id=1).values("student__sname") # 双下划线表示跨表 <QuerySet [{'student__sname': '张三'}, {'student__sname': '李四'}, {'student__sname': '小明'}]>