Django-6:django模型层ORM-2
ORM-2
一、django测试文件
django中的应用目录里,会自带一个test.py的文件,可以供我们做一些测试脚本,不用书写前后端交互的形式来查看效果。
- 缺点:不能立即使用,直接书写代码并执行会报错,需要先拷贝几行代码。
配置方法:
-
一、前往manage.py中拷贝前四行代码
# 将manage.py文件中的前四行,拷贝到test.py文件 import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoProject.settings")
注意,这里的djangoProject以实际的项目命名为主,否则会报错,报错如下:
ModuleNotFoundError: No module named ‘djangoProject’
-
二、再补充两行
# test.py文件 import os # import sys 可以删掉,因为不需要这个模块就可以。 if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoProject.settings") import django django.setup() # 测试脚本代码1 # 测试脚本代码2 .......
脚本代码,无论是写在应用下的tests.py,还是自己单独开设py文件,只要按照上面的两步走,配置完成之后都可以
二、ORM常用方法
汇总一览:
函数名 | 说明 |
---|---|
all() | 返回表中所有的数据,返回值为QuerySet对象 |
filter(条件) | 过滤查询,当没有条件时会和all一样,返回所有 |
get() | 过滤查询,不过与filter不同,返回值为数据对象,会报错不建议 |
first() | QuerySet对象的方法,用于获取内部第一个数据对象,反义词是last() |
values() | QuerySet对象的方法,可获取指定字段的数据,返回的数据格式为列表套字典的QuerySet对象 |
values_list() | 与values()类似,不过返回的是列表套元祖格式的QuerySet对象 |
distinct() | 对获取到的数据进行去重处理 |
order_by() | 需要结合values()使用,可用于将获取到的数据对象,按照指定字段,从小到大或者大到小排列。 |
reverse() | 针对order_by()的排序进行反转 |
count() | 结合filter()使用,可以统计符合条件的个数 |
exclude() | 除了…以外 |
一、all()
-
作用:返回表中所有的数据,返回值为QuerySet对象
res = models.User.objects.all() ''' 输出结果示例:<QuerySet [<User: User object>, <User: User object>, <User: User object>, .....]> '''
二、filter()
-
作用:过滤查询,也可以全部查询,返回值为QuerySet对象
res1 = models.User.objects.filter() res2 = models.User.objects.filter(pk=1) #主键值为1的数据 ''' 输出结果示例: <QuerySet [<User: User object>, <User: User object>, <User: User object>, .....]> <QuerySet [<User: User object>]> '''
三、get()
-
作用:过滤查询,与filter不同的是,get获取的直接就是数据对象,不需要再使用first()来从QuerySet对象中获取。
但是该方法一旦查询不到就会报错,不建议使用,建议使用filter,因为当没查询到时,QuerySet对象会为空,但不会报错。
res1 = models.User.objects.get(pk=1
print(res1.username)
res2 = models.User.objects.get(pk=100)
'''
输出结果示例:
User object
数据对象中字段名为username的值
报错:User matching query does not exist. 翻译:用户匹配查询不存在。
'''
四、first()与last()
-
作用:获取QuerySet对象中第一和倒数第一的元素(数据对象)
res = models.User.objects.filter(pk=1).first() ''' 输出结果示例:User object '''
五、values()与values_list()
-
作用:QuerySet对象(列表套数据对象)调用后,可以获取指定字段的数据。
- values,返回的是列表套字典格式的QuerySet对象。
- values_list,返回的是列表套元祖的QuerySet对象。
res1 = models.User.objects.values('username','age') #获取这两个字段的数据。
print(res1)
res2 = models.User.objects.values_list('username', 'age')
print(res2)
'''
输出结果示例:
<QuerySet [{'username': '盖伦', 'age': 38}, {'username': '嘉文四世', 'age': 39}....]>
<QuerySet [('盖伦', 38), ('嘉文四世', 39), ('赵信', 59), ....]>
'''
1.values,返回的是列表套字典格式的QuerySet对象。
2.values_list,返回的是列表套元祖的QuerySet对象。
六、distinct()
-
作用:返回去重之后的数据,但是去重的要求,必须是两个一模一样的数据才可以去重,如果带有主键值,那么肯定数据做不到一模一样。
所以可以结合values来“过滤”掉主键的数据,只获取其他的。
res = models.User.objects.values('password').distinct() # 对查询到的所有字段为password的数据,进行去重 #(并不会对数据库进行任何修改,只是获取查询的结果去重而已。) ''' 输出结果示例: <QuerySet [{'password': 123}, {'password': 123456}, {'password': 123321},......]> '''
**七、order_by() **
-
作用:将获取到的数据对象,按照指定字段从小到大或者从大到小排列,因为需要指定字段排列,所以需要用到values
res = models.User.objects.values('age').order_by('age') # 获取每个数据对象的age字段数据,并按照 print(res) ''' 输出结果示例:<QuerySet [{'age': 26}, {'age': 27}, {'age': 38}, {'age': 39}, .....]> '''
默认从小到大排列,如果需要从大到小,只需要将order_by的参数添加一个“-”即可,代码示例如下:
res = models.User.objects.values('age').order_by('-age') print(res) ''' 输出结果示例:<QuerySet [.....,{'age': 38}, {'age': 27}, {'age': 26}]> '''
八、reverse()
-
作用:针对order_by的排序进行反转
res = models.User.objects.values('age').order_by('-age').reverse() # 等同于order_by('age'),从大到小。
九、count()
-
作用:计数,可用于统计符合条件的个数。
res = models.Book.objects.filter(price__gt = 500).count() print(res) ''' 代码:统计书籍列表中,价格大于500的个数。 输出结果示例: 4 '''
十、exclude()
-
作用:除了…以外
models.User.objects.exclude(username='zhangsan') ''' 输出结果示例:<QuerySet [<User: User object>, <User: User object>, ......]> '''
2.1 附:查看内部sql语句
作用:
- 在执行测试脚本时,可以看到实际SQL语句是那些,在数据库查询优化章节,会需要使用到。
方式一
- queryset对象,可以 .query 查看内部的sql语句。
res = models.User.objects.filter(password=123).values('pk','username')
print(res.query)
'''
控制台输出:
SELECT `app01_user`.`id`, `app01_user`.`username` FROM `app01_user` WHERE `app01_user`.`password` = 123
'''
方式二
- 配置文件配置,最终可实现所有sql都能直接查询,不需要再 .query
# settings.py文件
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
三、双下划线查询
汇总预览:
名称 | 作用 |
---|---|
__gt | 大于 |
__lt | 小于 |
__gte | 大于等于 |
__lte | 小于等于 |
__in=[值1,值2,值3…] | 值为列表中的其中之一(or的关系) |
__range=[值1,值2] | 值为range的区间内(不过这里的range是首尾都要) |
__contains=‘值’ | 查询里面含有“值”的数据,注意:区分大小写 |
__icontains=‘值’ | 查询里面含有“值”的数据,忽略大小写 |
__startswith=‘值’ | 查询出名字以“值”为开头的数据(区分大小写) |
__endswith=‘值’ | 查询出名字以“值”为结尾的数据(区分大小写) |
示例:
1.年龄大于35岁的数据
res = models.User.objects.filter(age__gt=35)
2.年龄小于35岁的数据
res = models.User.objects.filter(age__lt=35)
3.大于等于
res = models.User.objects.filter(age__gte=32)
4.小于等于
res = models.User.objects.filter(age__lte=32)
5.年龄是18 或者 32 或者40
res = models.User.objects.filter(age__in=[18,32,40])
6.年龄在18到40岁之间的,首尾都要
res = models.User.objects.filter(age__range=[18,40])
7.查询出名字里面含有s的数据 模糊查询(区分大小写)
res = models.User.objects.filter(name__contains='s')
8.查询出名字里面含有p的数据 (忽略大小写)
res = models.User.objects.filter(name__icontains='p')
9.查询出名字以j开头的数据(区分大小写)
res = models.User.objects.filter(name__startswith='j')
10.查询出名字以j结尾的数据(区分大小写)
res1 = models.User.objects.filter(name__endswith='j')
扩展:
-
当需要获取日期字段的数据时,可以使用__month和__year来获取月份和年份
当该字段为日期时,可利用__month和__year来获取年月份。 # 查询出注册时间是 2020 1月 # res = models.User.objects.filter(register_time__month='1') # res = models.User.objects.filter(register_time__year='2020')
四、外键增删改
本章节主要介绍,如何利用外键,来给表中增删改查内容。
章节的准备代码:
'''
以下为models文件,构造表的字段、外键等。
'''
class Book(models.Model):
title = models.CharField(max_length=32,verbose_name='title')
price = models.DecimalField(max_digits=8,decimal_places=2,verbose_name='price')
# 书与出版社是一对多关系
publish = models.ForeignKey(to='Publish')
# 与作者呈多对多关系
authors = models.ManyToManyField(to='Author')
class Publish(models.Model):
name = models.CharField(max_length=32,verbose_name='name')
addr = models.CharField(max_length=42,verbose_name='addr')
class Author(models.Model):
name = models.CharField(max_length=10,verbose_name='name')
# 与书呈多对多关系
# 与作者信息呈一对一关系
author_info = models.OneToOneField(to='AuthorInfo')
class AuthorInfo(models.Model):
hometown = models.CharField(max_length=42,verbose_name='hometown')
introduction = models.CharField(max_length=70,verbose_name='introduction')
以测试脚本文件为例,给数据库中的表增加数据。
'''
以下为test.py文件
'''
# book书籍表中添加数据
models.Book.objects.create(title='《水浒传》',price=79.9)
models.Book.objects.create(title='《红楼梦》',price=64.9)
models.Book.objects.create(title='《西游记》',price=57.8)
# publish出版社表中添加数据
models.Publish.objects.create(name='《人民文学出版社》',addr='北京市朝阳门内大街166号')
models.Publish.objects.create(name='《作家出版社》',addr='北京市朝阳区农展馆南里10号')
models.Publish.objects.create(name='《北京图书馆出版社出版》',addr='北京市西城区文津街7号')
# author作者表中添加数据
models.Author.objects.create(name='施耐庵')
models.Author.objects.create(name='罗贯中')
models.Author.objects.create(name='曹雪芹')
models.Author.objects.create(name='高鹗')
models.Author.objects.create(name='吴承恩')
# authorinfo作者详情表中添加数据
models.AuthorInfo.objects.create(hometown='江苏',introduction='施耐庵(1296年—1370年),名耳,明代文学家。')
models.AuthorInfo.objects.create(hometown='山西',introduction='罗贯中(约1330年-约1400年),太原人,号湖海散人,元末明初小说家。')
models.AuthorInfo.objects.create(hometown='辽宁',introduction='曹雪芹(约1715年5月28日—约1763年2月12日),名霑,字梦阮,号雪芹,又号芹溪、芹圃,中国古典名著《红楼梦》的作者,')
models.AuthorInfo.objects.create(hometown='辽宁',introduction='高鹗(1758年—约1815年),字云士,号秋甫,别号兰墅、行一、红楼外史。中国古典小说《红楼梦》出版史、传播史上首个刻印本、全璧本——程高本的两位主要编辑者、整理者、出版者之一。')
models.AuthorInfo.objects.create(hometown='江苏',introduction='吴承恩(约1500年—1582年),字汝忠,号射阳居士,又称射阳山人,南直隶淮安府山阳县河下(今江苏淮安)人,祖籍安东。明代文学家。')
此时,我们的表里面以及有了一些基本的内容了,但是并没有真正的建立关联,最明显的,外键字段都是空。
所以后续的8.1和8.2章节,将介绍如何针对一对多和多对多这两种情况,来给表中增添数据。
4.1 一对多 外键增删改
一对多的关系,也就是上面代码中的book表与publish表之间的关系,所以本小结主要针对这两个表进行操作。
一对一和一对多,都是一样的。
前言:
虚拟字段:
一本章节的代码为例
class Book(models.Model): '''略''' publish = models.ForeignKey(to='Publish') authors = models.ManyToManyField(to='Author')
publish字段与authors字段,都是<font color='MediumPurple1'>虚拟外键字段</font> 因为publish字段,会被ORM补全后缀,变成**publish_id**,所以<font color='MediumPurple1'>publish_id</font>才是真实的外键字段,这个<font color='MediumPurple1'>publish</font>就变成了不存在的字段。 而authors字段更不存在,因为ORM会给多对多关系的两张表重新生成一个表,专门用来记录对应关系的,所以<font color='MediumPurple1'>authors 和 publish</font>都是<font color='MediumPurple1'>虚拟外键字段</font>
注:至于说publish和authors字段到底是叫做外键字段还是虚拟外键字段,这个不重要,本章节以及后续的正反向、子查询、连表查询这样可以搞明白怎么回事就好,个人是喜欢叫做虚拟外键字段。
一、增加
在添加书籍时,添加上外键的对应关系。
-
第一种方法,利用数据库中实际字段publish_id ,赋值的方式添加数据,完成绑定。
models.Book.objects.create(title='《水浒传》',price=49.9,publish_id=1) models.Book.objects.create(title='《红楼梦》',price=49.9,publish_id=2) models.Book.objects.create(title='《西游记》',price=49.9,publish_id=3)
-
第二种方法,利用在models.py中书写的虚拟字段publish ,赋值一个出版社对象即可完成绑定。
publish_obj = models.Publish.objects.filter(pk=1).first() # 先拿到需要绑定的出版社对象。 models.Book.objects.create(title='《测试》',price=999.99,publish=publish_obj) # 利用虚拟字段绑定。
二、删
删除本条数据,同时会解除其绑定关系(级联删除)
-
models.Publish.objects.filter(pk=1).delete() # 级联删除
三、修改
-
第一种方法:
调用update函数,将publish_id这个实际字段赋值上出版社表数据的主键值。
# 将主键值为1的书,与出版社表,主键值2的数据,进行绑定。 models.Book.objects.filter(pk=1).update(publish_id=2)
-
第二种方法:
同样是调用update函数,但使用的是publish这个虚拟字段,随后赋值的就不是主键值,而是出版社对象。
# 先获取出版社对象 publish_obj = models.Publish.objects.filter(pk=1).first() # 利用update函数来给外键字段重新赋值。 models.Book.objects.filter(pk=1).update(publish=publish_obj)
四、查
- filter()、all()、values()、values_list() 都是查,此处略。
4.2 多对多 外键增删改查
多对多的增删改查其实就是对那第三张虚拟表进行操作。
一、增
-
第一种方法,通过**add(主键)**的方式。
# 获取图书数据对象 book_obj = models.Book.objects.filter(pk=1).first() # 图书对象 .虚拟字段 book_obj.authors # 打印控制台会输出 app_01.Author.None book_obj.authors.add(1) # 书籍id为1的书籍,添加绑定一个主键为1的作者。 book_obj.authors.add(2,3) # 由于是多对多的关系,所以可以一次绑定多个 ''' .authors类似于已经到了第三张关系表了 book_obj.authors.add(4,5) 表示该书籍对象,与作者表主键值为4和5的数据,进行绑定 '''
这里的 authors为虚拟字段,不要和 author作者表混淆
-
第二种方法,通过**add(对象)**的方式。
author_obj1 = models.Author.objects.filter(pk=2).first() author_obj2 = models.Author.objects.filter(pk=3).first() book_obj = models.Book.objects.filter(pk=1).first() book_obj.authors.add(author_obj1,author_obj2) """ add给第三张关系表添加数据 括号内既可以传数字也可以传对象,并且都支持多个。 """
-
代码示例:
# 给书籍主键值2的数据,绑定author表主键值为3和4的数据。 models.Book.objects.filter(pk=2).first().authors.add(3,4) # 给书籍主键值3的数据,绑定author表主键值为5的数据。 models.Book.objects.filter(pk=3).first().authors.add(5)
二、删
-
第一种方法,直接remove
# 解除主键为5的书籍 与主键为5的作者 两者的关系清除 # 也就是第三章表,即book_authors表 models.Book.objects.filter(pk=4).first().authors.remove(5)
-
第二种方法,remove传入对象。
author_obj = models.Author.objects.filter(pk=5).first() models.Book.objects.filter(pk=4).first().authors.remove(author_obj) """ remove 括号内既可以传数字也可以传对象 并且都支持多个 """
三、改
-
函数名 set() ,不过在修改时,set的参数必须是个可迭代对象
author_obj = models.Author.objects.filter(pk=5).first() author_obj2 = models.Author.objects.filter(pk=1).first() # 主键值为4的书籍,与作者表中主键为5和1的进行数据绑定。 models.Book.objects.filter(pk=4).first().authors.set([author_obj,author_obj2]) ''' 先获取作者对象,随后将指定书籍的作者进行重新赋值。 '''
四、清空
-
作用:清空在第三张关系表中某个书籍与作者的绑定关系
# 先获取需要清空作者的书籍 book_obj = models.Book.objects.filter(pk=1).first() # 在第三张关系表中清空某个书籍与作者的绑定关系 book_obj.authors.clear()
五、多表查询
在8章节中,我们将多张表的数据通过外键进行的关联,关联之后要如何通过绑定的关系去跨表查询呢,
5.1 正反向查询的概念
正向查询:
- 外键字段在哪个表,那么通过这个表查询就是正向。
反向查询:
- 外籍字段在对面,那么通过该表查对面就是反向。
一对一和多对多正反向的判断也是如此
正反向查询的区别:查询时格式不一样:
- 正向查询按照 .字段名小写(虚拟外键字段)
- 反向查询按照 .表名小写._set
5.2 子查询
子查询:基于对象的跨表查询
一、需求:查询书籍主键为1的出版社、
分析:
- 通过书查询出版社,即book --> publish,因为主键在book,所以本次查询为正向查询
- 正向查询只需要**.字段名小写(虚拟字段)**即可。
代码示例:
- book_obj.publish改行代码中的 publish为 虚拟字段名而非表名。
book_obj = models.Book.objects.filter(pk=1).first()
res = book_obj.publish
print(res) # Publish object
print(res.name) #输出对应出版社的名称
print(res.addr) #地址
'''
先获取主键值为1的数据对象,随后通过这个书籍的数据对象,.publish虚拟外键字段,即可拿到本书与之关联的publish出版社数据对象。
有了数据对象之后 .字段名,也就是.name 就可以拿到对应出版社的名称。
'''
注意:
-
不管是正向查询还是反向查询,只要数据对象不止一条时,都需要加上 .all()方法
如:通过书籍查询作者,这种情况下,就需要用all()
book_obj = models.Book.objects.filter(pk=1).first() print(book_obj.bind_authors.all()) ''' <QuerySet [<Author: Author object>, <Author: Author object>]> '''
如何可以快速判断出是否需要加all方法:
当看到返回值为 应用名.类.None 时,那么就需要加all() 如:app_01.Book.None
二、需求:查询出版社主键为2的所有书籍
分析:
- 通过出版社查询书籍,主键在书籍表,那么就是反向查询
- 反向查询需要按照表名小写._set
代码示例:
# 出版社对象
publish_obj = models.Publish.objects.filter(pk=2).first()
# 出版社主键为2,所对应的书籍queryset对象
book_query = publish_obj.book_set.all()
for book_obj in book_query:
print(book_obj.title) # 拿到数据对象之后.title属性,即可获取图书的名称。
-
publish_obj.book_set.all()这里的book 为表名
因为一个出版社所出版的图书肯定不止一本,所以数据会有很多,需要再加上all()
三、需求:通过书籍,拿到作者简介
主键在book表,所以book表——虚拟字段——>author表,是正向查询。
此时再通过,author表——虚拟字段——>authorinfo表,查询对应书籍作者的详细信息,这段也是正向查询,因为主键在author表这边。
'''
查询book表中,主键值为3这本书的作者。
'''
# 书籍数据对象
book_obj = models.Book.objects.filter(pk=3).first()
# 案例为了节约篇幅,所以在该书的众多作者中,只选取QuerySet对象中的第一位作者数据对象
author_obj = book_obj.authors.all().first()
authorInfo_obj = author_obj.author_info # 从作者表再跳到作者信息表中,获取对应作者信息对象。
print(authorInfo_obj.introduction) # 打印对应字段的数据
- book_obj.authors.all().first(),这里的authors为虚拟字段名
- author_obj.author_info,这里的author_info为虚拟字段名
注意不要和表名混淆,做测试时可以将models.py中,建立表关系的时候,都把虚拟字段改成bind_publish、fk_publsh、fk_author等,这样可以更好的区分。
三、需求:通过作者名称,拿到该作者写的所有书籍名称
主键在book,所以通过作者对象进行查找,是反向查询。
author_obj = models.Author.objects.filter(name='施耐庵').first()
book_query = author_obj.book_set.all()
# 此时book_query为queryset对象,里面的每个数据对象就是作者'施耐庵'所写的书。
9.3 联表查询
联表查询: 基于双下划线的跨表查询
代码示例:
一、找到《水浒传》这本书的作者和作者简介
res = models.Book.objects.filter(title='《水浒传》').values('authors__name','authors__author_info__introduction')
print(res)
print(type(res.first()))
'''
返回结果:
<QuerySet [{'authors__name': '施耐庵', 'authors__author_info__introduction': '简介'},
{'authors__name': '罗贯中', 'authors__author_info__introduction': '简介'}]>
<class 'dict'>
'''
注意:
-
authors__name这里的authors可不是表名,而是虚拟字段,后面的__name为连表查询,查询的字段名为name。
所以该行代码其实就是先通过authors虚拟字段,进入到第三章虚拟表,然后查询到对应author作者表的name字段。
-
authors__author_info__introduction这一行,同样是利用__双下划线后跟外键名(虚拟字段)实现跨表查询,以及查询表内字段值。
-
另外,返回的结果虽然同样是QuerySet对象,但是里面不再是数据对象,而是字典格式。
二、找到作者吴承恩,所出版的书和作者的简介
res = models.Author.objects.filter(name='吴承恩').values('book__title','author_info__introduction')
print(res)
# 输出结果示例:
# <QuerySet [{'book__title': '《西游记》', 'bind_author_info__hometown': '江苏'}]>
- 通过作者查书,因为是反向查询,所以直接表名小写开头,在查询作者详情表时,为正向查询,以外键字段(虚拟字段)开头。
练习:
# 通过作者故乡,查到作者(一对一,反向)
res = models.AuthorInfo.objects.values('author__name').filter(hometown='江苏')
print(res)
# 通过作者查书(多对多,反向)
res = models.Author.objects.filter(name='曹雪芹').values('book__title')
print(res)
# 通过书查作者(多对多,正向)
res = models.Book.objects.filter(title='《红楼梦》').values('authors__name')
print(res)
# 通过书查出版社(一对多,正向)
res = models.Book.objects.filter(title='《西游记》').values('publish__name')
print(res)
# 通过出版社查书(一对多,反向)
res = models.Publish.objects.filter(name='《北京图书馆出版社出版》').values('book__title')
print(res)
基于对象和双下划线跨表查询的不同之处:
-
子查询(基于对象的跨表查询),最后可以拿到的是数据对象,有了对象可以通过调用方法,来获取该对象的其他数据。
<QuerySet [<Book: Book object>]>
-
联表查询(基于双下划线的跨表查询),最后可以拿到的是字典
<QuerySet [{'publish__name': '《人民文学出版社》'}]>