Python入门自学进阶-Web框架——12、Django实践小项目2
前面的例子是单表操作,这里进行多表操作的实验。
一对多的操作,即一张表中有外键,先创建测试表
class Province(models.Model):name = models.CharField(max_length=32)# 默认Django会生成id列作为主键,也可以自己定义一个唯一的主键# nid = models.IntegerField(unique=True)class City(models.Model):name = models.CharField(max_length=32)pro = models.ForeignKey("Province",on_delete=models.DO_NOTHING)# 如果要关联另一个表的自定义的主键,可以使用下面的方式,to_field参数默认就是id# pro = models.ForeignKey("Province",to_field="nid")
创建一对多的关联表,定义的外键需要有on_delete=配置,一般有两种配置,DO_NOTHING、和CASCADE,分别表示在Prvince中删除一条记录时,City中不做任何操作,和City中进行级联删除。
还有就是外键定义的字段,默认是id,可以自己定义,前提是在关联的表中自己定义的字段必须是整型和唯一的。
获取记录方法:
pa = models.Province.objects.all()print(pa)pav = models.Province.objects.all().values('id','name')print(pav)pavl = models.Province.objects.all().values_list('id','name')print(pavl)

all()的结果是一个列表,其值是一个个的Prince对象,可以使用xxx.xx的方法获取,如使用for循环:for item in pa
这时item是一个Prince对象,使用item.id和item.name就能获取对应字段的值。
values()是一个列表,其值是字典,字典中是字段名:字典值
value_list()是一个列表,其值是元组,即一条记录的值的元组。
values()和value_list()也可以这样写:
pv = models.Province.objects.values()
pvl = models.Province.objects.values_list()
不带参数,就列出所有的字段。
对于有外键的表的查询:
cv = models.City.objects.values()
print(cv)
cvl = models.City.objects.values_list()
print(cvl)打印的结果:
也是打印本表的全部字段。因为City有外键,想将外键对应的一些信息显示出来:
cv = models.City.objects.values('id','name','pro_id','pro__id','pro__name')
print(cv)
cvl = models.City.objects.values_list('id','name','pro_id','pro__id','pro__name')
print(cvl)打印结果:
这里主要注意的是pro_id,pro__id,pro__name,pro_id是City表中的字段名,City类中定义的外键叫pro,表中自动加上_id,使用双下划线__id,则是取的外键关联表中的对应的字段,这里就是Province表中的id,pro__name是Province表中的名字。也就是双下划线代表查找关联表相关字段。
对于all()方法:
pa = models.City.objects.all()print(pa[0].id,pa[0].name,pa[0].pro_id,pa[0].pro,pa[0].pro.id,pa[0].pro.name)打印结果:
1 济南 1 Province object (1) 1 山东
结果是对象的列表,取出一个对象,使用点的方法获取其中的字段值,对于外键,使用点外键名是获取的外键对应的一个对象,这里是Province对象,再使用对应的点方法获取外键对象中的字段值。要想直接获得外键的值,需要使用外键名_id,即这里的pa[0].pro_id
以上通过查找City对象值然后再通过外键关联到关联表,然后获取到关联表的字段值的方法,叫做正向查找。即起始点是从含有外键的表对象开始。
还有一种查找方法是先在关联表,即Province中先找到相关记录,然后通过这个值到City中找对应的记录,称它为反向查找。
写了一下语句:pro_list = models.Province.objects.values('ddd')
结果在页面上显示:

