Django-模型层
目录
-测试脚本
当我们只想测试django项目中的某一个py文件中的代码,我们也可以不用复杂地每次都需启动django项目,而是可以写一个测试脚本
PS:脚本代码无论是写在app下的tests.py还是自己单独新建一个.py文件都行
# 测试环境准备,去manage.py中拷贝前四行代码到脚本文件中,然后自己写两行 # 总的在测试脚本文件中的准备如下 import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTING_MODULT", "项目名.settings") # 以下为自己补充的两行 import django django.setup() # 然后在下面就可以写测试脚本代码了 # ...
-关于主键id字段的补充
eg: models.User.objects.filter(pk=1).first() """ 一般都用pk来表示主键,因为pk会自动查找当前表的主键字段,然后指代该主键字段 比如可能你当前表的主键字段可能为uid,sid,pid等 但用了pk之后,就可以不用很明确的指出主键字段是什么了 """
-Django查看SQL语句的方式
--方式一
"""方式一,QuerySet对象.query:只有QuerySet对象才能.query查看SQL语句""" from app01 import models res = models.User.objects.filter(name='weer') print(res.query)
--方式二
"""方式二,所有的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', }, } }
-ORM必知必会十三条
"""orm必知必会十三条""" # 1、all()查所有 models.User.objects.all() # 2、filter() 带过滤的查询 models.User.objects.filter(name='weer') # 3、get() 直接拿数据对象,但条件不存在会报错,一般都用filter models.User.objects.get(name='weer') # 4、first() 拿QuerySet对象的第一个元素 models.User.objects.all().first() # 5、last() 拿QuerySet对象的最后一个元素 models.User.objects.all().last() # 6、values() 获取指定的数据字段, 列表套字典 models.User.objects.values('name','age') # <QuerySet [{'name': 'weer', 'age': 18}, {'name': 'yuner', 'age': 18}]> # 7、values_list() 获取指定数据字段, 列表套元组 models.User.objects.values_list() # <QuerySet [('weer', 18), ('yuner', 18)]> # 8、distinct() 去重 models.User.objects.values('age').distinct() """去重一定是要一模一样的数据,带主键的肯定不一样!!!""" # 9、order_by() 排序 models.User.objects.order_by('age') # 默认升序 models.User.objects.order_by('-age') # 降序 # 10、reverse() 反转 models.User.objects.order_by('age').reverse() """反转前提是数据已经排过序""" # 11、count() 计数 models.User.objects.all().count() # 12、exclude() 排除在外 models.User.objects.exclude(name='weer') # 13、exists() 是否存在,返回布尔值 models.User.objects.filter(pk=6).exists()
-双下划线查询
"""双下划线查询""" # 年龄大于35的 res = models.User.objects.filter(age__gt = '35') # 年龄小于35的 res = models.User.objects.filter(age__lt = '35') # 年龄在18,24,32之内的 res = models.User.objects.filter(age__in = [18,24,32]) # 年龄在18~24之内的 res = models.User.objects.filter(age__range = [18,24]) # 名字中包含w的 res = models.User.objects.filter(name__contains = 'w') # 名字中包含w的(忽略大小写) res = models.User.objects.filter(name__icontains = 'w') # 名字是以w开头的 res = models.User.objects.filter(name__startwith = 'w') # 名字是以w结尾的 res = models.User.objects.filter(name__endwith = 'w') # 注册时间register_time月份是1月的 res = models.User.objects.filter(register_time__month = '1') # 注册时间年份是2003年的 res = models.User.objects.filter(register_time__year = '2003') # 注册时间在19号的 res = models.User.objects.filter(register_time__day = '19')
-多表操作
- 一对多外键字段
如书对出版社的"一对多"关系(书对出版社一对一,出版社对书一对多),外键建在"多"的书的这一方,在Book表中会自动生成一个publish_id用来表示书对应出版社的对应关系:
对书这张表中数据增加: 方式1: 直接写绑定关联字段的id models.Book.objects.create(title='三国演义', price=123.23,publish_id = 1) 方式2: 传入绑定的字段的对象 publish_obj = models.Publish.objects.filter(pk=2).first() # 拿到与书关联的publish对象 models.Book.objects.create(title='三国演义', price=123.23,publish = publish_obj)
- 多对多外键字段
多对多关系表的增删改查就是对自动创建的第三张表的操作 那如何操作第三张表呢?
比如书Book与作者Authors这两张"多对多"关系的表:给书籍添加作者: book_obj = models.Book.objects.filter(pk=1).first() book_obj.authors.add(1) book_obj.authors.add(1, 2) """ 先拿到book_obj对象,其实就是在操作第三方表了 book_obj.add(1)就是表示对id为1的书给它绑定添加一个作者id为1的关系 book_obj.authors.add(1,2)就是表示给id=1的书绑定作者id为1和2的关系 即add可传多值 """ """ add也可传一个或多个与之对应的对象 author_obj = models.Authors.objects.filter(pk=1).first() book_obj.authors.add(author_obj) # 表示含义同上 """ 给书删除作者: book_obj = models.Book.objects.filter(pk=1).first() book_obj.authors.remove(2) #id为1的书删除其id为2的作者 """ .remove()同样支持传多个值或对象 """ 修改书的作者: $假设id为2的书的作者原来是1,3$ book_obj = models.Book.objects.filter(pk=2).first() book_obj.authors.set([1,2]) """ .set()将原来2书的作者1,3修改为1,2 注意:set内必须传的是可迭代对象 这用的是列表 元组也可 且.set是将原来的数据删掉 再重新添加 即原来对应关系是2对应1,3 .set是先将对应关系删掉 然后再新加2对应1,2的关系 且括号内也是既可以传数字也可以传对象 """ 清空: book_obj = models.Book.objects.filter(pk=2).first() book_obj.authors.clear() """ 将id为2的书的所有绑定关系全部清空 clear括号内不加任何参数 """
-正反向查询定义
在多表查询操作中,对于有关系的表都会建外键,外键在哪个地方,从该地方向它的对应关系向查询就是正向,反之就是反向。
如book表和publish表:book>>>publish多对一 , publish>>>book一对多
外键建在book表内:则从book表查询publish表中数据就是正向
publish表中没有外键字段:则从publish表中查book表数据就是反向
一对一和多对多判断也是如此
-多表查询
""" 正向查询按外键字段,反向查询按要查的表名小写(/表名小写_set) """
多表准备:有Book表,Publish表,AuthorDetail表,Authors表 Book表---Publish表 一对多,外键publish = models.ForeignKey(to='Publish') Author表---AuthorDetail表 一对一, author_detail = models.OneToOneField(to='Author') Book表---Author表 多对多, authors = models.ManyToManyField(to='Author')
--子查询
""" 子查询(基于对象的跨表查询)""" # 查询书籍主键为1的出版社 book_obj = models.Book.objects.filter(pk=1).first() # 先拿到书对象再说 # 书查出版社>>>正向,按外键字段 res = book_obj.publish print(res, res.name, res.addr) # 查询书籍主键为2的作者 book_obj = models.Book.objects.filter(pk=2).first() # 书查作者>>>正向 res = book_obj.authors.all() # 有多条 print(res,res.name) # 查询作者weer的电话号码 author_obj = models.Author.objects.filter(name='weer').first() # Author查AuthorDetail >>> 正向 res = author_obj.author_detail print(res.phone) # 查询出版社是东方出版社出版的书 publish_obj = models.Publish.objects.filter(name = '东方出版社').first() # Publish查Book >>> 反向,按表名小写 res = publish.book.all() # 或 res = publish.book_set.all() print(res, res.name) # 查询作者是weer的书 author_obj = models.Author.objects.filter(name = 'weer').first() # Author查Book >>> 反向 res = author_obj.book.all() # res = author_obj.book_set.all() # 查询手机号是666的作者姓名 author_detail_obj = models.AuthorDetail.objects.filter(phone=666).first() # 作者详情查作者 >>> 反向 res = author_detail_obj.author print(res.name)
--联表查询
"""联表查询:基于双下划线的跨表查询""" 1、查询weer的手机号和姓名 res = models.Author.objects.filter(name='weer').values('author_detail__phone', 'name') print(res) """ models.Author.objects.filter(name='weer')先拿到名为weer的对象,此时在Author表中 .values('author_detail')表示Author查AuthorDetail是正向,用外键字段author_detail,此时从Author表跨到了AuthorDetail表;__phone表示拿AuthorDetail表中的phone数据 .values()内部可传多个参数,要作者姓名,就在Author表上,不用跨表,故直接传'name' """ 反向: res = models.AuthorDetail.objects.filter(author__name='weer').values('phone','author__name') print(res) """ 不.Author表,而.AuthorDetail表来操作,前面数据对象可以.正反向,上面values可以.正反向,这filter也可以.正反向 models.AuthorDetail.objects此时在AuthorDetail表上 .filter(author)表示AuthorDetail到Author表是反向,用表名小写,跨到Author表上过滤,但只是过下滤 .values()此时还是在AuthorDetail表上,所以'phone','author__name'反向用表名小写 """ 2、查询书籍主键为1的出版社名称和书的名称 res = models.Book.objects.filter(pk=1).values('publish__name', 'title') print(res) 反向: res = models.Publish.objects.filter(book_ _id=1).values('name','book__title') print(res) 3、查询书籍主键为1的作者姓名 res = models.Book.objects.filter(pk=1).values('authors__name') print(res) 反向: res = models.Author.objects.filter(book__id = 1).values('name') print(res) 4、查询书籍主键为1的作者的手机号 res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone') print(res) """ 这先是Book表,然后跨到Author表,再__跨到AuthorDetail表,再直接拿phone号 ∴可以无限制跨表 """
-聚合查询
就是利用5个聚合函数max,min,sum,count,avg进行查询
使用前需导入对应模块:from django.db.models import Max, Min, Sum, Count, Avg
一般都是配合分组进行使用,若要单独使用需配合aggregate使用:
eg: res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), count('pk'), Avg('price'))
print(res)
-分组查询
要分组需使用关键字annotate 类似于MySQL中的group by
"""分组查询""" 1、统计每本书的作者个数 # 先按书分组,再找书对应作者,最后Count res = models.Book.objects.annotate(author_num=Count('authors_ _id')).values('title', 'author_num') res_simple = models.Book.objects.annotate(author_num=Count('authors')).values('title', 'author_num') print(res) """ models.Book.objects.annote()表示按Book表分组即按书分组 .annotate()中放要分组后统计字段,起一个变量来接收,这的author_num为自定义变量 Count()内统计的是作者个数,而Book表中没有作者,只有与作者对应id,所以需要跨到Author表 书查作者==>正向,直接外键字段authors,然后查对应id计数Count 但其实ORM自动帮你封装,所以可直接写authors即可,即为res_simple的写法 .annotate()得到的是一个对象,用.values()获得我们要的结果:'title'为书的名称,'author_num'为我们前面自定义的变量,表示的是书对应作者数 """ 2、统计每个出版社的最便宜书的价格 # 先按出版社分组,再找对应书,min找price res = models.Publish.objects.annotate(min_price=Min('book_ _price')).values('name', 'min_price') print(res) """ 按Publish分组,用min_price接收最小价格,出版社查书,跨到书表是反向,直接表名小写,再_ _price求价格最小值,用values得出版社名和最小价结果 """ 3、统计不止一个作者的书 # 先按书分组,找作者数大于1 res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num_ _gt = 1).values('title', 'author_num') print(res) 4、查询每个作者发行的书的总价格 # 按作者分组,找其书,Sum求价格 res = models.Author.objects.annotate(sum_price=Sum('book_ _price')).values('name','sum_price') print(res)
-F与Q查询
--F查询
F查询能帮助我们直接获取到表中某个字段对应的数据
表的准备:书Book表中多了库存storage和已卖出sale字段,对应数据为整数类型
"""F查询""" 1、查询出卖出数大于库存数的书籍 # res = models.Book.objects.filter(sale_ _gt = ???) # 若直接写=后拿不到提示,因为_ _gt后的=都应该是确切数字,而这是动态数据,故需要用F查询 from django.db.models import F res = models.Book.objects.filter(sale_ _gt = F('storage')).first() print(res) 2、将所有书的价格提高50块 from django.db.models import F models.Book.objects.update(price = F('price') + 50) 3、将所有书的名称后面加上"特价"两个字 """ F查询在操作数据的时候,是无法直接进行字符串的拼接的 而需要用到Concat和Value两个模块 """ from django.db.models.functions import Concat from django.db.models import Value models.Book.objects.update(title = Concat(F('title'), Value('特价')))
--Q查询
Q查询能帮我们实现逻辑与或非关系查找
""" Q查询 """ 1、查询书的卖出数大于100或者价格小于600的书籍 # res = models.Book.objects.filter(sale_ _gt=100, prict_ _lt=600) # print(res) # filter默认是and关系,结果不对 # Q查询↓ from django.db.models import Q # res = models.Book.objects.filter(Q(sale_ _gt=100), Q(prict_ _lt =600)) # Q用逗号分割,还是and关系 res = models.Book.objects.filter(Q(sale_ _gt=100) | Q(prict_ _lt =600)) # or关系 # res = models.Book.objects.filter(~Q(sale_ _gt=100)) # not关系 """ 上面Q查询左侧条件均为整型变量,那如何能使左侧条件是字符串呢? q = Q() # 看源码可知Q是一个类 q.connector = 'or' # 默认还是and关系,改为or关系 q.children.append(('sale_ _gt', 100)) # 往对象中塞筛选条件 元组 q.children.append(('price_ _lt', 600)) res = models.Book.objects.filter(q) # filter中还能放Q对象 print(res) # 结果同上,表示含义与上一样 """ # PS:现在左侧条件就可以放任意字符串条件了
-Django中开启事务
事务的四大特性:ACID
# Django中开启事务 from django.db import transaction with transaction.atomic(): # orm语句1 # orm语句2 … # 在with中写的所有ORM操作均属于一个事务
-数据库查询优化
补:ORM语句特点——惰性查询 就是如果我们写了ORM语句得到了一个查询结果,但后面没有用到该结果,ORM会自动识别直接不执行该ORM语句也就没有SQL语句执行 eg: res = models.Book.objects.all() 当配置中开启了自动打印背后SQL语句代码时,运行上述ORM语句,控制台没有SQL语句打印,因为我们根本没用到查询结果res 而若我们用了一下该结果res,比如print(res),就会执行该ORM,打印SQL语句 • only与defer 题:查询书表中所有书籍的名字title 正常:res = models.Book.objects.values('title') for d in res: print(d.get('title')) """ 正常的res得到的结果是一个字典,只包含键'title'字段,要想获得数据还需遍历得到 """ 而使用only: res = models.Book.objects.only('title') for d in res: print(d.title) """ 用only查询得到的是书籍对象,就意味着不但可以得到title,还能得到price等其他字段数据。 比如print(d.price)就是虽然只查询的是title字段,但得到的是对象,就还能.其它字段得到数据。 但是:only查询得到字段虽是个对象,但其只包含了title数据,就是还想要.其它字段如.price得到该书籍对象的价格,虽不用再写ORM语句,它会自动再走数据库查询price字段得到数据返回给你 """ 而使用defer: res = models.Book.objects.defer('title') for d in res: print(d.title) print(d.price) """ 而defer刚好与only相反:即defer得到的对象包含了除括号内字段外所有其它未被查询的数据,即我们直接d.price就直接能从对象中得到数据,而不会像only一样再走数据库再查询字段对应数据给你 反映在SQL语句打印就是only查询only括号内没有的字段,会走数据库即要打印SQL语句,而defer查询括号内没有的字段不走数据库,直接从对象里点来给你结果 """ • select_related与perfetch_related 主要跨表操作中 题:从Book表中查其出版社名称 Book-Publish 外键为publish,在Book表中设的 正常原来: res = models.Book.objects.all() for d in res: print(d.publish.name) """看结果可知没循环一次就会打印一次SQL语句 效率较低""" 而使用select_related: res = models.Book.objects.select_related('publish') for d in res: print(d.publish.name) """select_related内部先将Book表与Publish表先INNER JOIN拼接起来,然后一次性从大表中将所有数据全部封装给结果对象res 这时循环遍历总才打印2条SQL语句 减少了走数据库的流程 提高了效率 但注意:select_related括号中只能放一对一、一对多的外键字段 多对多不行 """ 而使用prefetch_related: res = models.Book.objects.prefetch_related('publish') for d in res: print(d.publish.name) """prefetch_related内部就是子查询,然后再从子查询结果中查结果 显示SQL语句为3条,即多了一条走数据库查子查询结果的 不过它也是将查询结果封装到对象中,看起来好像是一次性搞定的"""
-choices参数
对一些有多个值且需要数据列举完全的字段,一般都会利用choices进行存储 比如用户性别有男女,在数据库中存储时就需要把所有可能都列举出来: 在models.py中: gender_choices = ( (1, '男'), (2, '女'), ) gender = models.IntegerField(choices = gender_choices) """ 先是用xxx_choices元组套元组里面列举出所有可能出现的数据种类,然后用某个数据类型标识,这用的是整型 然后还是用正常gender变量来存储性别数据,但IntegerField类型还是取决于用来标识数据种类的数据类型,这是整型所以是它。若是字符串就用CharField等 然后用choices关键字传gender_choices数据,实现想要实现的结果 """ 存还是用对应数据存,这是用的1和2,那如何获取该数据对应的真实信息呢?: user_obj = models.User.objects.filter(pk = 1).first() # print(user_obj.gender) # 拿到的是标识数据即1 print(user_obj.get_gender_display()) # 真实性别数据 """ 只要是choices参数数据,想要获取到其真实对应信息,固定方法:get_字段名_display() """ 那如果存数据的时候存入元组里没有的标识数据呢?比如存入3就没有对应性别: 存还是能存进去 但取的时候也很人性化,不会报错,只会取到标识数据,比如 user_obj = models.User.objects.filter(pk = 3).first() print(user_obj.get_gender.display()) # 结果为3,没有真实性别对应 真正存的数据类型取决于标识该数据的类型: score_choices = ( ('A', '优秀'), ('B', '良好'), ('C', '及格'), ('D', '很差'), ) score = models.CharField(choices = score_choices)
-MTV与MVC
-多对多三种创建方式
--全自动
'''全自动,orm自动帮我们创建第三张表''' 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) """ 不需要我们写,支持orm提供操作的第三张关系表的方法 但第三张表的扩展性差,没法添加额外字段 """
--纯手动
'''纯手动''' 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') """ 第三张表取决于自己,可以任意扩展 写的较多,不能使用orm提供的简便方法 不推荐用 """
--半自动
'''半自动''' 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='Book',through='Book2Author',through_fields=('author','book')) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') """ 注意through_fields字段中的先后顺序 不足:不能用orm中的set,add,remove,clear方法 """