Django之模型层
测试环境搭建
需求:需要练习django中的ORM操作,但django只支持通过网页去调后台函数,如果我们想在一个py文件中单独测试sql效果时,就需要使用tests.py了。
配置方法:
# 1. 打开manage.py,将下面几行代码粘贴至tests.py文件中
import os
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject2.settings')
# 2. 再添加两行代码
import django
django.setup()
# 3. 注意以上顺序不能动。之后就可以进行使用,比如要测试ORM时,可以导入models.py
from app01 import models
# 这里是内容区域
# 4. 所有测试内容都写在main()函数中所以需要再在最下面调用一下这个函数
main()
查看ORM执行的SQL的方法
方法一:
如果值为一个QuerySet对象,可以直接以点的方式,使用query方法查看SQL语句
缺点:不是QuerySet对象就无法查看SQL,比如create返回值就不是QuerySet对象
res = models.User.objects.create(name='Jack', age='15')
# 返回值为:<QuerySet [<User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>]>,可以确认为一个QuerySet
print(res.query)
# 返回的SQL
'''
SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_date` FROM `app01_user` WHERE (`app01_user`.`age` = 15 AND `app01_user`.`name` = Jack)
'''
# 测试非QuerySet对象使用
res = models.User.objects.create(name='Rose', age=14)
print(res) # 返回:用户名>>>:Rose
print(res.query) # 报错:AttributeError: 'User' object has no attribute 'query'
方法二:
如果要查看所有ORM底层的SQL语句,可以在配置文件中添加日志记录
需要在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',
},
}
}
之后所有的ORM操作都会打印日志
res = models.User.objects.create(name='Maria', age=20)
print(res)
'''
返回结果为:
用户名>>>:Maria
(0.001) SELECT @@SQL_AUTO_IS_NULL; args=None # 这个是准备工作,不需要看
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None # 这个是准备工作,不需要看
(0.002) INSERT INTO `app01_user` (`name`, `age`, `register_date`) VALUES ('Maria', 20, '2023-03-07 03:15:14.589445'); args=['Maria', 20, '2023-03-07 03:15:14.589445'] # 返回的SQL语句
'''
ORM执行SQL语句
当ORM执行效率较低时,可以直接直接使用SQL语句进行操作。
方法一:
res = models.User.objects.raw('select * from app01_user;')
print(list(res)) # 或者使用for循环
方法二:
# 其实使用的pymysql中的方法,django封装了一下,其它方法可以参考pymysql的方法。
from django.db import connection
cursor = connection.cursor()
cursor.execute('select * from app01_user;')
print(cursor.fetchall())
其它方法还有extra等方法,可以自行查询
ORM常用字段
AutoField
int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列。
IntegerField
一个整数类型,范围在 [-2147483648 to 2147483647]。(一般不用它来存手机号(位数也不够),直接用字符串存)
BigIntegerField
整数类型,范围在 [-9223372036854775808,9223372036854775807]
DecimalField
浮点数类型,注意max_digits中是包含decimal_places的,所以max_digits必须大于decimal_places
参数:
max_digits=10 # 定义一共10位
decimal_places=2 # 小数点后占2位
CharField
字符类型,必须提供max_length参数, max_length表示字符长度。
在Django中CharField对应在mysql中表示varhchar类型,Django中没有char类型,可以通过自定义来定义一个char类型。
DateField
日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
配置上auto_now=True,每次更新数据记录的时候会更新该字段。
DateTimeField
日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
配置上auto_now=True,每次更新数据记录的时候会更新该字段。
BooleanField
传入布尔值,True会以数字1的形式保存在数据库中,False会以数字0的形式保存到数据库中
TextField
存储大段文本,比如文章内容。
EmailField
存储邮箱字段,本质还是CharField
IPAddressField
存储IP字段,本质还是CharField
FileField
上传文件后,会自动保存到提前配置好的路径下并存储该路径信息
- 将路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" # 上传文件的保存路径
storage = None # 存储组件,默认django.core.files.storage.FileSystemStorage
ImageField
与FileField类似,只是多两个参数
upload_to = "" # 上传文件的保存路径
storage = None # 存储组件,默认django.core.files.storage.FileSystemStorage
width_field_None # 上传图片的高度保存的数据库字段名(字符串)
height_field=None # 上传图片的宽度保存的数据库字段名(字符串)
字段合集:
AutoField(Field)
- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models
class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)
class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767
IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647
BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
BooleanField(Field)
- 布尔值类型
NullBooleanField(Field):
- 可以为空的布尔值
CharField(Field)
- 字符类型
- 必须提供max_length参数, max_length表示字符长度
TextField(Field)
- 文本类型
EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"
URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL
SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字
UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)
DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field)
- 浮点型
DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
BinaryField(Field)
- 二进制类型
ORM字段与MySql字段对应关系:
对应关系:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
自定义字段类型
from django.db import models
# Create your models here.
#Django中没有对应的char类型字段,但是我们可以自己创建
class FixCharField(models.Field):
'''
自定义的char类型的字段类
'''
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):
'''
限定生成的数据库表字段类型char,长度为max_length指定的值
'''
return 'char(%s)'%self.max_length
#应用上面自定义的char类型
class Class(models.Model):
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=32)
class_name=FixCharField(max_length=16)
gender_choice=((1,'男'),(2,'女'),(3,'保密'))
gender=models.SmallIntegerField(choices=gender_choice,default=3)
ORM常用字段参数
primary # 主键
verbose_name # 注释
max_length # 最大长度
max_digits # 小数总共多少位
decimal_places # 小数点后面的位数
auto_now # 每次更新数据记录的时候会更新该字段
auto_now=True
auto_now_add # 创建数据记录的时候会把当前时间添加到数据库
auto_now_add=True
null # 允许字段为空
default # 字段默认值
unique # 唯一值
unique=True
db_index # 为该字段添加索引
db_index=True
choices # 当一个字段的可能性能够被列举完的情况下使用,比如:姓名、学历、工作状态
比如:
class User(models.Model)
name = models.CharField(max_length=64)
gender_choice = (
(1, 'Man'),
(2, 'Woman'),
(3, 'Other'),
)
gender = models.IntegerField(choices=gender_choice)
使用:
数据库中存储对应字段为数字,1、2、3、4等等,获取时使用:
user_obj = models.User.objects.filter(pk=1)
user_obj.gender # 此时结果为数字 1
user_obj.get_gender_display() # 此时会获取对应数字的姓别
# 注意:如果没有指定对应关系,不会报错,会获取对应数字
# get_字段名_display() 这个为固定写法
- 外键字段
to # 设置要关联的表
to_field # 设置要关联的表的字段(不写默认关联数据主键)
on_delete # 当删除关联数据,与之关联的也删除
models.CASCADE
# 级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
models.SET_NULL
# 当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
models.PROTECT
# 当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
models.SET_DEFAULT
# 当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
models.SET()
# 当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
models.DO_NOTHING
# 什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似
ORM建表操作
# 1. 在models.py中创建表的class
class User(models.Model):
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
register_date = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)
# 函数作用为:在调用时会返回self.name字段对应的用户姓名,比较方便看懂
def __str__(self):
return f'用户名>>>:{self.name}'
# 2. 在项目根目录下(与manage.py目录同级目录),执行下面两个命令:
python manage.py makemigrations
python manage.py migrate
ORM新增数据
比如给User表添加数据
res = models.User.objects.create(name='Jack', age='15')
print(res) # 返回值为:用户名>>>:Jack
ORM常用操作
filter过虑查询(QuerySet)
- 根据条件筛选内容,返回一个QuerySet,显示为 [数据对象1, 数据对象2, 数据对象N] ,也就是列表套数据对象。
- 根据上面的结论得出,列表无法以点的方式来作进一步操作,只有数据对象才可以。比如:
# filter括号内不传任何内容为查询所有
res = models.User.objects.filter()
print(res)
# 返回值:<QuerySet [<User: 用户名>>>:jerry>, <User: 用户名>>>:tom>, <User: 用户名>>>:Jack>]>
print(res.age) # 报错:AttributeError: 'QuerySet' object has no attribute 'age'
print(res[0].age) # 正常返回
filter之条件查询
- filter中括号内可以添加多个条件,条件之间默认为and关系。不添加条件为查询所有
# 查询所有内容,与all()相同
res = models.User.objects.filter()
# 按条件查询
res = models.User.objects.filter(name='jerry', age=5)
print(res) # 返回:<QuerySet [<User: 用户名>>>:jerry>]>
filter之first与last
- Query支持索引取值,但只支持正数,且orm不建议使用索引取值
- 建议使用first()与last()
res = models.User.objects.filter().first() # 取查询结果的第一个值
res = models.User.objects.filter().last() # 取查询结果的最后一个值
# 索引取值
res = models.User.objects.filter()
print(res[0])
- 与索引取值的区别为,当索引取值范围超过了列表后,会直接报错,而使用first与last就不会
# 索引取值
res = models.User.objects.filter(pk=20) # 库里面就3条数据,查询结果为空列表
print(res[0]) # 报错:IndexError: list index out of range
# first与last取值
res = models.User.objects.filter(pk=20)
print(res.first()) # 返回None
print(res.last()) # 返回None
all()(QuerySet)
- 查询所有数据,返回一个QuerySet,显示为 [数据对象1, 数据对象2, 数据对象N] ,也就是列表套数据对象。
res = models.User.objects.all()
# 结果为<QuerySet [<User: 用户名>>>:jerry>, <User: 用户名>>>:tom>, <User: 用户名>>>:Jack>]>
values()
- 根据指定字段获取数据
- 返回结果为QuerySet,且为列表套字典的形式
res = models.User.objects.all().values('age')
# 返回值为:<QuerySet [{'age': 5}, {'age': 5}, {'age': 15}]>
res = models.User.objects.values('age')
# 返回值下上面相同
res = models.User.objects.filter(name='jerry').values()
返回值:<QuerySet [{'id': 1, 'name': 'jerry', 'age': 5, 'register_date': datetime.datetime(2023, 3, 7, 2, 9, 7, 448918, tzinfo=<UTC>)}]>
values_list()
- 可以指定字段获取数据
- 返回结果为QuerySet,且为列表套元组的形式
res = models.User.objects.all().values_list()
# 返回结果:<QuerySet [('jerry', 5), ('tom', 5), ('Jack', 15), ('Jack', 15), ('Rose', 14), ('Maria', 20)]>
distinct()(QuerySet)
- 去重,所有数据值需要一模一样,如果有主键肯定不行(因为主键必须唯一),比如
如上图中,索引3、4、7中,姓名均一致,但3与其他两个的register_date不一致,其余两个除索引外均一致
res = models.User.objects.filter(name='Jack')
# 返回值:<QuerySet [<User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>]>
res = models.User.objects.filter(name='Jack').distinct()
# 返回值:<QuerySet [<User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>]>
# 可以看到,上面的去重并未生效,因为QuerySet中是包含主键值的(想一下它为什么可以点出任何字段来)
# distinct()正确的用法
res = models.User.objects.filter(name='Jack').values('name', 'age').distinct()
# 返回值:<QuerySet [{'name': 'Jack', 'age': 15}]>
order_by()(QuerySet)
- 根据指定条件排序,默认为升序排序,如果需要使用降序排序,需要在排序字段前加负号
res = models.User.objects.all().order_by('name') # 按一个条件升序排序
res = models.User.objects.all().order_by('name', 'age') # 指定多个条件升序排序
res = models.User.objects.all().order_by('-age') # 指定一个条件降序排列
res = models.User.objects.all().order_by('name', '-age') # 升序与降序可以混合使用
count()
- 统计数据集中数据的个数,属于QuerySet方法
res = models.User.objects.all().count()
冷门方法
get()
- 根据条件筛选数据并直接获取到数据对象,条件一旦不存在,直接报错!!!!
res = models.User.objects.get(pk=1)
# 返回结果:用户名>>>:jerry
res = models.User.objects.get(pk=99999)
# 直接报错
exclude()(QuerySet)
- 排除指定条件的数据,相当于取反操作,支持多个条件,默认为and关系
res = models.User.objects.exclude(name='Jack') # 单个条件
# 返回结果为除了name='Jack'以外的所有数据
res = models.User.objects.exclude(name='Jack', age=15) # 多个条件
reverse()(QuerySet)
- 对排序取反操作,必须在排序之后再使用才有效
res = models.User.objects.all().order_by('name').reverse()
# 这时会以降序进行排列
exists
- 判断结果集中是否存在数据,如果有返回True,如果没有返回False
注意:在python中所有数据自带布尔值,没有必要转换为True或False
res = models.User.objects.filter(pk=1).exists() # 返回True
res1 = models.User.objects.filter(pk=100).exists() # 返回False
双下划线查询
使用方法:
字段名__方法,如下:
# 如查找大于5岁的用户
res = models.User.objects.filter(age__gt=5)
- 具体常用方法介绍
方法 | 释义 | SQL |
---|---|---|
__gt | 大于 | where xxx > x; |
__lt | 小于 | where xxx < x; |
__gte | 大于等于 | where xxx >= x; |
__lte | 小于等于 | where xxx <= x; |
__in | 成员运算,类似于mysql中的in | where xxx in (x, y, j); |
__range | 取一个范围 | where xxx between xxx; |
__contains | 包含(比如包含名字中有‘a’字母,注意:区分大小写) | where xxx like BINARY '%a%'; |
__icontains | 包含(不区分大小写) | where xxx like '%a%'; |
__year | 日期字段按年查询(比如查询2023年的数据) | BETWEEN '2023-01-01 00:00:00' AND '2023-12-31 23:59:59.999999' |
__month | 日期字段按月查询(注意:不管年份,只管月份了,注意时区) | WHERE EXTRACT(MONTH FROM CONVERT_TZ(app01_user .register_date , 'UTC', 'Asia/Shanghai')) = 3 |
__day | 日期字段按日查询 | |
__hour | 日期字段按时查询 | |
__minute | 日期字段按分查询 | |
__second | 日期字段按秒查询 |
ORM外键字段创建
说明
- 外键字段关系有三种:
1. 一对一 外键字段建在查询频率多的表中 在ORM中会自动添加_id的后缀
2. 一对多 外键字段建在多的表 在ORM中会自动添加_id的后缀
3. 多对多 外键字段建在第三张关系表 在ORM中不会在表中展示字段,会创建第三张表
创建三种外键字段语句
- 创建外键表示例
class Book(models.Model):
"""图书表"""
title = models.CharField(max_length=32, verbose_name='书名')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')
"""一个出版社可以对应多本书,所以出版社与图书属于一对多关系,一对多关系将字段建在多的一方"""
"""创建书籍与出版社的一对多外键字段"""
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
"""注意:Django1.x版本,外键字段默认都为级联更新删除,Django2.x版本及以上,需要自己申名"""
"""
1. 一本书可以有多个作者,一个作者也可以有多本书,所以属于多对多关系。
2. 对于多对多关系,需要将表建在第三张关系表中。在ORM中,可以将外键字段创建到查询频率高的表中,django内部会自动帮你创建第三张关系表
3. 多对多关系属于虚拟字段,用于让ORM自动创建第三张表,不会在表中创建对应字段
"""
authors = models.ManyToManyField(to='Author')
def __str__(self):
return f'书籍对象>>>:{self.title}'
class Publish(models.Model):
"""出版社表"""
name = models.CharField(max_length=32, verbose_name='出版社名称')
address = models.CharField(max_length=64, verbose_name='出版社地址')
def __str__(self):
return f'出版社对象>>>:{self.name}'
class Author(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
"""一个作者只能对应一个作者详情,属于一对一关系"""
"""创建一对一关系字段"""
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=True)
def __str__(self):
return f'作者对象>>>:{self.name}'
class AuthorDetail(models.Model):
"""作者详情"""
phone = models.CharField(max_length=11, verbose_name='手机号')
address = models.CharField(max_length=64, verbose_name='家庭住址')
外键字段的增删改查
添加示例数据
# 1. 添加基础数据
models.AuthorDetail.objects.create(phone=1111, address='河北')
models.AuthorDetail.objects.create(phone=2222, address='河南')
models.Author.objects.create(name='Jerry', age=18, author_detail_id=1)
models.Author.objects.create(name='Tom', age=28, author_detail_id=2)
models.Publish.objects.create(name='北方出版社', address='北京')
models.Publish.objects.create(name='东方出版社', address='东京')
models.Publish.objects.create(name='南方出版社', address='南京')
# 2. 添加外键字段数据(一对多)
# 针对一对多,插入数据可以直接写表中的实际字段
models.Book.objects.create(title='三国演义', price=888.88, publish_id=1)
models.Book.objects.create(title='红楼梦', price=666.77, publish_id=2)
models.Book.objects.create(title='西游记', price=999.55, publish_id=1)
models.Book.objects.create(title='围城', price=888.66, publish_id=2)
models.Book.objects.create(title='人性的弱点', price=777.55, publish_id=1)
# 针对一对多,插入数据也可以写表所属类的字段名,值可以使用查询后的对象
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.crfeate(title='水浒传', price=555.66, publis=publish_obj)
# 3. 添加外键字段数据(多对多)
# 先拿到书籍对象,这里别忘记first()
book_obj = models.Book.objects.filter(pk=1).first()
# 再添加数据,给当前这本书绑定一个ID为1的作者
book_obj.authors.add(1)
book_obj.authors.add(2, 3) # 也可以一次性绑定多个关系
# 修改对应关系,注意需要使用元组/字典等(需要可以被for循环)
book_obj.authors.set((1, 2))
# 删除一个对应关系(其实也可以使用set)
book_obj.authors.remove(1) # 也可以一次性删除多个关系
# 清除一个对应关系
book_obj.authors.clear() # 将book_obj对应的关系全部清除
ORM跨表查询
正向查询
外键字段在表A中,由表A需要查询表B中的数据,这时称为正向查询
由外键字段所在的表数据查询关联的表数据,称为正向查询
反向查询
外键字段在表A中,由表B需要查询表A中的数据,这时称为反向查询
没有外键字段所在的表数据查询关联的表数据,称为反向查询
总结:
正反向的核心就是看外键字段在不在当前数据所在的表中
在ORM中:
正向查询按外键字段
反向查询按表名小写
基于对象的跨表查询
正向查询练习题:
# 1. 查询主键为1的书籍对应的出版社名称
# 1.1. 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=1).first()
# 1.2. 判断是正反向查询,由书查出版社,外键字段在Book表中,所以为正向查询。
print(book_obj.publish.name)
# 2. 查询主键为4的书籍对应的作者姓名
# 2.1. 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=4).first()
# 2.2. 判断是正反向查询,由书查作者,外键字段在Book表中,所以为正向查询
print(book_obj.authors) # 结果:app01.Author.None
# 2.3. 因为是多对多关系,所以需要使用下面的方法查询
print(book_obj.authors.all())
# 3. 查询Jerry的电话号码
# 3.1. 先根据条件获取数据对象
author_obj = models.Author.objects.filter(name='Jerry').first()
# 3.2. 判断正反向查询,由作者查作者详情,外键字段在作者表中,所以为正向查询
print(author_obj.author_detail.phone)
反向查询练习题:
# 1. 查询北方出版社出现过的书籍名称
# 1.1. 先根据条件获取数据对象
publish_obj = models.Publish.objects.filter(name='北方出版社').first()
# 1.2. 判断正反向查询,由出版社查询书籍,外键字段在书籍表中,所以为反向查询
print(publish_obj.book_set.all()) # 由于返回结果是多个,所以需要使用_set和all()方法
# 2. 查询Jerry写过的书籍
# 2.1. 先根据条件获取数据对象
author_obj = models.Author.objects.filter(name='Jerry').first()
# 2.2. 判断正反向查询,由作者查询书籍,外键字段在书籍表中,所以为反向查询,且一个作者可以写多本书,所以需要使用_set和all()方法
print(author_obj.book_set.all().values('title'))
# 3. 查询电话是1111的作者姓名
# 3.1. 先根据条件获取数据对象
author_detail_obj = models.AuthorDetail.objects.filter(phone=1111).first()
# 3.2. 判断正反向查询,由电话查询作者,外键字段在作者表中,所以为反向查询,且一个作者详情只对应一位作者,所以不需要使用_set和all()方法
print(author_detail_obj.author.name)
# 总结:
'''
如果查询结果为多个的时候,需要使用 表名_set.all()方法
如果查询结果为一个的时间,不需要使用 表名_set.all()方法
'''
基于双下划线的跨表查询
基于双下划线的正向查询
- 这种方法与上面的正反向查询一样,但写法更简单
练习:
# 1. 查询主键为1的书籍对应的出版社名称
# 1.1. 判断为正反查询后,只需要在values中写好出 版社表__字段名 即可
res = models.Book.objects.filter(pk=1).values('publish__name')
# 2. 查询主键为4的书籍对应的作者姓名
# 2.1. 这里拿到了两个字段,title是book表中的书籍名称,再拿到对应的作者姓名
res = models.Book.objects.filter(pk=4).values('title', 'authors__name')
# 3. 查询Jerry的电话号码
res = models.Author.objects.filter(name='Jerry').values('author_detail__phone')
基于双下划线的反向查询
练习:
# 1. 查询北方出版社出现过的书籍名称和价格
res = models.Publish.objects.filter(name='北方出版社').values('book__title', 'book__price')
# 2. 查询Jerry写过的书籍
res = models.Author.objects.filter(name='Jerry').values('book__title')
# 3. 查询电话是1111的作者姓名
res = models.AuthorDetail.objects.filter(phone='1111').values('author__name')
正反向查询的进阶操作
练习:
# 1. 查询主键为1的书籍对应的出版社名称
# 1.1. 查询了出版了主键为1的出版社名称
res = models.Publish.objects.filter(book__pk=1).values('name')
# 2. 查询主键为4的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=4).values('name')
# 3. 查询Jerry的电话号码
res = models.AuthorDetail.objects.filter(author__name='Jerry').values('phone')
# 4. 查询北方出版社出现过的书籍名称和价格
res = models.Book.objects.filter(publish__name='北方出版社').values('title', 'price')
# 5. 查询Jerry写过的书籍
res = models.Book.objects.filter(authors__name='Jerry').values('title')
# 6. 查询电话是1111的作者姓名
res = models.Author.objects.filter(author_detail__phone=1111).values('name')
# 7. 查询主键为3的书籍对应的作者电话号码(跨三张表查询)
# 7.1. 正向查询
res = models.Book.objects.filter(pk=4).values('authors__author_detail__phone')
# 7.2. 反向查询
res = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
# 7.3. 正反向查询混用
res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')
多对多关系的三种创建方式
- 全自动创建
- 优点:
- 自动创建第三张表
- 提供了add、remove、set、clear四种操作
- 缺点:第三张表无法创建更多字段,扩展性差
全自动创建就是上面我们做练习时使用的方式,代码如下:
class Book(models.Model):
"""书籍表"""
title = models.CharField(max_length=64, verbose_name='书籍名称')
authors = models.ManyToManyField(to='Author')
class Authors(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='作者姓名')
-
纯手工创建
-
优点
- 第三张表由自己来设计创建,扩展性强
-
缺点
- 第三张表由自己来设计创建,编写繁琐
- 不再支持add、remove、set、clear四种操作
- 不再有正反向概念
代码如下:
# 1. 创建书籍表
class Book(models.Model):
"""书籍表"""
title = models.CharField(max_length=64, verbose_name='书籍名称')
# 2. 创建作者表
class Authors(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='作者姓名')
# 3. 创建第三张多对多关系表
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, verbose_name='xxx')
create_time = models.DateField(auto_now_add = True, verbose_name='创建时间')
- 半自动创建
结合了自动创建与手工创建,在手工创建的基础上,在查询频率较高的表中添加ManyToManyField自动创建的参数,同时,需要添加两个参数:
through='手工创建的第三张关系表' # 这个是告诉ORM哪张表是第三张关系对应表
through_fields=('第三张关系表中对应的外键字段','第三张关系表中对应的外键字段') # 告诉ORM哪两个字段对应外键关系。两张表顺序不能错,哪个表查询频率更高,哪个表需要写在前面。 - 优点
- 第三张表由自己创建,扩展性强。
- 支持正反向概念
- 缺点
- 不再支持add、remove、set、clear四种操作
- 编写比较繁琐
代码如下:
# 1. 创建书籍表
class Book(models.Model):
"""书籍表"""
title = models.CharField(max_length=64, verbose_name='书籍名称')
authors = models.ManyToManyField(to='Author', through='Book2Author', through_fields=('book', 'author'))
# 2. 创建作者表
class Authors(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='作者姓名')
# 3. 创建第三张多对多关系表
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, verbose_name='xxx')
create_time = models.DateField(auto_now_add = True, verbose_name='创建时间')
聚合查询
非分组查询使用
常见聚合函数:Max Min Sum Count Avg
在ORM中支持单独使用聚合函数,需要配合aggregate参数
- 聚合函数需要导入相应模块才可以使用
from django.db.models import Max, Min, Sum, Count, Avg
# 1. 查询价格最高的一本书
res = models.Book.objects.aggregate(Max('price'))
# 2. 查询价格最高和最低的两本书(也可以使用别名)
res = models.Book.objects.aggregate(max_price=Max('price'), min_price=Min('price'))
分组查询
-
按表分组:models.表名.objects.annotate()
-
按字段名分组: models.表名.objects.values('字段名').annotate()
# 1. 统计每本书的作者个数
# 1.1. 注意,这里使用models.哪张表,就是按哪个表分组。比如这里要统计每本书,所以需要按书名分组。
res = models.Book.objects.annotate(count=Count('authors__pk')).values('title', 'count')
# 2. 统计出每个出版社卖得最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
# 3. 统计不止一个作者的书
# 3.1. 先查询出每本书作者的个数,再进行筛选
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title', 'author_num')
# 4. 统计每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
# 5. 统计每个出版社出了几本书
res = models.Book.objects.values('publish_id').annotate(count=Count('pk')).values('publish_id', 'count')
F与Q查询
-
当查询条件不是明确的,需要从数据库中获取时,就需要使用F查询
-
当查询条件需要使用or连接而不是默认的and的时候,需要使用Q查询
-
需要导入模块 from django.db.models import F, Q
准备工作:
# 在book表中新增两个字段,并添加数据(添加数据不再演示)
stock = models.IntegerField(verbose_name='库存数')
sell = models.IntegerField(verbose_name='卖出数')
数据如下:
F查询
- 当查询条件不是明确的,需要从数据库中获取时,就需要使用F查询
# 1. 查询库存数大于卖出数的书籍
from django.db.models import F, Q
res = models.Book.objects.filter(stock__gt=F('sell'))
# 2. 将每个商品的价格提高500块
models.Book.objects.update(price=F('price')+500)
# 3. 每本书名后面加上(新款)两个字,字符串的拼接需要导入两个模块
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F('title'), Value('(新款)')))
Q查询
- 当查询条件需要使用or连接而不是默认的and的时候,需要使用Q查询
- '~'符号是not,也就是取反;
- '|' 符号是or关系
- 逗号是and关系
from django.db.models import F, Q
# 1. 查询主键是1或者库存大于1500的书籍
res = models.Book.objects.filter(Q(pk=1) | Q(stock__gt=1500))
# 2. 查询主键不是1或者库存大于1500的书籍,取反操作使用'~'符号,如下:
res = models.Book.objects.filter(~Q(pk=1) | Q(stock__gt=1500))
Q查询的进阶操作
- Q可以将查询条件的左侧的字段名,以字符串形式进行操作
- 可以与用户交互,可以用于搜索功能
# 1. 导入Q模块
from django.db.models import Q
# 2. 产生对象
q_obj = Q()
# 3. 添加查询条件
q_obj.children.append(('pk', 1))
# 4. 可以添加多个查询条件
q_obj.children.append(('price__gt', 1100))
# 5. 查询支持直接填写Q对象
res = models.Book.objects.filter(q_obj)
ORM查询优化
only与defer
'''
1. ORM的查询都是惰性查询
如果ORM语句写了,但是在后续的代码中都未使用,则不会真正的进行查询。
2. ORM的查询都自带分页处理
在查看ORM的SQL语句的时候,可以看到LIMIT 20这种字样,其实是ORM在自动做分页处理
3. only与defer
需要获取数据库中表的数据对象,并且此数据对象只包含指定的字段对应的数据
res = models.Book.objects.only('title', 'price')
only是会将扩号内的字段封装到数据对象中,当去查询这些字段的值的时候(res.title),不会再走SQL查询,但如果想要查询不在only中的字段(res.publish_time),ORM会重新再走一遍SQL语句(要注意,在for循环中,每点一次会走一遍,也就是每次循环都会重新进行一次SQL查询)。
defer与only相反,会将不是扩号内的字段封装到数据对象中,当去查询这些字段的值的时候(res.title),每次调用都会重新走一遍SQL,而去调用非扩号内的字段数据时,则不会再走SQL。
'''
select_related与prefetch_related
# 场景:
res = models.Book.objects.all()
for i in res:
# 当去查询没有在数据对象中的数据时,会每次循环都会执行一次SQL语句,如果数据量过大,效率会非常低
print(i.publish.name)
# 解决方案:
# 底层会走一个inner join,注意,这里面不可以使用多对多的字段
res = models.Book.objects.select_related('publish')
for i in res:
print(i.publish.name)
# 解决方案二:
# 底层是select子查询,所以会走两条SQL,同样是不可以使用多对多字段
res = models.Book.objects.prefetch_related('publish')
for i in res:
print(i.publish.name)
ORM事务操作
'''
事务的四大特性ACID
原子性、一致性、隔离性、持久性
相关SQL关键字
start transaction;
rollback;
commit;
savepoint;
相关重要概念
脏读
幻读
不可重复读
MVCC多版本控制
'''
1 全局开启
在Web应用中,常用的事务处理方式是将每个请求都包裹在一个事务中。这个功能使用起来非常简单,你只需要将它的配置项ATOMIC_REQUESTS设置为True。
它是这样工作的:当有请求过来时,Django会在调用视图方法前开启一个事务。如果请求却正确处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'db1',
'HOST': '192.101.0.1',
'PORT': '3306',
'USER': 'root',
'PASSWORD': '123',
'OPTIONS': {
"init_command": "SET default_storage_engine='INNODB'",
#'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", # 配置开启严格sql模式
}
"ATOMIC_REQUESTS": True, # 全局开启事务,绑定的是http请求响应整个过程
"AUTOCOMMIT":False, # 全局取消自动提交,慎用
},
'other':{
'ENGINE': 'django.db.backends.mysql',
......
} # 还可以配置其他数据
对部分视图函数取消事务
from django.db import transaction
@transaction.non_atomic_requests
def my_view(request):
do_stuff()
@transaction.non_atomic_requests(using='other')
def my_other_view(request):
do_stuff_on_the_other_database()
注意:只要在视图函数中,执行到了return就算一个事务执行完成,就会commit数据
def my_view_func(request):
models.User.object.create(name='jjj', age=10)
return 123321 # 执行到这里时会报错,但执行到了return,也算是一个事务成功完成
2 局部开启
需要导入模块: from django.db import transaction
atomic(using=None, savepoint=True)[source] ,参数:using=‘other’,就是当你操作其他数据库的时候,这个事务才生效,看上面我们的数据库配置,除了default,还有一个other,默认的是default。savepoint的意思是开启事务保存点。
原子性是数据库事务的一个属性。使用atomic,我们就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。
被atomic管理起来的代码块还可以内嵌到方法中。这样的话,即便内部代码块正常运行,如果外部代码块抛出异常的话,它也没有办法把它的修改提交到数据库中。
用法1:给视图函数装饰器
from django.db import transaction
@transaction.atomic
def my_func(request):
# This code executes inside a transaction.
pass
用法2:作为上下文管理器来使用
from django.db import transaction
def my_func(request):
with transaction.atomic(): #保存点
pass
return xxxx
一旦把atomic代码块放到try/except中,完整性错误就会被自然的处理掉了,比如下面这个例子:
from django.db import IntegrityError, transaction
@transaction.atomic
def my_func(request):
create_parent()
try:
with transaction.atomic():
generate_relationships()
except IntegrityError:
handle_exception()
add_children()
用法3:还可以嵌套使用,函数的事务嵌套上下文管理器的事务,上下文管理器的事务嵌套上下文管理器的事务等
from django.db import IntegrityError, transaction
@transaction.atomic
def viewfunc(request):
create_parent()
try:
with transaction.atomic():
generate_relationships()
#other_task() #还要注意一点,如果你在事务里面写了别的操作,只有这些操作全部完成之后,事务才会commit,也就是说,如果你这个任务是查询上面更改的数据表里面的数据,那么看到的还是事务提交之前的数据。
except IntegrityError:
handle_exception()
add_children()
批量插入数据
使用ORM的create向数据库中批量插入数据效率非常低,我做了一个前面页面,当访问这个路由时,就会向数据库中插入数据,完成后再展示到前台。
经测试,访问页面后约五秒只插入2W多行数据。(这还只是一个字段的数据,放在生产中不可想象会有多慢)
代码如下:
# 1. 路由层:
path('test/', views.test_func)
# 2. 模型层
class Test(models.Model):
"""书籍表"""
name = models.CharField(max_length=64, verbose_name='测试字段')
# 3. 视图层
def test_func(request):
# 1. 向测试表中插入10万条数据
for i in range(1, 100000):
models.Test.objects.create(name=f'第%s条数据' % i)
# 2. 查询出插入的所有数据并展示给前端
book_queryset = models.Test.objects.all()
return render(request, 'test.html', locals())
# 4. 模板层
{% for obj in test_queryset %}
<p>{{ obj.name }}</p>
{% endfor %}
- ORM中提供了一个批量插入的方法bulk_create,代码如下:
def test_func(request):
# 1. 向测试表中插入10万条数据
test_obj_list = []
for i in range(1, 100000):
test_obj = models.Test(name='第%s条数据' % i)
test_obj_list.append(test_obj)
models.Test.objects.bulk_create(test_obj_list)
# 2. 查询出插入的所有数据并展示给前端
test_queryset = models.Test.objects.all()
return render(request, 'test.html', locals())
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix