Django之模型层

目录

一、前言 

【1】测试脚本

【2】数据准备

二、单表操作

【1】数据的增加

【2】数据的删除

【3】数据的更改

三、常见的十几种查询方法

四、查看内部sql语句的方式

【1】方式一

【2】方式二

五、基于双下划线的查询

六、多表查询引入

【1】数据准备

【2】外键的增删改查

(1)一对多外键的增删改查

(2)多对多外键的增删改查

【补充】正反向的概念

七、多表查询

【1】子查询(基于对象的跨表查询)

【2】联表查询(基于双下划线的跨表查询)

八、聚合查询

九、分组查询

十、F与Q查询

【1】F查询

【2】Q查询


一、前言 

Django自带的sqlite3数据对日期格式不敏感,处理的时候容易出错

【1】测试脚本

  • 测试脚本
    • 当我们只想要测试Django中的某一个py文件的内容时,我们可以不需要书写前后端交互的形式,而是直接写一个测试脚本即可
  • 测试环境的准备
    • 在测试文件中书写下面内容
  • 这内容其实就是最外部 manage.py 文件中的上面几句话
    • 脚本代码无论是写在应用下的 tests.py文件还是自己新建文件,将内容写在新文件中,都会生效
from django.test import TestCase
 
# Create your tests here.
import os
 
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day07.settings")
 
    import django
    django.setup()
    
    # 在下面书写我们需要测试的代码
    # 即所有的测试代码都必须等待环境准备完毕之后才能书写

【2】数据准备

  • 在models里面创建我们需要的数据库中的表
from django.db import models
 
 
# Create your models here.
 
class User(models.Model):
    name = models.CharField(verbose_name="姓名", help_text="姓名", max_length=32)
    age = models.IntegerField(verbose_name="年龄", help_text="年龄")
    register_time = models.DateTimeField(verbose_name="注册时间", help_text="注册时间")
    '''
    DateField
    DateTimeField
        两个关键参数
            auto_now : 每次操作数据的时候该字段会自动将当前时间更新
            auto_now_add : 在创建数据的时候会自动将当前创建时间记录下来,只要不是人为修改,就不会发生更改
    '''
  • 配置数据库文件(settings.py)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        # 数据库名字
        'NAME': 'django_day10',
        # 用户
        "USER": "root",
        # 密码
        "PASSWORD": "123",
        # IP
        "HOST": "127.0.0.1",
        # 端口
        "PORT": 3306,
        # 编码集
        "CHARSET": "utf8",
    }
}
  • 在项目下的 init.py 中声明数据库类型
import pymysql
 
pymysql.install_as_MySQLdb()

二、单表操作

【1】数据的增加

from django.test import TestCase
 
# Create your tests here.
import os
 
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day07.settings")
 
    import django
 
    django.setup()
 
    # 在下面书写我们需要测试的代码
    # 即所有的测试代码都必须等待环境准备完毕之后才能书写
    from app01 import models
 
    # 增加数据
    # register_time (1)支持自己指定传值
    res = models.User.objects.create(name="jerry", age=20, register_time='2023-11-15')
    # 返回值为对象本身
    print(res)  # User object
    
    # register_time (2)支持传入日期对象
    import datetime
    # 生成一个当前的时间对象
    c_time = datetime.datetime.now()
    user_obj = models.User.objects.create(name="egon", age=20, register_time=c_time)
    # 保存数据
    user_obj.save()

【2】数据的删除

# 删除数据
"""pk : 自动查找到当前表的主键字段,指代的就是当前表的主键字段
使用 pk 后不需要知道当前表的主键字段,它会自动帮我们查找并匹配"""
# (1)方式一
res = models.User.objects.filter(pk=4).delete()
print(res) # (1, {'app01.User': 1})
# (2)方式二
# 拿到当前用户对象
user_obj = models.User.objects.filter(pk=1).first()
# 利用对象的方法进行删除
user_obj.delete()

【3】数据的更改

# 数据的更改
# (1)方式一
res = models.User.objects.filter(pk=5).update(name="mengmeng")
print(res)  # 1
# (2)方式二
"""get方法返回的直接就是当前数据对象;
但是该方法不推荐使用:如果查询的数据不存在会直接报错 ,fileter不会"""
user_onj = models.User.objects.get(pk=5)
# 调用对象更改数据
user_onj.name = "xiaomeng"
user_onj.save()

三、常见的十几种查询方法

    ################必知必会十三条################
    # (1) all() - 查询所有数据
    # (2) filter() - 带有过滤条件的查询
    # (3) get() - 根据条件拿数据对象,但是条件不存在会直接抛出异常
    # (4) first() - 拿queryset中的第一个元素
    res = models.User.objects.all().first()
    print(res)
    # (5) last() - 拿queryset中的最后一个元素
    res = models.User.objects.all().last()
    print(res)
 
    # (6) values() - 可以指定获取的数据字段
    res = models.User.objects.values('name')
    # 返回的数据格式为列表套字典 - 本质上是一个 QuerySet 对象 ,而不是真的列表
    print(res)  # 
    # (7) values_list() - 可以指定获取的数据字段
    res_list = models.User.objects.values_list('name', 'age')
    # 返回的数据格式为列表套元祖 - 本质上是一个 QuerySet 对象 ,而不是真的列表
    print(res_list)  # 
    # 该语句可以查看当前执行命令的 SQL 语句 - 只有queryset对象才能使用该方法
    print(res_list.query)  
    # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`
 
    # (8) distinct() - 去重(带有主键就意味着数据存在不一样的地方,
    # 所以一定要去除主键后再去重;去重一定要是一模一样的数据)
    res = models.User.objects.values('name', 'age').distinct()
    print(res)
    # (9) order_by() - 排序
    # 默认升序
    res = models.User.objects.order_by('age')
    print(res)
    # 降序
    res = models.User.objects.order_by('-age')
    print(res)
 
    # (10) reverse() - 反转的前提是数据已经经过排序过的数据
    # 只能对有序的数据进行反转
    res = models.User.objects.order_by('age').reverse()
    print(res)
 
    # (11) count() - 统计当前数据的个数
    res = models.User.objects.count()
    print(res)
 
    # (12) exclude() - 排出在外
    # 将某个数据排出在结果之外
    res = models.User.objects.exclude(user="dream")
 
    # (13) exists() - 是否存在 - 返回布尔值 - 用处不大,因为数据本身就有布尔值的状态
    res = models.User.objects.filter(pk=5).exists()
    print(res)

四、查看内部sql语句的方式

【1】方式一

只有queryset对象才能使用该方法

res_list = models.User.objects.values_list('name', 'age')
    # 返回的数据格式为列表套元祖 - 本质上是一个 QuerySet 对象 ,而不是真的列表
    print(res_list)  # 
    # 该语句可以查看当前执行命令的 SQL 语句 - 只有queryset对象才能使用该方法
    print(res_list.query)  
    # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`

【2】方式二

所有 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',
        },
    }
}

五、基于双下划线的查询

    # 神奇的双下划线查询
    # (1)年龄大于35岁的数据
    res = models.User.objects.filter(age__gt=35)
    print(res)
    # (2)年龄小于35岁的数据
    res = models.User.objects.filter(age__lt=35)
    print(res)
    # (3)年龄大于等于32岁的数据
    res = models.User.objects.filter(age__gte=32)
    print(res)
    # (4)年龄小于等于30岁的数据
    res = models.User.objects.filter(age__lte=30)
    print(res)
    # (5)年龄是18或者32或者40
    res = models.User.objects.filter(age__in=(18, 32, 40))
    print(res)
    # (6)年龄是18-40之间 - 首尾都要
    res = models.User.objects.filter(age__range=(18, 40))
    print(res)
    # (7)查询出名字中含有 s 的数据  -- 模糊查询
    res = models.User.objects.filter(name__contains='s')
    print(res)
    # (7.1)是否区分大小写?
    res = models.User.objects.filter(name__contains='N')
    print(res)
    # 默认区分大小写
    # (7.2)忽略大小写
    res = models.User.objects.filter(name__icontains='N')
    print(res)
    # (8)以什么开头/结尾
    res = models.User.objects.filter(name__startswith='d')
    print(res)
    res = models.User.objects.filter(name__endswith='m')
    print(res)
    # (9)查询出注册时间是2020年1月份的数据/年/月/日
    res = models.User.objects.filter(register_time__month='1')
    print(res)
    res = models.User.objects.filter(register_time__year='2020')
    print(res)
    res = models.User.objects.filter(register_time__day='28')
    print(res)

六、多表查询引入

【1】数据准备

class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish_date = models.DateField(auto_now_add=True)

    # 一对多
    publish = models.ForeignKey(to='Publish')
    # 多对多
    authors = models.ManyToManyField(to='Author')


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
   
    # varchar(254)  该字段类型不是给models看的,而是给后面我们会学到的校验性组件看的

    def __str__(self):
        return self.name


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 一对一
    author_detail = models.OneToOneField(to='AuthorDetail')


class AuthorDetail(models.Model):
    phone = models.BigIntegerField()  
    # 电话号码用BigIntegerField或者直接用CharField
    addr = models.CharField(max_length=64)
  • 重载数据库
python36 manage.py makemigrations
 
python36 manage.py migrate

 【2】外键的增删改查

(1)一对多外键的增删改查

1.1 外键的增加

# (1)外键的增加 - 直接写实际字段
models.Book.objects.create(title="三国演义", price=1369.25, publish_id=1)
# (2)外键的增加 - 虚拟字段
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title="红楼梦", price=1569.25, publish=publish_obj)

1.2 外键的删除

# (2)一对多的外键的删除
models.Publish.objects.filter(pk=1).delete()  # 级联删除

1.3 外键的修改

#  - 直接写实际字段
models.Book.objects.filter(pk=1).update(publish_id=2)
#  - 虚拟字段
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)

(2)多对多外键的增删改查

多对多增删改查就是在操作第三张表 

2.1 增加

# (1)如何给书籍添加作者
book_obj = models.Book.objects.filter(pk=1).first()
# book_obj.authors - 这样我们就已经能操作第三张关系表了
# 书籍ID为1的书籍绑定了一个主键为1的作者
book_obj.authors.add(1)
# 可以传多个参数
book_obj.authors.add(2,3)
# 支持参数传对象 - 且支持多个对象
book_obj.authors.add(author_obj)

2.2 删除(remove)

支持多个参数/支持对象

# (2)删除
book_obj = models.Book.objects.filter(pk=1).first()
# 支持多个参数 - 支持多个对象
book_obj.authors.remove(2)

2.3 更改(set)

先删除后增加

# (3)修改
book_obj = models.Book.objects.filter(pk=1).first()
# 括号内必须给一个可迭代对象
# 把 1 删掉 替换成 2
book_obj.authors.set([1, 2]) # 括号内必须给一个可迭代对象
# 把原来都删掉 , 替换成 3
book_obj.authors.set([3]) # 括号内必须给一个可迭代对象
# 支持放对象
book_obj.authors.set([author_obj])

2.4 清空

# (4) 清空
# 在第三张表中清除某一本书和作者的绑定关系
book_obj = models.Book.objects.filter(pk=1).first()
# clear:括号内不要加任何参数
book_obj.authors.clear()

【补充】正反向的概念

  • 正向

    • 外键字段在我手上,那么我查你就是正向
    • book >>>> 外键字段在书这边(正向) >>>> 出版社
  • 反向

    • 外键字段不在我手上,那么我查你就是反向
    • 出版社>>>> 外键字段在书这边(反向) >>>> book
  • 一对一和一对多的判断也是这样

正向查询按字段

反向查询按表明名(小写)

​ __set

​ ...

七、多表查询

【1】子查询(基于对象的跨表查询)

# (1)查询书籍主键为1的出版社
book_obj = models.Book.objects.filter(pk=1).first()
# 书查出版社 - 正向 - 按字段查
res = book_obj.publish
print(res)  # Publish object
print(res.name)  # 东方出版社
print(res.addr)  # 东方
 
# (2)查询书籍主键为2的作者
book_obj = models.Book.objects.filter(pk=1).first()
# 书查作者 - 正向查询按字段
res = book_obj.authors
print(res)  # app01.Author.None
# 列表中存放的是作者对象
print(res.all())  # ]>
 
# (3)查询作者 的 电话号码
author_obj = models.Author.objects.filter(name="dream").first()
# 作者查询作者详情 - 正向查询按字段
res = author_obj.author_detail
print(res)  # AuthDetail object
print(res.phone)  # 110
print(res.addr)  # 山东
 
'''
    在书写ORM语句的时候跟写SQL语句一样的
    不要企图一次性将ORM语句写完,如果比较复杂,需要写一些看一些
    正向 什么时候需要加 .all()
        当查询返回的结果是多个的时候就需要用 .all()
        当查询的结果只有一个的时候直接拿到数据对象
            book_obj.publish
            book_obj.authors.all()
            author_obj.author_detail
    '''
 
# (4)查询出版社是东方出版社出版的书
# 先拿到出版社对象
publish_obj = models.Publish.objects.filter(name="东方出版社").first()
# 出版社查书 - 主键字段在书 - 反向查询
res = publish_obj.book_set.all()
# publish_obj.book_set
# print(res) # app01.Book.None
# publish_obj.book_set.all()
print(res)  
# , , ]>
 
# (5)查询作者是dream写过的书
# 先拿到作者对象
author_obj = models.Author.objects.filter(name="dream").first()
# 作者查书 - 主键在书 - 反向
res = author_obj.book_set.all()
print(res)  # ]>
 
# (6)查询手机号是 110的作者姓名
# 先拿到作者详情的对象
author_detail_obj = models.AuthDetail.objects.filter(phone=110).first()
# 详情查作者 - 主键在作者 - 反向
res = author_detail_obj.author
print(res)  # Author object
print(res.name)  # dream

基于对象

  • 反向查询的时候
    • 当你的查询结果可以有多个的时候 就必须加_set.all()
    • 当你的结果只有一个的时候 不需要加_set.all() 

【2】联表查询(基于双下划线的跨表查询)

# (1)查询dream的手机号和作者的姓名
# 正向:先查询到作者信息再 .value(需要查询信息的表__需要查询的字段,其他字段)
res = models.Author.objects.filter(name="dream").values('author_detail__phone', 'name')
print(res)  
# 

# 反向:先拿到详情,再用作者详情关联作者表,通过 __字段的方法 过滤出我们想要的指定数据
res = models.AuthDetail.objects.filter(author__name="dream").values('phone', 'author__name')
# AuthDetail.objects.filter(author__name="dream")
print(res)  # ]>
# AuthDetail.objects.filter(author__name="dream").values('phone','author__name')
print(res)  # 
 
# (2)查询书籍主键ID为1的出版社名字和书的名字
# 正向:先过滤出书籍ID为1的书籍对象,再去关联出版者表,利用__字段取值
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)  
# 

# 反向:先查询到指定出版社,再从出版社反向找到书籍名字
res = models.Publish.objects.filter(book__id=1).values('name', 'book__title')
print(res)  # 
 
# (3)查询书籍主键ID为1的作者姓名
# 先拿到 书籍主键ID为1的对象,再关联作者信息表,通过__字段取值
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)  # 

# 反向 : 先拿到 书籍ID为1的作者数据再去取作者的名字
res = models.Author.objects.filter(book__id=1).values('name')
print(res)  # 
 
# 查询书籍主键是1的作者的手机号
# book author authordetail
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)  #  
'''
    只要掌握了正反向的概念
    以及双下划线查询
    就可以无限跨表
    '''

八、聚合查询

# 聚合查询
    # 聚合查询通常情况下都是配合分组一起使用的
    '''
    只要是和数据库相关的模块
        基本上都在 django.db.models 里面
        如果这里面没有 那大概率可能在 django.db 里面
    '''
    from django.db.models import Max, Min, Sum, Count, Avg
 
    # (1)所有书的平均价格
    # 正常情况下,我们是需要先进行分组再进行聚合函数运算的
    # 但是Django给我们提供了一种方法 :aggregate--> 可以不分组进行某个字段的聚合函数
    res = models.Book.objects.aggregate(Avg('price'))
    print(res)  # {'price__avg': 1890.083333}
 
    # (2)一次性使用
    res = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'), Sum('price'), Count('pk'))
    print(res) 
    # {'price__avg': 1890.083333, 'price__max': Decimal('5959.25'), 'price__min': Decimal('555.25'), 'price__sum': Decimal('11340.50'), 'pk__count': 6}

九、分组查询

# 分组查询   annotate
'''
    MySQL中的分组查询
        分组之后只能获取到分组的依据,组内其他字段都无法获取
            严格模式中可以修改
            ONLY_FULL_GROUP_BY
            set global sql_mode='ONLY_FULL_GROUP_BY'
    '''
from django.db.models import Max, Min, Sum, Count, Avg
# (1)统计每一本书的作者个数
# models 后面跟的是什么,就是按什么分组
# res = models.Book.objects.annotate()
res = models.Book.objects.annotate(author_number=Count('authors')).values('title','author_number')
'''
author_number 是我们自己定义的字段,用来存储统计出来的每本书的作者个数
'''
# 等价于
# res = models.Book.objects.annotate(author_number=Count('authors__pk')).values('title','author_number')
# print(res) 
# 
 
# (2)统计每个出版社最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res) 
# 
 
# (3)统计不止一个作者的图书
# (3.1)先按照图书分组
# (3.2)过滤出不止一个作者的图书
# 我的数据有限,我统计的是大于0的作者的图书
res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=0).values('title', 'author_num')                                                                             
'''
只要ORM语句得到的是 一个 queryset 对象
那么就可以继续无限制的调用封装 的方法
'''
print(res)  # 
 
# (4)查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
print(res)  
# 
 
'''
    如果想按照指定的字段分组该如何处理
    如果 annotate 前面没东西 则会按照 Book 分组 ,如果前面有参数 就会按照前面的参数进行分组 price
    models.Book.objects.values('price').annotate()
    机器上如果出现分组查询报错的情况
    需要修改数据库严格模式
'''

十、F与Q查询

【1】F查询

from django.db.models import F
 
# (1)查出卖出数大于库存数的书籍
# F 查询 : 帮助我们直接获取到表中的某个字段对应的数据
res = models.Book.objects.filter(sales__gt=F('stock'))
print(res)  # ]>
 
# (2).将所有书籍的价格提升500块
res = models.Book.objects.update(price=F('price') + 500)
print(res)  # 6 - 影响到了 6 条数据
 
# (3)将所有书的名称后边加上爆款两个字
# 在操作字符串的时候,F查询不能够直接坐到字符串的拼接
from django.db.models.functions import Concat
from django.db.models import Value
 
res = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
print(res)
# models.Book.objects.update(title=F('title') + '爆款')  # 所有的名称会全部变成空白

【2】Q查询

# F与Q查询
 
from django.db.models import Q
 
# (1)查询卖出数大于100或者价格小于500的书籍
 
# (1.1)直接使用 filter 查询数据,逗号隔开,里面放的参数是 and 关系
res = models.Book.objects.filter(sales__gt=100, price__lt=500)
print(res)  # 
 
# (1.2)直接使用 Q 查询数据,逗号隔开,里面放的参数还是 and 关系
res = models.Book.objects.filter(Q(sales__gt=100), Q(price__lt=500))
print(res)  # 
 
# (1.3)直接使用 Q 查询数据,逗号可以换成其他连接符达到效果
res = models.Book.objects.filter(Q(sales__gt=100) or Q(price__lt=500))
# 二者等价 (| :或关系) (~  : 取反 not 关系)
res = models.Book.objects.filter(Q(sales__gt=100) | Q(price__lt=500))
print(res)  
# , , , ]>
 
# (2) Q的高阶用法 能够将查询条件的左边也变成 字符串形式
# 产生 Q 对象
q = Q()
# 修改Q查询的默认连接条件
q.connector = 'or'
q.children.append('sales', 100)
q.children.append('stock', 600)
# filter 参数支持Q对象,默认还是 and 关系
res = models.Book.objects.filter(q)
print(res)

你可能感兴趣的:(django,python,后端)