目录
django模型层
前期了解知识
1.django自带的sqlite3数据库对时间字段不够敏感,而且功能也少,所以我们习惯切换成常见的数据库
2.对于django,ORM不会自定帮我们创建库,所以需要我们提前准备好库
3.单独测试Django的某个功能层
Django默认是不允许单独测试某个py文件的,如果我们想要测试可以使用下面的两种方法
1.pycharm提供的python console 中写代码测试,但是这个是模拟终端的,写的代码不会保存,建议一些简单的功 能可以在这里测试
2.自己搭建一个测试环境,可以使用自带的test.py文件或者是自己重新创建一个
1.拷贝manage.py前四行代码
2.自己再加两行代码
import django
django.setup()
4.dajngo ORM的底层还是SQL语句,可以查看
1.如果我们拿到的是一个Queryset对象,那么我们可以直接通过点query的方式查看SQL语句
2.如果我们想查看所有ORM底层的SQL语句,可以在配置文件中添加日志记录,之后会将SQL语句打印到pycharm终端上
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
ORM常用关键字
1.create() 创建数据并获取当前创建的数据对象
create是有返回值的,返回的是用户对象
2.filter() 根据条件筛选数据,结果是Queryset [数据对象, 数据对象]
filter在括号内什么都不填的情况下默认查询所有
filter括号内支持多个条件,但是这些条件之间默认都是and关系
3.update() 更新数据(批量更新)
与filter搭配使用,filter查询出来几条数据,update就更改几条数据
4.delete() 删除数据(批量删除),用法与update差不多
5.first() 取第一个数据
Queryset支持索引取值,但是只支持正整数,而且ORM默认不建议使用索引取值
使用索引取值时,值不存在会报错,first不会报错,返回的是None
6.last() 取最后一个数据
7.all() 查询所有数据,结果是Queryset [数据对象,数据对象]
8.values() 根据指定字段获取数据,结果是Queryset [{},{}],列表套字典
9.values_list() 根据指定字段获取数据,结果是Queryset [(),()] 列表套元组
10.distinct() 去重,数据必须完全一致,如果有主键则用不了
11.order_by() 根据指定条件排序,默认是升序,字段前面加负号则是降序
12.get() 根据条件筛选数据,并直接获取数据对象,如果数据不存在则直接报错
13.exclude() 取反
14.reverse() 颠倒顺序,被操作的对象必须是已经做过排序的
15.count() 统计结果集中数据的个数
16.exists() 判断结果集中是否含有数据,如果有则返回True,否则返回False
# 在编程中无论关键字或方法有多好用,只要是容易报错的都尽量少用
ORM执行SQL语句
方式一:raw()方法执行原生SQL语句
models.User.objects.raw('SQL语句') # 使用raw在ORM中直接编写SQL语句,其返回值为Queryset对象
方式二:直接执行SQL语句
from django.db import connection
cursor = connection.cursor()
cursor.execute('SQL语句')
print(cursor.fetchall())
单表查询之双下划线查询
1.查询年龄大于18的用户数据
models.User_info.objects.filter(age__gt=18) # ORM中不支持比较运算符的操作,不能直接写比较运算符
2.查询年龄小于18的用户数据
models.User_info.objects.filter(age__lt=18)
3.大于小于/小于等于
models.User_info.objects.filter(age__gte=18) # 大于等于
models.User_info.objects.filter(age__lte=18) # 小于等于
4.查询年龄是18或者是28或者是38的数据
models.User_info.objects.filter(age__in(18, 28, 38))
5.查询年龄在18到38之内的数据
models.User_info.objects.filter(age__range=(18, 38)) # 包括18和38
6.查询名字中含有字母j的数据
models.User_info.objects.filter(name__contains='j') # 区分大小写,查询不包含大写的字母J
models.User_info.objects.filter(name__icontains='j') # 不区分大小写
7.查询注册年份是2022的数据
models.User_info.objects.filter(register_time__year=2022) # 时间的查询使用register_time,后面双下跟的是我们要查询的时间,如年、月、日等
ORM外键字段的建立
1.创建基础表(书籍表,出版社表,作者表,作者详情表)
书籍表
class Book(models.Model):
name = models.CharField(max_length=32, verbose_name='书名')
# DecimalField表示小数,max_digits最大位数,decimal_places小数点后面的位数
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
出版社表
class Press(models.Model):
"""出版社表"""
name = models.CharField(max_length=32, verbose_name='出版社名称')
address = models.CharField(max_length=64, verbose_name='出版社地址')
作者表
class Author(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='作者')
age = models.IntegerField()
作者详情表
class AuthorDetail(models.Model):
"""作者详情表"""
phone = models.BigIntegerField(verbose_name='电话')
address = models.CharField(max_length=64, verbose_name='家庭地址')
2.确定外键关系
一对多: ORM与SQL一样外键字段健在多的一方
多对多:
1.ORM外键字段可以直接建在查询频率较高的表中,内部会自动帮我们创建第三张关系表
2.SQL则是我们自己创建第三张关系表并创建外键字段
一对一: ORM与SQL一致,外键字段建在查询频率较高的一方
3.ORM创建
书籍表与出版社表
一个书籍只能被一个出版社出版,一个出版社可以从出版多本书籍,为一对多关系,外键字段建在书籍表中
press = models.ForeignKey(to='Press', on_delete=models.CASCADE)
书籍表与作者表
一本书籍可以有多个作者,一个作者可以写多本书,为多对多关系,书籍表使用频率较高,外键字段建在书籍表中
authors = models.ManyToManyField(to='Author')
作者表与作者详情表
一名作者只能有一个详情,一个详情只能对应一名作者,为一对一关系,外键字段建在作者表中
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)
外键字段相关操作
1.普通表录入数据
# 建议录数据时先从没有外键字段的表开始
2.针对一对多
1.插入数据可以填写表中实际的字段
models.Book.objects.create(name='雪中悍刀行', price=68, press_id=4)
2.也可以填写表里面的类字段名
press_obj = models.Press.objects.filter(pk=4).first()
models.Book.objects.create(name='完美世界', price=66, press=press_obj)
3.针对多对多
1.直接绑定
book_obj = models.Book.objects.filter(pk=2).first()
book_obj.authors.add(1) # 在第三张关系表中给当前书籍绑定作者
2.绑定对象
author_obj1 = models.Author.objects.filter(pk=2).first()
author_obj2 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(author_obj1, author_obj2) # 也可以添加作者对象
3.修改关系
book_obj.authors.set((1,))
# set内使用列表也行,内部填写的表示当前绑定的作者,同样也可以使用作者对象
4.删除关系
book_obj.authors.remove(2, 3) # 在当前书籍绑定的作者中删除2,3 同样也可以使用作者对象
5.清空关系
book_obj.authors.clear() # 清空当前书籍绑定的所有作者
多对多的三种创建方式
1.全自动创建
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
class Author(models.Model):
name = models.CharField(max_length=32)
优势:自动创建第三张表,并且提供了add,remove,set,clear四种操作方法
劣势:第三张表无法创建更多的字段,扩展性较差
2.纯手动创建
class Book(models.Model):
title = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Author')
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
优势:第三张表是自己创建的,想怎么样就怎么样扩展性强
劣势:编写较为繁琐,且不支持add,remove,set,clear四种操作方法,也不支持正反向的概念
3.半自动创建
class Book(models.Model):
title = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author', # 与哪张表建立多对多关系
through='Book2Author', # 哪张表是用来维护多对多关系的
through_fields=('book','author') # 通过哪两个字段来维护多对多的外键关系,字段的顺序是外键字段写在哪张表中哪个字段就写在前面
)
class Author(models.Model):
name = models.CharField(max_length=32)
class Book2Author(models.Model):
book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
others = models.CharField(max_length=32)
join_time = models.DateField(auto_now_add=True)
优势:第三张表也是完全由自己创建,扩展性强,正反向概念依旧清晰可用
劣势:编写繁琐,且不再支持add,remove,set,clear四种操作方法
跨表查询
正反向查询的概念
正向查询
从有外键字段的表查询关联表的数据
反向查询
从没有外键字段的表查询关联表的数据
核心就是看外键字段
口诀
正向查询按外键字段
反向查询按表名小写
基于对象的跨表查询
1.查询主键为2的书籍对应的出版社名称
先根据条件获取对象
book_obj = models.Book.objects.filter(pk=2).first()
再判断正反向的概念
print(book_obj.press.name)
2.查询主键为2的书籍对应的作者姓名
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.authors) # Myapp.Author.None
print(book_obj.authors.all()) # 作者可能有多个所以需要加all
3.查询辰东的电话
author_obj = models.Author.objects.filter(name='辰东').first()
print(author_obj.author_detail.phone)
4.查询人民出版社出版过的书籍
press_obj = models.Press.objects.filter(name='人民出版社').first()
print(press_obj.book_set.all()) # 查询结果可能存在多个所以加all
5.查询辰东写过的书籍
author_obj = models.Author.objects.filter(name='辰东').first()
print(author_obj.book_set.all()) # 查询结果可能存在多个所以加all
6.查询电话是15855010170的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=15855010170).first()
print(author_detail_obj.author.name)
总结:
查询的结果可能为多个时加all()
基于双下划线的跨表查询
1.查询主键为2的书籍对应的出版社名称
res = models.Book.objects.filter(pk=2).values('press__name')
# values中直接写press表示进入到出版社表中,然后再使用双下name即可得到对应的出版社名称
print(res)
2.查询主键为2的书籍对应的作者姓名
res = models.Book.objects.filter(pk=2).values('authors__name')
print(res)
3.查询辰东的电话
res = models.Author.objects.filter(name='辰东').values('author_detail__phone')
print(res)
4.查询人民出版社出版过的书籍
res = models.Press.objects.filter(name='人民出版社').values('book__name')
print(res)
5.查询辰东写过的书籍
res = models.Author.objects.filter(name='辰东').values('book__name')
print(res)
6.查询电话是15855010170的作者姓名
res = models.AuthorDetail.objects.filter(phone=15855010170).values('author__name')
print(res)
进阶操作
1.查询主键为2的书籍对应的出版社名称
res = models.Press.objects.filter(book__pk=2).values('name')
# values之前的意思是查询出版了书籍主键为2的出版社
print(res)
2.查询主键为2的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=2).values('name')
print(res)
3.查询辰东的电话
res = models.AuthorDetail.objects.filter(author__name='辰东').values('phone')
print(res)
4.查询人民出版社出版过的书籍
res = models.Book.objects.filter(press__name='人民出版社').values('name')
print(res)
5.查询辰东写过的书籍
res = models.Book.objects.filter(author__name='辰东').values('name')
print(res)
6.查询电话是15855010170的作者姓名
res = models.Author.objects.filter(author_detail__phone=15855010170).values('name')
print(res)
7.查询主键为2的书籍对应的作者的电话
写法一:
res = models.Book.objects.filter(pk=2).values('authors__author_detail__phone')
print(res)
写法二:
res = models.AuthorDetail.objects.filter(author__book__pk=2).values('phone')
print(res)
写法三:
res = models.Author.objects.filter(book__pk=2).values('author_detail__phone')
print(res)
聚合查询
ORM中支持单独使用聚合函数,但是不能直接使用,需要用aggregate模块
我们在使用聚合函数之前需要先导入聚合函数
from django.db.models import Max, Min, Sum, Avg, Count
res = models.表名.onjects.aggregate(别名=Max('字段名'), 别名=Min('字段名'), 别名=Sum('字段名'), 别名=Avg('字段名'), 别名=Count('字段名'))
聚合查询也支持使用别名
分组查询
分组查询的关键字为annotate,默认是以表为分组单位
以之前导入的图书相关的四个表为例
1.统计每一本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk'))
# 以图书表为分组,统计每本书的作者数
print(res)
2.统计出每个出版社卖的最便宜的书的价格
res = models.Press.objects.annotate(min_price=Min('book__price')).values('min_price')
print(res)
3.统计不止一个作者的图书
res = models.Book.objects.annotate(book_authors=Count('authors__pk')).filter(book_authors__gt=1).values('name')
print(res)
4.查询每个作者出的书的总价格
res = models.Author.objects.annotate(all_price=Sum('book__price')).values('name', 'all_price')
print(res)
注意:
在执行ORM分组时可能会报错,并且有关键字sql_mode strict mode
需要移除sql_mode中的only_full_group_by
操作步骤
1.以管理员模式登录Mysql,进入到当前使用的库
2.执行set global sql_mode='strict_trans_tables';
F与Q查询
F查询为当前查询条件不明确,也需要从数据库中获取时使用的
Q查询为执行条件之间的关系为与或非时使用的
from django.db.models import F, Q
F查询
1.查询库存数大于卖出数的书籍
res = models.Book.objects.filter(repertory__gt=F('sale'))
print(res)
2.将所有书的价格涨800
models.Book.objects.update(price=F('price') + 800) # update只能接收一个传值
3.将所有书的名称后面追加爆款
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.filter(pk=10).update(name=Concat(F('name'), Value('(爆款)'))) # ORM中不能直接做字符串的拼接,需要使用Concat
Q查询
查询主键是2或者价格大于900的书籍
res = models.Book.objects.filter(Q(pk=2) | Q(price__gt=900))
for i in res:
print(i.name)
Q查询进阶操作
Q查询除了可以将多个查询条件之间的关系做修改还可以直接作为查询条件
from django.db.models import Q
q_obj = Q() # 产生Q对象
q_obj.connector = 'or' # 修改多个条件的连接关系,默认是and关系
q_obj.children.append(('pk', 1)) # 添加查询条件,支持添加多个,需要一个一个写,不能一次性添加
res = models.Book.objects.filter(q_obj) # 查询时直接填写Q对象
print(res)
ORM查询优化
1.ORM的查询默认都是惰性查询
# 查询语句在编写完成以后,如果后续的代码中没有用到则查询语句暂不执行
2.ORM的查询自带分页处理
# 为了防止查询出来的的结果较多一次性全部涌出,ORM会自动对查询出来的结果做分页处理
3.only与defer # 数据对象和含有指定字段对应的数据
res = models.Book.objects.only('name', 'price') # 生成一个Queryset对象
for obj in res: # 对Queryset内的数据对象做for循环,only查询到的数据封装在数据对象内
print(obj.name) # 点一个only括号内填写的字段不走SQL查询,直接从数据对象内调用
print(obj.press_time)
# 点一个括号内没有的字段获取数据,走SQL查询,数据对象内没有需要现查‘
res = models.Book.objects.defer('name', 'price')
for obj in res:
print(obj.name) # 点括号内填写的字段需要重新走一遍SQL查询,数据对象内没有
print(obj.press_time)
# 点括号内没有的字段,数据对象内有,不需要重新走一遍SQL查询
4.select_related与prefetch_related
res = models.Book.objects.all()
for obj in res:
print(obj.press.name) # 正常情况下我们点一个表内没有的数据,每次查询都需要走一次SQL
res = models.Book.objects.select_related('press') # 先连表后封装查询
# 先连表然后将连表得到的数据表封装进数据对象中
for obj in res:
print(obj.press.name) # 每次执行只是从数据对象中获取数据,不再走SQL查询
res = models.Book.objects.prefetch_related('press') # 子查询
for obj in res:
print(obj.press.name)
select_related的作用就类似于连表操作,一个SQL语句就搞定了
prefetch_related的作用就相当于子查询,需要执行两个SQL语句
ORM事物操作
1.事物的四大特性(ACID)
原子性、一致性、隔离性、持久性
2.相关SQL关键字
start transaction 开启事务
rollback 事物回滚
commit 提交
savepoint 回到某一个点
3.事物相关的一些重要概念
脏读、幻读、不可重复读、MVCC多版本控制
Django中提供了至少三种开启事物的方式
1.配置文件中数据库添加键值对 全局有效
"ATOMIC_REQUESTS": True 每次请求涉及到的ORM操作同属于一个事物
2.装饰器 局部有效
from django.db import transaction
@transaction.atomic
def index():pass
3.with上下文管理 局部有效
from django.db import transaction
def reg():
with transaction.atomic():
pass
ORM常用字段类型
AutoField # 创建主键使用的 必需参数 primary_key=True
CharField # 创建的字段类型为字符类型 必需参数 max_length 创建出来的字段类型为varchar
IntegerField # 创建的字段类型为整型
BigIntegerField
DecimalField # 创建的字段类型为小数 必需参数 max_digits总位数 decimal_places小数位数
DateField # 创建的字段类型为日期类型 年-月-日
DateTimeField # 时间类型 年月日时分秒
BooleanField # 传布尔值自动存0或1
TextField # 存储大段文本
EmailField # 邮箱格式
FileField # 传文件对象,自动保存到提前配置好的路径下并存储该路径信息
除了上面这些ORM自带的字段类型我们还可以自定义字段类型
自定义Char字段类型
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
ORM常用字段参数
primary_key 主键
verbose_name 注释
max_length 字段长度
max_digits 小数类型字段的总位数
decimal_places 小数点后的位数
auto_now 每次操作数据都会自动更新时间
auto_mow_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)
# 如果gender拿到的数字在gender_choice中则可以拿到列举的对应关系,否则拿到什么就是什么
user_obj = models.User.objects.filter(pk=1).first() # 拿到主键值为1的数据对象
user_obj.gender # 拿到的还是数字
user_obj.get_gender_display() # 拿到的是对应关系,如果没有对应关系拿到的还是数字本身
to 关联表
to_field 关联字段(不写默认是关联主键)
on_delete 删除表中的数据时,关联表行为
on_delete共有六个参数
1.models.CASCADE
级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
2.models.SET_NULL
当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段全都设置为null,使用时需要注意该字段必须允许为空
3.models.PROTECT
当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
4.models.SET_DEFAULT
当主表中的一行数据删除时,从表中所有相关数据的关联字段设置为默认值,定义外键时该外键字段应该有一个默认值
5.models.SET()
当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,从表中相关字段不需要设置
6.models.DO_NOTHING
什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似