框架第八课---Q查询进阶操作,ORM查询优化(重点!),ORM事务操作,ORM常用字段类型,ORM常用字段参数,ORM三种创建多对多的方式,Ajax请求发送文件数据给后端
上周内容回顾
-
图书管理系统
1.表的设计与创建 书籍表、出版社表、作者表、作者详情表 2.开设图书管理系统首页接口 前端页面搭建 3.开设图书列表展示页 ORM查询、过滤器、标签 4.开设图书添加页 ORM操作 5.开设图书编辑页 路由动态匹配、视图函数传参 6.开设图书删除页 前端js二次确认
-
聚合查询
和数据库相关的,和models相关的,这些模块一般都在django.db或者django.db.models下面!!! from django.db import Max,Min,Sum,Avg,Count 整张表默认是一组 直接使用聚合函数也可以 models.Book.objects.aggregate(Max('price'),min_price=Min('price'))
-
分组查询
models.Book.objects.annotate() 按照整张表的主键值分组 models.Book.objects.values('字段').annotate() 按照括号内指定的字段分组
-
F与Q查询
---------------------------------------------------
F 能够帮助我们,去获取表中另外字段所对应的数据!!
F:获取表中指定字段对应的数据作为查询条件!!!
from django.db.models import F
models.Book.objects.filter(kucun__gt=F('maichu')) # 书表里面库存数大于买出数的书对象
models.Book.objects.update(price=F('price') + 888) # 每一条数据都是原来价格加了888
ps:针对字符串类型的数据拼接需要使用模块concat、values
---------------------------------------------------
Q:可以将多个查询条件的关系做修改!!! 用Q将条件包起来!!!
from django.db.models import Q
models.Book.objects.filter(Q(pk=1),Q(title='三国')) # and关系
models.Book.objects.filter(Q(pk=1) | Q(title='三国')) # or关系
models.Book.objects.filter(~Q(pk=1),Q(title='三国')) # not关系
Q与Q之间用逗号连接and关系 Q与Q之间用管道符连接or关系
Q前面加波浪号代表该Q的条件取反!!!
今日内容概要
- Q查询进阶操作!!!
- ORM查询优化!!!面试可能会问
- ORM事务操作
- ORM常用字段类型
- ORM常用字段参数
- ORM三种创建多对多的方式
- Ajax请求
今日内容详细
Q查询进阶操作
Q的另外一个作用是:能够将查询条件的左侧字段名变成字符串的形式!!!
将查询条件变为字符串,就具备了和用户互动的条件了!!!
from django.db.models import Q
q_obj = Q() # 1. 产生q对象
q_obj.connector = 'or' # 默认多个条件的连接是and,也手动可以修改为or
q_obj.children.append(('pk', 1)) # 2. 添加查询条件
q_obj.children.append(('price__gt', 2000)) # 3. 支持添加多个
res = models.Book.objects.filter(q_obj) # 4. 查询支持直接填写q对象
print(res)
----------------------------------------------
q_obj = Q()
q_obj.connector = 'or'
q_obj.children.append(('pk', '1'))
q_obj.children.append(('title__contains', '三'))
res = models.Book.objects.filter(q_obj)
# 这个'三' 就可以有用户在前端输入,后端通过request动态获取!!!
# 查询 书籍表里面title字段里面数据有含有三的书籍对象!!!
# 在前端的页面上有些简单的搜索框就可以用Q查询来实现
# 搜索的类型就是字段名的字符串,用户填写的要搜索的内容对应到Q里面就是对应的具体的条件
.
.
.
.
.
ORM查询优化 重点!!!
优化主要就是优化的sql执行的次数,优化查询数据的时间!!
1.ORM的查询默认都是惰性查询!!!
-------------------------------------------
res = models.Book.objects.all()
print('hehe')
# 这种情况下如果ORM语句的下面的代码里,都没有引用到接收该sql语句的变量名时,
# 这个时候django就默认不执行这句话了,这种现象就叫做惰性查询!!!
-------------------------------------------
.
.
.
2.ORM的查询自带分页处理!!!
所以将来自己写sql对一张表进行数据查询时,
最好要在sql语句的最后加个limit限制一下查询数据的展示的数量!!!
ORM的查询自带分页处理!!!ORM会自动帮我分页,防止当表里面数据特别多的时候,
一下子把电脑搞卡死了!!!
.
.
.
3.only与defer 关键字 优化查询数据的时间
原来用all,要查所有字段的数据,然后封装到一个个对象里面去
现在用only就可以只查需要的几个字段数据,封装到一个个对象里面去,
这样查询数据的时间肯定减少了!!
面试甚至会出考题考你,对象点字段名,哪一个走了sql查询,哪一个没走sql查询!!!
-----------------------------
res = models.Book.objects.all() # queryset [数据对象、数据对象]
for obj in res:
print(obj.title) # 点击括号内填写的字段,不走SQL查询
print(obj.price)
这种情况下 只会走一条sql语句,查询所有数据,然后将所有数据封装到queryset里面一个个对象里面去!!
后续的for循环出一个一个的数据对象,以及用数据对象点的方式拿对象里面的数据都不再走数据库的查询了!!!
-------------------------------------------
需求:只想要书名和价格
res = models.Book.objects.all().values('title','price') # queryset [{},{},{}]
print(res) # queryset [{'title':'三国演义','price': Decimal('22.2')},{},{}]
for i in res: # 此时i 是一个个的字典{'title':'三国演义','price':Decimal('22.2')}
print(i.title)
print(i.price) # 此时字典是没办法通过点的方式取值的,所以会报错!
---------------------------------------------
上面all()得到的结果是包含一个个的数据对象的queryset,
但是每个数据对象里面含有的是所有的字段名!
上面values()方法得到的结果是包含一个个的字典的queryset,
每个字典里面含有的是指定字段的数据!
.
.
.
关键字 only
感觉像是all的升级版 all拿到的对象,里面有所有的字段名及对应的值
only拿到的对象,里面只有括号里面指定的字段名及对应的值
后续用对象点括号里面有的字段名不走sql查询,点没有的字段名会走sql查询!!!
only方法的作用是:可以将括号里面所列举的字段封装到数据对象里面,
当数据对象点击这些字段的时候是不会走sql查询的。
但是如果用数据对象去点击括号里面所没有填写的字段时,可以拿到数据,但是每点一次都会走一次sql数据库查询。
需求升级:只获取指定字段的数据,并且结果还是一个的数据对象!!!
res = models.Book.objects.only('title', 'price')
print(res) # queryset [数据对象、数据对象]
for obj in res:
print(obj.title)
print(obj.price) # 点击括号内填写的字段 不走SQL查询
# 这种情况下用only获取到的对象,可以直接点击它括号里面的字段,不会走sql语句
print(obj.publish_time)
# 也可以点击括号内没有的字段,也能获取到数据 ,但是会走SQL查询
-------------
.
.
关键字 defer 和only作用相反
拿到的对象里面除了括号里面指定的字段名数据没有 ,其他的字段名数据都有!!!
后续用对象点括号里面有的字段名就会走sql查询,
点括号里面没有的字段名,不走sql查询!!!
res = models.Book.objects.defer('title', 'price')
print(res) # queryset [数据对象、数据对象]
for obj in res:
print(obj.title) # 点击括号内填写的字段 走SQL查询
print(obj.price)
print(obj.publish_time) # 点击括号内没有的字段获取数据 不走SQL查询
.
.
.
.
.
4.select_related与prefetch_related 优化的sql执行的次数
面试问的频率可能更高!!!
如果正常用表模型对象点关联表里面的字段时,肯定会走sql查询
如果该sql查询使用的地方较多,使用的次数也较多的情况下
就可以考虑使用select_related或prefetch_related来代替all
这样无论是基于连表还是子查询,走完后,
后续再用对象点关联表里面的字段时就不走sql查询了
.
.
.
.
select_related 底层是先连表后查询,再封装到对象里
res = models.Book.objects.all()
for obj in res:
print(obj.title) # 不走SQL查询,all的sql查询将所有数据都塞到每个对象里面了
print(obj.publish.name) # 当用数据对象点关联的出版社表里面的数据时,每次查询都需要走SQL查询
------------------------------------------------
------------------------------------------------
res = models.Book.objects.select_related('publish') # 底层是先连表后查询封装
# 括号内支持填写一对多外键字段
# 提前先连表后查询数据,最后封装到一个个对象里面去!
for obj in res:
print(obj.publish.name) # 不再走SQL查询!!!
# 先将书表和出版社表拼到一起,然后把大表里面的所有数据封装到一个个书籍对像里面去
# 这样拿到的对象通过点的方式既可以点书表里面的所有字段,也可以点出版社表里面的所有字段
# 所以就不需要再走sql查询了!!!
------------------------------------------------
------------------------------------------------
res = models.Book.objects.select_related('authors')
# 括号内不支持填写多对多外键字段!!!
res1 = models.Author.objects.select_related('author_detail')
# 括号内也支持填写一对一外键字段
print(res1)
-------------------------------------------------
.
.
.
.
prefetch_related 底层是先子查询,再封装到对象里
res = models.Book.objects.prefetch_related('publish')
# 底层是子查询,总共执行两条sql语句,第一条sql查询的结果作为第二条sql的查询条件
for obj in res:
print(obj.publish.name) # 不再走SQL查询!
# prefetch_related() 括号里面也是只能填写外键字段(除了多对多)
# 作用是 先把书表里面的所有数据查出来,然后基于书里面publish_id对应的值,再到出版式表里面,把对应的数据再查出来,之后封装起到一个个的书籍对象里面去
.
.
.
.
.
.
ORM的事务操作
不配置事务代码,默认orm走到哪行,就算到哪行,就没有失败回退一说了!!
"""
1.事务的四大特性(ACID)
原子性、一致性、隔离性、持久性
2.相关SQL关键字
start transaction;
rollback;
commit;
savepoint;
3.相关重要概念
脏读、幻读、不可重复读、MVCC多版本控制...
当一个事务没有提交确认的时候,另外一个事务在读取数据的时候就会出现'脏读'
-------
指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,
当之前的事务再次读取该范围的记录会产生幻行。
------
一个事务从开始直到提交之前所作的任何修改,对其他事务都是不可见的,这种级别也叫做"不可重复读"
"""
django orm提供了至少三种开启事务的方式:
------------------------------------------
方式1:配置文件数据库相关添加键值对(全局开启,全局有效):
"ATOMIC_REQUESTS": True
# 每次网络请求所涉及到的orm操作同属于一个事务!!!
# 这一次网络请求所涉及到的所有orm操作中,只要有一个orm操作失败,整个事务自动回退到初始状态
# 就是事务的原子性,事务中的各项操作是不可分割的整体,要么同时成功要么同时失败!!!
# 当视图函数走到return后,就算这个事务已经确认完成了!!!
------------------------------------------
------------------------------------------
方式2: 装饰器 局部有效 只对装饰的函数有效!!!
from django.db import transaction
@transaction.atomic # 事务的装饰器
def index():pass
被装饰器修饰的视图函数里面的所有orm操作,默认是一个事务,没问题正常执行,有问题自动回退
------------------------------------------
------------------------------------------
方式3:(在视图函数的代码里面用) with transaction.atomic() 局部有效
from django.db import transaction
def reg():
其他的代码
with transaction.atomic():
orm操作代码1
orm操作代码2
orm操作代码3
在with transaction.atomic下面的所有orm操作代码,
默认是一个事务,没问题正常执行,有问题自动回退!!! 有点像异常捕获的感觉
------------------------------------------
.
.
.
.
.
ORM常用字段类型
----------------------------------------------
AutoField 自增 字段类型
括号内常见参数: primary_key=True
-----------------------
CharField 对应到数据库里面是varchar字段类型
括号内常见参数: max_length
----------------------------------------------
数字相关字段类型:
IntegerField
BigIntergerField
DecimalField
括号内常见参数: max_digits decimal_places
----------------------------------------------
日期相关字段类型:
DateField
括号内常见参数: auto_now auto_now_add
DateTimeField
括号内常见参数: auto_now auto_now_add
----------------------------------------------
布尔值字段类型:
BooleanField
传布尔值自动存0或1
----------------------------------------------
文本字段类型:
TextField
存储大段文本
----------------------------------------------
邮箱字段类型:
EmailField
存储邮箱格式数据
----------------------------------------------
文件对象字段类型:
FileField
传文件对象 自动保存到提前配置好的路径下并存储该路径信息!!
参数:
upload_to = "" 上传文件的保存路径
storage = None
# 存储组件,默认django.core.files.storage.FileSystemStorage
-----------------------------------------------------------
ORM还支持用户自定义字段类型:
class MyCharField(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
return 'char(%s)' % self.max_length
class User(models.Model):
name = models.CharField(max_length=32)
info = MyCharField(max_length=64)
.
.
.
.
明天看
ORM常用字段参数
primary_key 主键
verbose_name 注释
max_length 字段长度
max_digits 小数总共多少位
decimal_places 小数点后面的位数
auto_now 每次操作数据自动更新事件
auto_now_add 首次创建自动更新事件后续不自动更新
null 允许字段为空
default 字段默认值
unique 唯一值
db_index 给字段添加索引
choices 当某个字段的可能性能够被列举完全的情况下使用
性别、学历、工作状态、...
class User(models.Model):
name = models.CharField(max_length=32)
info = MyCharField(max_length=64)
# 提前列举好对应关系
gender_choice = (
(1, '男性'),
(2, '女性'),
(3, '其他'),
)
gender = models.IntegerField(choices=gender_choice,null=True)
user_obj = User.objects.filter(pk=1).first()
user_obj.gender
user_obj.get_gender_display()
to 关联表
to_field 关联字段(不写默认关联数据主键)
on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。
1、models.CASCADE
级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
2、models.SET_NULL
当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
3、models.PROTECT
当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
4、models.SET_DEFAULT
当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
5、models.SET()
当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
6、models.DO_NOTHING
什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似
.
.
.
Ajax组件
ajax的全名是:Asynchronous Javascript And XML(异步JavaScript和XML)
Ajax功能就8个字: 异步提交,局部刷新
ajax不是一门新的技术并且有很多版本 我们目前学习的是jQuery版本(版本无所谓 本质一样就可以)
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。(这一特点给用户的感受是在不知不觉中完成请求和响应过程)
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
---------------------------------------------------
需求:做一个页面,该页面上有3个input框,用户在前两个input框里面输入一些数字,点击提交按钮后,在第3个框里面展示出来结果!,要求点了按钮后将两个数据发送给后端,后端计算好后,再将结果发送给前端的第3个input框,然后在框里面展示出来!!!点击提交按钮后页面不准刷新!!!
---------------------------------
当html页面上的按钮触发点击事件,运行到发送ajax请求的代码时,ajax将数据按设定的请求方式朝设定的地址,发送请求后,
如果视图函数接收到了ajax请求后request.POST()获取请求体里面的数据后,此时视图函数的返回值作为参数传给ajax的异步回调函数,从而触发异步回调函数的运行!!不会将返回值传递到页面上去了,不再影响整个页面了!!!
以前只要后端用return HttpResponse('哈哈哈哈'),那么一定会在页面上显示出 哈哈哈哈 几个字的,现在只要用了ajax从前端朝后端发送post请求后,后端的return返回的数据就不会直接对页面起作用了,而是将返回的结果作为参数传给ajax的异步回调函数,从而触发异步回调函数的运行!!
------------------------------------------
基本语法:
<body>
<input type="text" id="d1"> + <input type="text" id="d2"> = <input type="text" id="d3">
<button id="subBtn">点我发送ajax请求</button>
<script>
// 2.给按钮绑定点击事件
$('#subBtn').click(function () {
// 1.先获取两个框里面的数据
let v1Val = $('#d1').val();
let v2Val = $('#d2').val();
// 3.发送ajax请求
$.ajax({
url:'', // 朝后端发送数据的地址 三种填写方式与form标签的action用法一致
type:'post', // 请求方式 默认get请求
data:{'v1':v1Val,'v2':v2Val}, // 发送的数据
success:function (args) { // 异步回调函数,后端返回结果后自动触发,args接收返回的数据
alert(args)
$('#d3').val(args)
}
})
})
</script>
</body>
---------------------------------
def ab_ajax_func(request):
if request.method == 'POST':
print(request.POST)
v1 = request.POST.get('v1')
v2 = request.POST.get('v2')
res = int(v1) + int(v2)
return HttpResponse(res)
# return HttpResponse('哈哈哈哈') # 不再影响页面,而是交给了args行参
# return render(request, 'abAjaxPage.html') # 不再影响页面,而是交给了args行参
# return redirect('https://www.baidu.com/') # 不再影响页面,而是交给了args行参
return render(request, 'abAjaxPage.html')
----------------------------------
.还可以用失去焦点事件来玩一下,不需要用按钮了!!!
<body>
<input type="text" id="d1"> + <input type="text" id="d2"> = <input type="text" id="d3">
<button id="subBtn">点我发送ajax请求</button>
<script>
// 2.给按钮绑定点击事件
$('#subBtn').blur(function () {
// 1.先获取两个框里面的数据
let v1Val = $('#d1').val();
let v2Val = $('#d2').val();
// 3.发送ajax请求
$.ajax({
url:'', // 朝后端发送数据的地址 三种填写方式与form标签的action用法一致
type:'post', // 请求方式 默认get请求
data:{'v1':v1Val,'v2':v2Val}, // 发送的数据
success:function (args) { // 异步回调函数,后端返回结果后自动触发,args接收返回的数据
$('#d3').val(args)
}
})
})
</script>
</body>
.
.
.
可以看到当点击按钮触发单击事件后,发送ajax请求给后端后,后端的返回值并不会直接发送到页面上去,而是将回的结果作为参数传给ajax的异步回调函数,从而触发异步回调函数的运行!!
最终在页面上看到了alert弹出框的效果了!!!
.
同理,可以看出当我们返回一个页面时,也不会真正的返回一个html页面,而是将返回的结果作为参数传给ajax的异步回调函数,从而触发异步回调函数的运行!!
最终在页面上看到了alert弹出框的效果变成了abAjaxPage.html里面的代码了!!!
返回的abAjaxPage.html里面的页面数据被异步回调函数的行参捕获到了,
只要用了ajax,后端的返回值就不会影响整个页面了!!!
返回值只会与ajax的异步回调函数做交互了!!!
.
.
甚至当用过ajax后,后端的重定向,也没反应了,还是一样,返回值只能和ajax的异步回调函数做交互,以及不能再影响到整个页面了
.
.
.
Content-Type 数据的编码格式
为什么django后端针对不同的数据,会放到不同的方法里面,django是怎么知道的?
比如request.POST与request.FILES
前端发送的数据如果携带了文件,需要提前告诉dango,发送的数据的编码格式Content-Type为formdata
ajax中通过Content-Type 参数的不同来控制的,django根据ajax发来的数据的Content-Type参数的不同,来对数据做不同的处理!!!
form表单只支持发送urlencoded与formdata这两种形式的编码数据!!!
只要前端往后端发送数据,在数据的请求头里面一定会有content—type 不主动设置默认就是urlencoded数据的编码格式,所以该Content-Type参数就是告诉django应该怎么处理数据的依据!!!
----------------------------------------------------------------
1.urlencoded
ajax提交数据的默认的编码格式、form表单提交数据的默认编码格式也是
数据格式 xxx=yyy&uuu=ooo&aaa=kkk
django后端会自动处理到request.POST中或者request.GET中!!!
---------------------------------------------------------------
2.formdata
django后端针对普通的键值对还是处理到request.POST中!!!
但是针对文件会处理到request.FILES中!!!
----------------------------------------------------------------
3.application/json form表单不支持 ajax可以
<button id='d1'>发送json格式数据</button>
<script>
$('#d1').click(function () {
$.ajax({
url:'', # 控制朝哪个路由提交请求,不写默认就朝当前页面的地址
type:'post', # 控制提交的请求的方式,不写默认是get
data:JSON.stringify({'name':'jason','age':18}),
contentType:'application/json',
success:function (args) {alert(args)}
})
} )
</script>
# 注意只要在ajax里面,只要contentType:'application/json'说明了要发json格式数据,那么data后面发的数据就一定要是json格式数据!!!
---------------------------------
django针对Content-Type 是application/json 的请求数据,既不会交给request.POST也不会交给request.FILES 而是交给request.body里面了,后端需要从request.body中获取并自己处理
其实,前端发过来的数据第一时间,都是存在request.body里面的,然后再根据编码格式的不同,采用不同的操作,比如Content-Type是urlencoded时,将body里面的数据处理好后赋值给request.post
如果Content-Type是formdata时,拿出普通的键值对给到request.POST,拿出文件给到request.FILES
如果Content-Type是application/json时,就把数据放在request.body里面不做任何操作!!
json.loads自带解码功能,如果给它传的byte类型的数据,会自动帮你解码,然后去反序列化!!
.
.
.
ajax既可以发 urlencoded 数据,
ajax也可以发form表单不能发的json格式数据,
ajax也能携带文件数据
ajax携带文件数据发给后端 重要!!!
<script>
$('#d3').click(function () {
// 1.先产生一个FormData对象,该对象专门用来帮我们携带含有文件数据的数据
let myFormDataObj = new FormData();
// 往该对象中添加普通数据
myFormDataObj.append('name', 'jason');
myFormDataObj.append('age', 18);
// 往该对象中添加文件数据
myFormDataObj.append('file', $('#d2')[0].files[0])
// 注意这个地方'file'相当于就是键 , $('#d2')[0].files[0] 文件对象相当于就是对应的值!!!
// 2.发送ajax请求
$.ajax({
url:'',
type:'post',
data:myFormDataObj,
// ajax发送文件固定的两个配置
contentType:false, # 因为是用对象发送数据,不要有任何编码
processData:false, # 不要让前端做任何的处理,不要做任何的打包,什么别做就把对象发过去就行了!!!
success:function (args){
alert(args)
}
})
})
</script>
--------------------------------
补充: 标签对象转文件对象的方法:
$('#d2') jquery对象
$('#d2')[0] 标签对象
$('#d2')[0].files[0] 文件对象
标签对象.files[0] 得到文件对象
--------------------------------
作业
1.利用q查询进阶操作编写一个简易版本的图书查询功能
书籍名称、书籍价格...
2.利用ajax完整一个用户名实时校验功能
3.利用ajax编写一个删除功能的二次提醒
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 解决跨域问题的这6种方案,真香!
· 分享4款.NET开源、免费、实用的商城系统
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库