Django-模型层

目录

-测试脚本

-关于主键id字段的补充

-Django查看SQL语句的方式

--方式一

--方式二

-ORM必知必会十三条

-双下划线查询

-多表操作

-正反向查询定义

-多表查询

--子查询

--联表查询

-聚合查询

-分组查询

-F与Q查询

--F查询

--Q查询

-Django中开启事务

-数据库查询优化

-choices参数

-MTV与MVC

-多对多三种创建方式

--全自动

--纯手动

--半自动


-测试脚本

当我们只想测试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()
orm必会13条

-双下划线查询

"""双下划线查询"""
 
# 年龄大于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('特价')))
F查询

--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:现在左侧条件就可以放任意字符串条件了
Q查询

-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条,即多了一条走数据库查子查询结果的
不过它也是将查询结果封装到对象中,看起来好像是一次性搞定的"""
only与defer,select_related与perfetch_related

-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)
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方法
"""
半自动

 

 

posted @ 2022-10-29 11:18  weer-wmq  阅读(12)  评论(0编辑  收藏  举报