可以看到,提示了在values()中可以使用的参数,其中id,name我们知道是Province中的字段,那么city是什么呢?它是城市表的名字city,即代表了可以反向查找city表数据。
pro_list = models.Province.objects.values('id','name','city')
print(pro_list)打印结果:
可以看到。city对应的是city表中相应省份城市的id值。
也支持双下划线:
pro_list = models.Province.objects.values('id', 'name', 'city__name')
print(pro_list)打印结果:
QuerySet [{'id': 1, 'name': '山东', 'city__name': '济南'}, {'id': 1, 'name': '山东', 'city__name': '青岛'}, {'id': 2, 'name': '河北', 'city__name': '邢台'}, {'id': 3, 'name': '广东', 'city__name': '深圳'}]>
它们的实际应该是通过SQL查询语句left join进行多表查询的。
pro_list = models.Province.objects.all() # pro_list是Province对象的列表for item in pro_list:print("A:",item) # item是一个个对象(Province对象)print("B:",item.id,item.name) # 可以使用点号来获取对象属性print("C:",item.city_set) # 通过“表名_set”属性,关联到city表中的记录,反向查找打印结果:
A: Province object (1)
B: 1 山东
C: myadminzdy.City.None
A: Province object (2)
B: 2 河北
C: myadminzdy.City.None
A: Province object (3)
B: 3 广东
C: myadminzdy.City.None
item.city_set打印的是myadminzdy.City.None,类名.None
models.Province.objects.获取的是一个类,item.city_set获取的也是类,也支持all()、filter()、values()、values_list()等方法。
修改打印:print("C:",item.city_set.all())
C:
获得的是City对象列表。修改一下
print(item.id,item.name,item.city_set.values())
打印结果:
1 山东
2 河北
3 广东
多对多的操作,基于一对多来构建
多对多一定出现第三张表。
自己创建第三张表:
class Book(models.Model):name = models.CharField(max_length=32)class Author(models.Model):name = models.CharField(max_length=32)class A_to_B(models.Model):bid = models.ForeignKey(Book)aid = models.ForeignKey(Author)class Meta: unique_together =( # 定义联合唯一('bid','aid'),)
Django自动生成第三张表:
class Book(models.Model):name = models.CharField(max_length=32)class Author(models.Model):name = models.CharField(max_length=32)m = models.ManyToManyField('Book') # 使用ManyToManyField间接生成第三张表
因为是间接生成的表,就不能直接查找表中的记录,只能通过间接查找法查找对应的记录。
自动创建第三张表测试结果,生成表如下:

除正常的book表和author表,多了第三张自动生成的表:author_m,即以包含多对多字段的类的名称小写加上下划线字段名,这里即author_m作为默认第三张表的表名。

正向查找:
# 正向查找,从含有多对多字段的类开始的查找,Author中含有多对多字段mobj = models.Author.objects.get(id=1) # 找到作者id为1的作者对象,即鲁迅print(obj,obj.name) # 打印Author object (1) 鲁迅obj.m.all() # 这是正向查找,找到鲁迅的所有作品,即在第三张表中作者id为1的所有记录print(obj.m.all()) # ]>print(obj.m.all().values('name')) # print(obj.m.values_list('name')) #
# 反向查找,从不含多对多字段的表开始查起obj = models.Book.objects.get(id = 1) # 先找到书的对象obj.author_set.all() # 书这个类没有多对多字段,需要通过作者表_set,即author_set反向找到对应的所有作者print(obj.name,obj.author_set.values('name')) # 狂人日记
author_list = models.Author.objects.values('id','name','m','m__name')# 直接使用双下划线进行关联表字段选择显示
print(author_list) #
for item in author_list:print(item['id'],item['name'],item['m'],item['m__name'])# 1 鲁迅 1 狂人日记# 2 吴承恩 None None# 3 曹雪芹 None None# 4 测试1 1 狂人日记
添加记录,使用add():
obj = models.Author.objects.get(id = 1)
# 在第三张表中增加一个对应关系,先找到一个作者的对象,通过多对多字段,然后使用add增加
obj.m.add(4) # 参数就是book表中的id
obj.m.add(*[5,6]) # 可以一次增加多个
# 以上增加的id在book表中都存在
obj.m.add(7) # 参数所代表的id在book中不存在,出错
出错信息:

外键约束出现了错误。如果传递的参数重复了,如obj.m.add(4)写了两遍,不会出错,重复的应该在表操作时不会执行。
删除记录,使用remove():
obj.m.remove(4,5) # 删除作者id为1的作者的书籍,删除的书籍id为4和5
删除一个不存在书籍id,不会出错。
add和remove操作返回的都是None。
obj.m.clear(),清空作者的全部书籍。
修改记录,使用set():

中间表如上:
obj = models.Author.objects.get(id = 1)
obj.m.set(6)

obj = models.Author.objects.get(id = 1)
obj.m.set([6,])

看结果,实际上是保存了set中的值,其他值删除。
执行:obj.m.set([6,1,3])

反向操作,即从Book开始,增删改作者:
obj = models.Book.objects.get(id = 3)
obj.author_set.add(1,2,4)
有的保留,没有的增加
obj.author_set.remove(1)
obj.author_set.clear()
obj.author_set.set([1,2,3])
这个功能使用的场景,左右两个选择框,左边是未选定的项,右边是已经选定的项。对最终的选定情况进行设置,只需将最终确定的选定项传递到后台,使用set操作数据库。
关于获取网页提交数据的方法get与getlist,对于checkbox等传递过个值的,使用getlist


print('getlist:::',req.POST.getlist('kk'))
print('get::::',req.POST.get('kk'))

对老师进行管理的页面,涉及多对多操作:
主页面,teacher.html,列出老师的列表
{% extends "index_base.html" %}
{% block css %}{% endblock %}{% block content %}老师管理页面
添加老师
{% for k,v in teacher_list.items %}{{ k }} {{ v.name }} {% for c in v.cls_list %}{{ c.caption }}{% endfor %} 编辑 | 删除 {% endfor %}
{% endblock %}{% block js %}
{% endblock %}
{% extends "index_base.html" %}
{% block css %}{% endblock %}{% block content %}添加老师页面
{% endblock %}{% block js %}
{% endblock %}
{% extends "index_base.html" %}
{% block css %}{% endblock %}{% block content %}修改老师信息页面
{% endblock %}{% block js %}
{% endblock %}
@auth
def teacher(req):current_user = req.session.get('username')# teacher_list = models.Teacher.objects.all()teacher_list = models.Teacher.objects.values('id','name','cls__id','cls__caption')t_l = {}# t_l结构#{教师id:{'name':'教师名',# ‘cls_list':[{'id':'班级id','caption':'班级名'},{},...]}}for t in teacher_list:if t['id'] in t_l:if t['cls__id']:t_l[t['id']]['cls_list'].append({'id':t['cls__id'],'caption':t['cls__caption']})else:if t['cls__id']:temp = [{'id':t['cls__id'],'caption':t['cls__caption']}]else:temp = []t_l[t['id']] = {'name': t['name'],'cls_list': temp}print(t_l)return render(req, "teacher.html", {'username':current_user,'teacher_list':t_l})
@auth
def add_teacher(req):if req.method == "GET":cls_list = models.Classes.objects.all()return render(req,'add_teacher.html',{'cls_list':cls_list})else:name = req.POST.get('name')cls = req.POST.getlist('cls')obj = models.Teacher.objects.create(name=name)obj.cls.add(*cls)return redirect('teacher.html')@auth
def edit_teacher(req,nid):if req.method == "GET":obj = models.Teacher.objects.get(id=nid)obj_cls_list = obj.cls.all().values_list('id','caption')print(obj_cls_list)# id_list = list(zip(*obj_cls_list))print(id_list)# [(10, 56), ('测试班级55班333333', '测试分页31班')]id_list_id = id_list[0]# (10, 56) 获得老师任课的班级id元组cls_list = models.Classes.objects.all()return render(req,'edit_teacher.html',{'obj':obj,'cls_list':cls_list,'id_list_id':id_list_id})else:name = req.POST.get('name')cls_li = req.POST.getlist('cls')obj = models.Teacher.objects.get(id=nid)obj.name = nameobj.save()obj.cls.set(cls_li)return redirect('teacher.html')
path('add_teacher.html',views.add_teacher),
path('edit_teacher-.html',views.edit_teacher),
关键点 :
一个是路由项,在修改信息的时候,有两种方式,一种是以前的做法,即使用get提交时在后面加上提交需要的参数信息:edit_teacher.html?id={{id}},这样提交时id提交给了后台,第二种就是这里修改老师信息页面中的edit_teacher-{{ obj.id }}.html,在路由项中定义的路由项使用带有参数名的正则路由项:edit_teacher-
二是在修改时,需要将已选择的班级在选择列表中标注出来,办法是将已选择的班级的id做成一个元组,将所有班级信息及这个元组传到前端,前端在显示所有班级的选择项时,判断班级id是否在这个元组中,在,其option元素的selected设为selected。这里主要是注意zip()这个内置函数的使用。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
