Django模型层ORM
测试环境
Django默认需要整个项目跑起来才可以运行功能函数,如果想测试某个文件需要准备测试环境。(主要指模型层 models.py)
1.python自带测试环境
python Console 但整个是命令行形式 看起来不够直观
2.自己搭建
在自己的app里面创建一个test.py文件
并复制已下代码
from django.test import TestCase
# Create your tests here.
import os
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djmodel.settings')
import django
django.setup()
#需要执行的内容
main()
查看orm底层执行的sql语句
在sttings文件中配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level': 'DEBUG',
},
}
}
这样每次运行models命令都会显示对于的sql语句
常用关键字
create
表中新增数据,并有返回值是创建的对象本身
res = models.User.objects.create(name='moon',age=18)
print(res)
# 在数据库表名 user 的表中 插入了一条数据,对应字段数据(name='moon',age=18)
# res就是这条数据本身
filter()
筛选 支持多个条件 默认是 多个条件是and关系
res = models.User.objects.filter(name='moon')
print(res)
# 查询 user表中 字段name 是 moon 的数据对象
# 返回的是 <QuerySet [<User: User object (1)>]> 是一个列表
搭配 first() last() 使用
res = models.User.objects.filter(name='moon').first()
# 拿到查询列表中 第一个数据对象 res=对象
res = models.User.objects.filter(name='moon').last()
# 拿到查询列表中 最后一个数据对象 res=对象
update()
更新数据 搭配筛选来决定更新的个数
# 批量更新
models.User.objects.filter().update()
#没有筛选条件 默认选中的是全部 所以是针对全部更新
# 单个更新
res = models.User.objects.filter(id=1).update(age=22)
print(res)
# 帅选出id=1的数据对象,针对这个对象 进行更新数据 age=22
delete()
删除数据 搭配筛选来决定删除的个数
# 批量删除
models.User.objects.filter().delete()
#没有筛选条件 默认选中的是全部 所以是针对全部删除
# 单个删除
res = models.User.objects.filter(id=1).delete()
print(res)
# 帅选出id=1的数据对象,针对这个对象 进行删除
all()
查询所有数据
models.User.objects.all()
<QuerySet [<User: 用户对象的对象是:duo>, <User: 用户对象的对象是:bo>...]>
# 或得到所有数据对象的类别
values()
指定字段获取内容
models.User.objects.all().values('name')
#获取user表中字段name的所有的值
models.User.objects.filter(id=1).values('name')
# #获取user表中id为1的数据 的 name字段的值
models.User.objects.values('name')
print(res) # <QuerySet [{},{},{}]> 列表套字典
# <QuerySet [{},{},{}]> 列表套字典
values_list()
指定字段获取内容
models.User.objects.values_list('name')
# <QuerySet [('duo',), ('bo',), ('duoduo',), ('bo',), ('hong',)]>
结果是QuerySet[(),(),(),()]列表套元组
distinct()
将查询的结果去重,不影响数据库中的数据
去重条件必须是查询出来的结果必须一模一样 所以不要带主键查询 因为主键都是不同的
models.User.objects.values('name','age').distinct()
# 针对表 user 字段 name 和 age 完全一样的数据去重展示
order_by()
根据指定条件排序 默认升序
models.User.objects.order_by('age')
# user表进行排序 排序根据为age 默认升序 从小到大
models.User.objects.order_by('-age')
# 加上 - 号 就是默认降序 从大到小
count()
统计结果集中数据数据的个数 返回值是整数
models.User.objects.filter(age=22).count()
# 统计user表中 age是22的有几个
# 3
exists()
判断数据中是否有该数据 返回布尔值
models.User.objects.filter(id=1).exists()
# 查看user表中是否有id为1的数据存在
---
True
ORM执行编写SQL语句
复杂数据查询时候orm的操作效率可能比较低 没有原生的sql效率高,我们也可以自己编写sql
关键词 raw
res = models.User.objects.raw('select * from app01_user;')
# 直接编写sql语句去执行
方式二:
from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursor.fetchall())
# 利用模块生成 cursor对象 然后利用cursor执行sql语句
# cursor.fetchall() 接收反馈结果
双下划线查询
大于 小于 大于等于 小于等于 区间 包含等查询方式
我们通过models.User.objects.filter()方法可以获得多个符号条件的对象列表
通过.filter()方法获得数据,支持双下划线查询
'''
只要还是queryset对象就可以无限制的点queryset对象的方法
queryset.filter().values().filter().values_list().filter()...
'''
models.User.objects.filter(age__gt=18)
# 查询年龄大于18的数据对象
models.User.objects.filter(age__lt=18)
# 查询年龄小于18的数据对象
models.User.objects.filter(age__gte=18)
#大于等于
models.User.objects.filter(age__lte=18)
#小于等于
models.User.objects.filter(age__in=(18,28,38))
# 查询年龄是 18 或者 28 或者 38的数据对象
models.User.objects.filter(age__range=(10,20))
# 查询年龄从10到20岁 之间 的数据对象
models.User.objects.filter(name__contains='m')
# 查询name字段中 包含 'm' 的数据对象 不包括大写M
models.User.objects.filter(name__icontains='m')
# 也包含大写的M
models.User.objects.filter(register_time__year=2022)
# 查询时间字段里面 年是 2022的数据对象
ORM表外键字段的创建
外键创建的位置
一对多
外键字段建在多的一方
多对多
外键字段统一建在第三张关系表
一对一
建在任何一方都可以 但是建议建在查询频率较高的表中
关系的判断可以采用换位思考原则 熟练的之后可以瞬间判断
一对多 ORM与MySQL一致 外键字段建在多的一方
多对多 ORM比MySQL有更多变化
1.外键字段可以直接建在某张表中(查询频率较高的)
内部会自动帮你创建第三张关系表
2.自己创建第三张关系表并创建外键字段
详情后续讲解
一对一 ORM与MySQL一致 外键字段建在查询较高的一方
代码实操:
一对多
一本书只可以对应一个出版社 一个出版社可以对应很多本书
publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE)
# 在 书 的表中建立外键 关联 出版社表 在django中会自动关联主键
# on_delete=models.CASCADE 级联删除的意思
多对多
书可以有多个作者 作者也可以有多本书
外键字段名 = models.ManyToManyField(to='关联表名')
# 在 书 表中 建立外键 关联 作者表 ,
# 多对多会自动生成一张表 记录 书对于作者的关系 和 作者对于书的关系
# 针对多对多 不会在表中有展示 而是创建第三张表
一对一
一个作者只可以对应一个信息 一个信息也只可以对应一个作者
author_info = models.OneToOneField(to='Author_info')
# 在作者表中 建立外键 关联 作者信息表
# 一对一外键字段建在查询较高的一方
#针对一对多和一对一同步到表中之后会自动加_id的后缀
外键字段增删改查
一对多 与 一对一的增删改查
在表中插入数据
一对多 与 一对一的增删改查
models.Book.objects.create
(name='西记',price='100',publish_id=1)
# 直接在外键添加实际字段 填写id
增:
publish_obj = models.Publish.objects.filter(pk=2).first()
# 先获取一个出版社对象
models.Book.objects.create
(name='西记',price='100',publish=publish_obj)
# 然后也可以直接把这个对象放入这个外接字段中publish=publish_obj
既可以写实际字段 publish_id='1' 也可以传对象 publish=publish_obj
删:
models.Publish.objects.filter(pk=2).delete()
# 级联删除
改:
models.Publish.objects.filter(pk=2).update(publish_id=2)
models.Publish.objects.filter(pk=2).update(publish=publish_obj)
多对多的增删改查
多对多的增删改查 其实就是针对于第三张表的增删改查
如果给书籍表添加作者
1.先拿到数据对象
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors
# 对象.外键其实已经进入了关系表了
book_obj.authors.add(1)
# 给这个数据对象 绑定一个 id为1的作者
add也可以同时添加多条
book_obj.authors.add(1,3)
# 这样相当于 给书的这个对象 绑定了 1和3 2个id的作者
book_obj.authors.add(author_obj,author_obj2)
# 也支持直接放入一个对象 书的id和这个对象的id会绑定
add是给第三张关系表添加数据 既可以传id 也可以直接传对象
删除:
remove
book_obj.authors.remove(1,3)
# 这样就直接删除了 这个书对于的作者关系
book_obj.authors.remove(author_obj,author_obj2)
# 跟增加一样 也支持直接删除对象
修改:
set
book_obj.authors.set([1,3])
# 把数据对应的作者关系 更改为 这本书 对应作者id为 1和3
# set(括号内必须是可迭代对象 列表 或者 元组)
book_obj.authors.set([author_obj,author_obj2])
# 也支持直接放对象
清空:
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.clear()
# 直接去authors这个表里面取消所有相关的绑定关系
正反向的概念
关键是看 外键字段在哪里
加入由书查出版社
因为外键字段在书内 所以就是正向查询
出版社查书 因为出版社内没有外键字段 就是 反向查询
多表查询
正向查询按字段
反向查询按表名小写
子查询(基于对象的跨表查询)
正向查询
代码演示:
查询书籍id为1的 出版社名称
1、首先要查询到id为1的书籍对象
book_obj = models.Book.objects.filter(pk=1).first()
2. 通过书的对象 查询出版社 属于正向查询 直接按字段查询
book_obj.publish.name
3. 直接通过书籍内关联出版社的外键字段 直接查询
查询书籍主键为1的作者
1、首先要查询到id为1的书籍作者
book_obj = models.Book.objects.filter(pk=1).first()
2. 通过书的对象查询作者属于正向查询 直接可以使用关于作者的外接字段
book_obj.authors.all()
3. 这样就直接拿到了作者对象
查询作者为'moon'的电话号码
1.首先查询到名为 moon 的作者对象
authors_obj = models.Authors.objects.filter(name='moon').first()
authors_obj.info.phone
正反向查询的概念(重要)
正向查询
由外键字段所在的表数据查询关联的表数据 正向
反向查询
没有外键字段的表数据查询关联的表数据 反向
ps:正反向的核心就看外键字段在不在当前数据所在的表中
ORM跨表查询的口诀(重要)
正向查询按外键字段
反向查询按表名小写
反向查询
查询是东方出版社出版的书
拿到东方出版社对象
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
publish_obj.book_set.all()
# 反向查询用表名小写 加_set
查询作者是'moon'写过的书
authors_obj = models.Authors.objects.filter(name='moon').first()
authors_obj.book_set.all()
# 都是先拿到已知的条件获得一个对象 然后判断是反向还是正向
# 正向就直接点外键字段查询 反向就小写表名加_set
'''
当你的查询结果是多个的时候 必须要用 _set.all()
当你的结果直接一个的时候不需要加_set.all()
'''
联表查询(基于双下划线方法的)
正向查询
1.查询作者'moon'的 手机号
authors_obj = models.Authors.objects.filter(name='moon').
values('author_detail__phone')
# 直接在values里面 填写外键字段名 然后直接 __取值
# 反向 models.info.objects.filter(authors__name='moon').valuse(phome)
2.查询数据id为1的出版社名称和书的名称
models.Book.objects.filter(pk=1).values(publish__name,name)
# 反向 models.Publish.objects.filter(book__pk=1).values(name,book__name)
3.查询书籍主键为1的作者姓名
models.Book.objects.filter(pk=1).values(authors__name)
# 反向 models.Authors.objects.filter(book__pk=1).values(name)
ORM聚合查询
1.Max 最大值
2.Min 最小值
3.Sun 求和
4.Count 次数
5.Avg 平均值
未分组查询时需要关键词 aggregate
需要导入模块后才可以使用
from django.db.models import Max,Min,Sun,Count,Avg
eg:查询商品表中所有商品价格的总和
models.Goods.objects.all().aggregate(Sun('price'))
# 统计Goods这个表中所有数据中 字段 price的总和
也可以给统计出来的数据 起名字
models.Goods.objects.all().aggregate(price_sun=Sunprice_sun)
利用变量名代替结果
也可以同时进行多个操作
models.Goods.objects.all().aggregate(Sun('price'),Max('supply'),Min('price'))
# 同时获取 这个表中 不同的字段数据 price字段的和 supply字段的最大值等等
ORM分组查询
分组关键字 annotate()
eg:统计每一本书的作者个数
一般只要需求中带有每个 每一个等词语 都是需要以此来分组比较合适
from django.db.models import Max,Min,Sun,Count,Avg
res = models.Book.objects.annotate
(author_num=Count('author__pk')).values('name','author_num')
# models.后面是什么就是根据什么分组 目前来看是根据Book来分组
# 然后获取分组后 统计出来每本书对应的作者个数
# 最后展示 书的名字 和 对应的作者个数
eg:统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.
annotate(min_price=Min(book__price).valuse('min_price')
# select publish.name, min(book.price) from publish inner join book on
# book.publish_id=publish.id
# group by publish.name having min(book.price)
# 先根据出版社进行分组
# 在分组内查询 最小的图书价格 用变量名 min_price代替
# 最后展示查询结果 min_price
eg:统计不止一个作者的图书
res = models.Author.objects.annotate(author_num=Count(authors))
.filter(author_num__gt=1).values('name','author_num')
# 先根据作者进行分组 求出每一本书对于的作者个数
# 然后在过滤出作者个数大于1的作者对象 最后选择展示内容
'''
只要你手上是一个QuerySet对象 都可以使用 filter方法再次过滤
'''
eg:查询每个作者出的书的总价格
res = models.Author.objects.annotate(book__price=Sun(book__price))
.values('name','book__price')
# 先按照作者分组 然后反向查询书的总价格 最后展示作者名和总价格
F查询与Q查询
**针对字段进行分组 **
F查询
from django.db.models import F,Q
eg:查出卖出数大于库存数的书籍
res = models.Book.objects.filter(maichu__gt=F('kuncun'))
# F('库存') F能够直接帮你获取到某个字段中对应的数据 当做比对条件
eg:将所有书籍的价格提升50元
models.Book.objects.all().update(price=F('price')+50)
eg:将所有书的名称后面加上 打折
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(name=Concat(F('name').Value('打折')))
# F不可以直接做字符串的拼接 如果要做字符串的拼接需要导入 Concat模块
# 设置字符串的拼接 Concat(F('name').Value('打折')
Q查询
from django.db.models import Q
eg:查询卖出数大于100或者价格小于600的书籍
models.Book.objects.filter(Q(maichu__gt=100)|Q(price__lt=600)
# filter(内默认是and关系)
# 可以使用大Q包裹 然后 可以改变条件关系
Q(条件),Q(条件) = and
Q(条件)|Q(条件) = or
~Q(条件)|~Q(条件) = not
# 小波浪号就是not的意思
Q查询进阶用法
使用Q查询可以让字符串与models交互
先通过Q这个类 生成一个对象
q=Q()
然后给这个对象直接添加条件
q.connector = 'or'
q.children.append(('price__lt',888))
q.children.append(('name',888))
models.Book.objects.filter(q)
#在q对象添加条件完毕时 可以直接把整个q筛选条件放入filter中
默认添加条件都是and 但是也可以通过.connector属性进行更改
可以尝试配合模糊查询 编写用户搜索功能
ORM数据库查询优化
orm默认都是惰性查询
如果你没有用到orm语句里面的参数,默认orm觉得你不需要就不执行,只是使用到了数据才会访问数据库
优化:提前把需要的数据一次性都取出来成为一个个对象,之后再使用对象点数据方法取数据时 就不需要再次数据库查询了 检查数据库查询次数
only与defer
如果要实现获取到一个数据对象,并且那个数据对象中只有我们想要获取的字段内容,这时候就需要用到only
'''数据对象+含有指定字段对应的数据'''
res = models.Book.objects.only('title')
res.title
# 通过only可以拿到一个个的数据对象 且对象中只有title字段
# 点击括号内填写的字段 不走SQL查询
models.Book.objects.all()
# 如果只是为了获取某个字段 不推荐这样,这样相当于 拿所有数据,运行速度较慢
获取某个表中字段的所有数据时建议使用 only 可以提高效率 减少代码查询压力
defer 跟only相反
res = models.Book.objects.defer('title')
# 这相当于除了defer括号内的字段不要 其他都要 然后存在对象中
# 点击括号内填写的字段 走SQL查询
select_related与****_related
跟跨表操作有关
res = models.Book.objects.select_related('publish')
select_related('只可以放主表的外键字段,一对一 一对多')
# 先连表后查询封装
# 括号内不支持多对多字段 其他两个都可以
# obj.publish.name 不再走SQL查询
可以内部进行连表操作 对于 book和pubiish表连起来,然后在一次性将所有数据封装给查询出来的对象
所以 当你使用这个对象 再去进行 book表数据和publish表数据查询都不会再走数据库了,因为已经把这两个表数据所有数据都封装给对象了
models.Book.objects.prefetch_ralated('publish')
该方法内部相当于子查询 实际使用了2条sql语句
这个适用于 多对多的表关系
ORM事务操作
事务四大特性
ACID
原子性 一致性 隔离性 持久性
原子性:不可分割的最小单位
一致性:跟原子性相辅相成
隔离性:事务直接数据互相不干扰
持久性:事务一旦确认永久生效
事务的回滚
rollback
事务的确认
commit
在django中如何开启事务
方法一:
直接在settngs文件中 数据库配置中加入
"ATOMIC_REQUESTS":True
这样全局事务开启,每一个视图函数都自带事务功能
如果视图函数中代码没有走到 return 中途出现代码错误则会
回滚值为操作前
方法二:
需要在 settings文件中导入模块
from django.db import transaction
with transaction.atomic():
只要在这个事务的内的子代码都属于找个事务
try:
with transaction.atomic():
# 创建一条订单数据
models.Order.objects.create(num="110110111", product_id=1, count=1)
# 能执行成功
models.Product.objects.filter(id=1).update(kucun=F("kucun")-1, maichu=F("maichu")+1)
except Exception as e:
print(e)
#可以配合错误捕捉 当事务内代码出现错误则报错 并回到事务开启前状态
#不影响任何数据
方式三:
装饰器
from django.db import transaction
@transaction.atomic
def index():pass
# 希望对哪个功能函数启动事务 就装饰到哪个函数上面
ORM中创建表常用字段类型
models文件中使用
1.AutoField
自增 配合 promary_key = True
2.CharField = sql中的 varchar 字符串类型
verbose_name 字段的注释
max_length 字段最大长度
3.IntegerField 整数类型
输入范围最高10位数
4.DecimalField 小数类型
max_digits = 8 设置总位数
decimal_places = 2 设置小数点后位数
5.EmailFiled 邮箱格式
6.DateField = date 年月日
auto_now 只记录每次更新数据的时间
auto_now_add 只记录数据创建的时间
DateTimeField = datetime年月日时分秒
auto_now 只记录每次更新数据的时间
auto_now_add 只记录数据创建的时间
7.models.BooleanField 布尔值
给该字段传入布尔值 字段在数据库存储为 0或1
8.TextField 大段文本
用来储存文章使用 没有字数限制
9.FileField(upload_to='上传文件的路径',storage= None)
属于字符类型 存储的是一个文件路径
给该字段传一个文件对象,会自动将文件对象保存到设置的路径下,然后将文件路径保存到数据库中
自定义字段类型创建一个char字段
在models.py文件中就可以
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
# 这样就相当于创建了一个char字段
class User(models.Model):
name = models.CharField(max_length=32)
info = MyCharField(max_length=64)
# 在创建表的时候就可以直接使用了
ORM创建字段常用参数
null
设置该字段是否可以为空 默认是不可以的
null=True
#可以手动更改为允许为空
name = models.CharField(max_length=18,verbose_name='书名',null=True)
Unique
设置该字段的值是唯一值,不可以重复的
默认为false
nuique=True
#可以手动更改为开启状态
db_index
db_index = True
# 标识给这个字段加上一个辅助索引的属性
# 可以增加字段查询速度
on_delete
设置外键字段的级联更新和级联删除方式
on_delete = models.CASCADE
级联删除 当主表中数据被删除 对应关联的数据也都会被删除
on_delete = models.SET_NULL
models.ForeignKey(to='Publish',on_delete=models.SET_NULL,null=True)
当主表中数据删除 对应关联表格相关字段值变为 null 但是这个字段必须允许为空
主表删除数据 不会删除对应关联的表
models.SET()
当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
choices
针对可以列举完全的可能性字段
例如 学历,工作年限,
gender_choices = ((1,'男'),(2,'女'))
# 大元组 套小元组
gender = models.IntegerField(choices=gender_choices)
# 存储的类型是根据小元组内 第一个数据的类型决定
存数据的时候可以存入 数字编号
如何取choices字段信息
obj.get_gender_display() #'男'
# get_加字段名加_display()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了