模型层
django自带的sqlite3数据库,功能很少,并且针对日期类型不精确
准备步骤
- 数据库正向迁移命令(将类操作映射到表中)
python3 manage.py makemigrations python3 manage.py migrate
- 数据库反向迁移命令(将表映射成类)
python3 manage.py inspectdb
需求
我们只想操作orm,不想使用网络请求,需要有专门的测试环境
方式
自己搭建tests.py
import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE","day54.settings") import django django.setup() """进行代码操作"""
pycharm提供
python console
ORM操作常见关键字
all()方法
查询数据
models.User.objects.all() # 列表套对象 models.User.objects.all().values('name') # 列表套字典 == models.User.objects.values('name') # 查找指定字段可以不写all() models.User.objects.all().values_list('name', 'age') # 列表套元祖
filter()方法
filter()括号内支持填写多个筛选条件,默认是and关系
查询数据
通过主键筛选数据,可以直接写pk,会自动定位到当前表的主键字段,无需查看
models.User.objects.filter(pk=1)[0] # 索引不存在报错 models.User.objects.filter(pk=1).first # 索引不存在返回None models.User.objects.filter().values('name','age') models.User.objects.filter().last() # 获取最后一个对象
distinct对于数据对象必须是一模一样,主键值存在则无法去重
关键字方法
models.User.objects.order_by('age') # 升序 models.User.objects.order_by('-age') # 降序 order_by支持多个字段 models.User.objects.exclude(name='zhou') # 取反操作 models.User.objects.count() # 统计结果集的个数 models.User.objects.exist() # 校验是否存在,返回布尔值 models.User.objects.get(pk=100) # 获取数据,条件不存在直接报错
双下划线查询
- __gt 大于
- __lt 小于
- __gte 大于等于
- __lte 小于等于
- __in 成员运算
- __range 范围查询
- __contains 模糊查询,区分大小写(name__conrains='j')
- __icontains 忽略大小写
- __startswith
- __endswith
- __regex
- __year 按照年份筛选数据
- __month 按照月份筛选数据
外键字段的创建
django 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_data = models.DateField(auto_now_add=True, verbose_name='出版日期') # 书和出版社的外键字段 publish = models.ForeignKey(to='Publish') # 书和作者的外键字段 author = models.ManyToManyField(to='Author') class Publish(models.Model): """出版社表""" name = models.CharField(max_length=32, verbose_name='出版社名称') addr = models.CharField(max_length=64, verbose_name='出版社地址') class Author(models.Model): """作者表""" name = models.CharField(max_length=32, verbose_name='姓名') age = models.IntegerField(verbose_name='年龄') # 作者和作者详情表的外键字段 author_detail = models.OneToOneField(to='AuthorDetail') class AuthorDetail(models.Model): """作者详情表""" phone = models.BigIntegerField(verbose_name='手机号') addr = models.CharField(max_length=64, verbose_name='家庭地址')
关系判断
书与出版社
一本书不能对应多个出版社,一个出版社可以对应多本书
一对多关系,书是多
ForeignKey
django orm外键字段针对一对多关系也是建在多的一方
书与作者
一本书可以对应多个作者,一个作者可以对应多本书
多对多关系
ManyToManyField
django orm外键字段针对多对多关系,可以不用自己创建第三张表
作者与作者详情
一个作者不能对应多个作者详情,一个作者详情不能对个多个作者
一对一关系
OneToOneField
django orm外键字段针对一对一关系,建在查询频率较高的表中
注意
ManyToManyField不会在表中创建实际的字段,而是告诉django orm自动创建第三张关系表
ForeignKey、OneToOneField会在字段的后面自动添加_id后缀,如果你在定义模型类的时候自己添加了该后缀那么迁移的时候还会再次添加_id_id,所以不要自己加_id后缀
to用于指定跟哪张表有关系,自动关联主键
to_field\to_fields,也可以自己指定关联字段
外键字段操作
一对多、一对一外键字段操作
增
可以传值,也可以传数据对象
publish_id=1
publish=publish_obj
改
update(publish_id=3)
update(publish=publish_obj)
多对多字段操作
第三张关系表创建数据(add)
括号内可以放主键值也可以放数据对象,并且都支持多个
第三张关系表修改数据(set)
括号内必须是一个可迭代对象,元素同样支持主键值或者数据对象
第三张关系表删除数据(remove)
括号内可以放主键值也可以放数据对象,并且都支持多个
第三张关系表清空指定数据(clear)
括号内无需传值,直接清空当前表在第三张关系表中的绑定记录
多表查询
正反向
概念
核心在于当前数据对象是否含有外键字段,有则是正向,没有则是反向
正向
- 由书籍查询出版社,外键字段在书籍表中,那么书籍查出版社就是正向
- 由书籍查询作者,外键字段在书籍表中,那么书查作者就是正向
- 由作者查询作者详情,外键字段在作者表中,那么也是正向
反向
由出版社查询书籍,外键字段不在出版社表,那么出版社查书就是反向
查询口诀
- 正向查询按外键字段名
- 反向查询按表名小写
基于对象的跨表查询本质就是子查询即分步操作即可
正向查询
对应的数据是多个需要再加上.all()
基于双下划线的跨表查询本质就是连表操作
正向查询
外键字段__查询目标
基于双下划线的跨表查询的结果也可以是完整的数据对象
- 方式1:如果结果集对象是queryset,那么可以直接点query查看
- 方式2:配置文件固定配置,适用面更广,只要执行了orm操作,都会打印内部SQL语句
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
聚合查询
from django.db.models import Max, Min, Sum, Count, Avg
没有分组也可以使用聚合函数,默认整体就是一组
分组查询
以表为单位做分组
annotate在values前面
# 统计每本书的作者个数 from django.db.models import Count res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num') # 统计每个出版社卖的最便宜的书的价格 res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price') # 统计不止一个作者的图书 from django.db.models import Count res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num') # 统计每个作者出的书的总价格 from django.db.models import Sum res = models.Author.objects.annotate(book_sum_price=Sum('book__price')).values('name','book_sum_price')
以表中的某个字段做分组
values在annotate前面
# 统计每个出版社主键值对应的书籍个数 from django.db.models import Count
res=models.Book.objects.values('publish_id').annotate(book_num=Count('pk')).values('publish_id','book_num') print(res)
补充说明
当表中已经有数据的情况下,添加额外的字段,需要指定默认值或者可以为null
- 方式1:IntegerField(verbose_name='销量',default=1000)
- 方式2:IntegerField(verbose_name='销量',null=True)
- 方式3:在迁移命令提示中选择1或者2
F查询、Q查询
F查询
当查询条件的左右两表的数据都需要表中的数据,可以使用F查询
from django.db.models import F
# 查询库存大于销量的书籍 res = models.Book.objects.filter(kucun__gt=F('maichu')) # 将所有书的价格提升1000块 res = models.Book.objects.update(price=F('price') + 1000)
修改char字段
from django.db.models.functions import Concat from django.db.models import Value res = models.Book.objects.update(name=Concat(F('title'), Value('爆款')))
Q查询
逻辑运算符
from django.db.models import Q
filter括号内多个条件默认是and关系,无法直接修改,使用Q对象,就可以支持逻辑运算符
res = models.Book.objects.filter(Q(price__gt=20000), Q(maichu__gt=1000)) # 逗号是and关系 res = models.Book.objects.filter(Q(price__gt=20000) | Q(maichu__gt=1000)) # 管道符是or关系 res = models.Book.objects.filter(~Q(price__gt=20000)) # ~是not操作
进阶用法
filter(price=100),filter('price'=100)
当我们需要编写一个搜索功能,并且条件是由用户指定,这个时候左边的数据就是一个字符串
q_obj = Q() q_obj.connector = 'or' # 默认是and 可以改为or q_obj.children.append(('price__gt',20000)) q_obj.children.append(('maichu__gt',1000)) res = models.Book.objects.filter(q_obj) print(res.query)
ORM查询优化
惰性查询
orm查询默认都是惰性查询,能不消耗数据库资源就不消耗,光编写orm语句并不会直接指向SQL语句,只有后续的代码用到了才会执行
orm查询默认自带分页功能,尽量减轻单次查询数据的压力
only与defer
only
res = models.Book.objects.values('title','price') for i in res: print(i.get('title'))
需求:单个结果还是以对象的形式展示,可以直接通过句点符操作
res = models.Book.objects.only('title', 'price') for obj in res: print(obj.title) print(obj.price) print(obj.publish_time)
only会产生对象结果集,对象点括号内出现的字段不会再走数据库查询;但是如果点击了括号内没有的字段也可以获取到数据,但是每次都会走数据库查询
defer
defer与only相反,对象点括号内出现的字段会走数据库查询;如果点击了括号内没有的字段也可以获取到数据,每次都不会走数据库查询
select_related和prefetch_related
select_related
res = models.Book.objects.select_related('publish') for obj in res: print(obj.title) print(obj.publish.name) print(obj.publish.addr)
select_related括号内只能传一对一和一对多字段,不能传多对多字段;效果是做了一个连表操作(inner join),永远只会走一条sql语句,然后将连接之后的大表中所有的数据全部封装到数据对象中,后续对象通过正反向查询跨表,内部不会再走数据库查询
prefetch_related
res = models.Book.objects.prefetch_related('publish') for obj in res: print(obj.title) print(obj.publish.name) print(obj.publish.addr)
内部原理用的是子查询,再将多次查询(多条sql语句)之后的结果封装到数据对象中,后续对象通过正反向查询跨表,内部不会再走数据库查询
常见字段类型和参数
字段类型
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) - 二进制类型 字段合集
对应关系: '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)',
choices
用于可以被列举完全的数据
class User(models.Model): username = models.CharField(max_length=32) password = models.IntegerField() gender_choice = ( (1,'男性'), (2,'女性'), (3,'变性') ) gender = models.IntegerField(choices=gender_choice) user_obj.get_gender_display()
user_obj.get_gender_display( pk= )
有对应关系就拿,没有还是本身
补充知识
auto_now:每次修改数据的时候都会自动更新当前时间
auto_now_add:在数据被创建出来的时候会自动记录当前时间
自定义字段类型
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): username = models.CharField(max_length=32) password = models.IntegerField() desc = MyCharField(max_length=64, null=True) gender_choice = ( (1,'男性'), (2,'女性'), (3,'变性') ) gender = models.IntegerField(choices=gender_choice)
User表中就会多出一个desc的char字段
重要参数
null 用于表示某个字段可以为空。 unique 如果设置为unique=True 则该字段在此表中必须是唯一的 。 db_index 如果db_index=True 则代表着为此字段设置索引。 default 为该字段设置默认值。 to 设置要关联的表 to_field 设置要关联的表的字段 on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。 models.CASCADE 删除关联数据,与之关联也删除 db_constraint 是否在数据库中创建外键约束,默认为True。 models.DO_NOTHING 删除关联数据,引发错误IntegrityError models.PROTECT 删除关联数据,引发错误ProtectedError models.SET_NULL 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) models.SET_DEFAULT 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) models.SET 删除关联数据, a. 与之关联的值设置为指定值,设置:models.SET(值) b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
事务操作
定义
将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性
典例
# 事务 # 买一本 跟jason学Linux 书 # 在数据库层面要做的事儿 # 1. 创建一条订单数据 # 2. 去产品表 将卖出数+1, 库存数-1 from django.db.models import F from django.db import transaction # 开启事务处理 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)
ORM执行原生sql
条件假设
就拿博客园举例,我们写的博客并不是按照年月日来分档,而是按照年月来分的,而我们的DateField时间格式是年月日形式,也就是说我们需要对从数据库拿到的时间格式的数据再进行一次处理拿到我们想要的时间格式,这样的需求,Django是没有给我们提供方法的,需要我们自己去写处理语句了
代码展示
# extra # 在QuerySet的基础上继续执行子语句 # extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # select和select_params是一组,where和params是一组,tables用来设置from哪个表 # Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) # Entry.objects.extra(where=['headline=%s'], params=['Lennon']) # Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) # Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) 举个例子: models.UserInfo.objects.extra( select={'newid':'select count(1) from app01_usertype where id>%s'}, select_params=[1,], where = ['age>%s'], params=[18,], order_by=['-age'], tables=['app01_usertype'] ) """ select app01_userinfo.id, (select count(1) from app01_usertype where id>1) as newid from app01_userinfo,app01_usertype where app01_userinfo.age > 18 order by app01_userinfo.age desc """ # 执行原生SQL # 更高灵活度的方式执行原生SQL语句 # from django.db import connection, connections # cursor = connection.cursor() # cursor = connections['default'].cursor() # cursor.execute("""SELECT * from auth_user where id = %s""", [1]) # row = cursor.fetchone() ORM 执行原生SQL的方法
多对多三种创建方式
全自动
orm自动创建第三张表,但是无法扩展第三张表的字段
authors = models.ManyToManyField(to='Author')
全手动
优势在于第三张表完全自定义扩展性高,劣势在于无法使用外键方法和正反向
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_id = models.ForeignKey(to='Book') author_id = models.ForeignKey(to='Author')
半自动
正反向还可以使用,并且第三张表可以扩展,唯一的缺陷是不能用add\set\remove\clear四个方法
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) '''多对多建在任意一方都可以 如果建在作者表 字段顺序互换即可''' books = models.ManyToManyField( to='Author', through='Book2Author', # 指定表 through_fields=('author','book') # 指定字段 ) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author')