Django基础之模型层 -- ORM

Django模型层(models层)之ORM介绍

我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。

ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行。

sqlite3数据库

pycharm查看sqlite3文件

直接双击db.sqlite3文件

进入Navicate软件,点击左上角连接 --> SQLite

测试环境的搭建

在开发django项目时,如果我们想跳过django的启动与操作流程,只测试某一部分的代码的功能,需要首先引入django的配置环境才可以。

在test.py文件创建django环境,代码如下

# 创建Django环境
# 在单个文件中启动Django就可以使用这几行代码

if __name__ == '__main__':
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
    
    import django
    django.setup()  # 启动Django

    # 编写测试代码,直接运行该文本文件即可

单表操作

单表的增删改查

在test.py文件,配置好django环境。

from django.test import TestCase

# Create your tests here.
if __name__ == '__main__':
    import os
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day54D.settings")
    
    import django
    django.setup()

1.增加

    # 在这里面导入models文件
    # 以后再写ORM相关的操作的时候,就写在这个下面
    from app01 import models

    # 增加
    # 第一种方式
    models.User.objects.create(username='kevin', password='123', age=20)

    # 第二种方式
    # cls = models.User  # <class 'app01.models.User'>
    res = models.User(username='tank', password='123', age=23)  # <class 'app01.models.User'>
    res.save()

2.修改

    # 第一种方式
    models.User.objects.filter(pk=1).update(username='jerry', password='123')

    # 第二种方式
    user_obj = models.User.objects.filter(pk=3).first()
    user_obj.username = 'tom'
    user_obj.password = '888'
    user_obj.age = 16
    user_obj.save()

3.删除

    # 第一种方式
    models.User.objects.filter(pk=2).delete()

    # 第二种方式
    user_obj = models.User.objects.filter(pk=1).first()
    user_obj.delete()

4.查询

    # QuerySet对象,类似与列表套对象的形式,[obj1,obj2]
    res = models.User.objects.filter(pk=3)  # <QuerySet [<User: User object>]>
    print(res)   # QuerySet对象,
    print(res[0])  # 具体的对象 # User object
    print(res[0].username)  # 对象具体的属性  # tom
    print(res[0].password)
    print(res[0].age)
    print(models.User.objects.filter(age=16))  # <QuerySet [<User: User object>, <User: User object>]>
    # User object,用户对象,就是这个用户的一条记录信息
    res1 = models.User.objects.filter(pk=3).first()  # User object
    print(res1)
    print(res1.username)

    print(models.User.objects.filter(username='kevin', password='666').first())  # User object
    print(models.User.objects.filter(username='kevin').filter(password='666').first())  # 与上面是等价的

    # 查询所有
    res2 = models.User.objects.all()
    print(res2)  # <QuerySet [<User: User object>, <User: User object>, <User: User object>, <User: User object>]>
    for obj in res2:
        print(obj.username)


    # 查询一条记录的两种方法
    one1 = models.User.objects.filter(pk=10).first()  # 当查询记录不存在的时候返回None
    print(one1)  # User object  # None

    one2 = models.User.objects.get(pk=10)  # 查询记录不存在的时候,报错
    print(one2)  # User object  # 报错

    try:
        one2 = models.User.objects.get(pk=3)
    except Exception:
        print('数据查询不存在')

常见的十几种查询方法

models.py文件:
    def __str__(self):  # str魔法,打印时直接运行该方法
        return self.username


1.all()       查询所有数据
2.filter()     带有过滤条件的查询
3.get()        直接拿数据对象 但是条件不存在直接报错


4.first()      拿queryset里面第一个元素
5.last()       拿queryset里面最后一个元素

res = models.User.objects.all().first()
print(res)
res = models.User.objects.all().last()
print(res)


6.values value_list

# values:指定查询的字段,返回的是列表套字典
# value_list:指定查询的字段,返回的是列表套元组

# select username, password from user
res = models.User.objects.values('username', 'password')  # 指定查询的字段,返回的是列表套字典
# <QuerySet [{'username': 'tom', 'password': '888'}, {'username': 'tank', 'password': '123'}]>
print(res)

res = models.User.objects.values_list('username', 'password', 'age')  # 指定查询的字段,返回的是列表套元组
# <QuerySet [('tom', '888', 16), ('tank', '123', 23)]>
print(res)
    

7.distinct()
# 去重, 每一条数据都要完全一样,如果说带主键,一定不会重复
res = models.User.objects.all().values('password', 'age').distinct()
print(res)


8.排序order_by()
# order by age asc/desc
res = models.User.objects.all().order_by('age')  # 默认是升序排列
res = models.User.objects.all().order_by('-age')  # 降序排列
res = models.User.objects.all().order_by('age', 'id')
res = models.User.objects.all().order_by('age', '-id')
print(res)
    

9.反转reverse()
# 反转,先排序,数据要先有序才能翻转
res = models.User.objects.all().order_by('-age').reverse()  # 与order_by('age')一致
print(res)


10.计数count()
# 对表中的数据计数
# select count(*) from user
res = models.User.objects.count()
print(res)


11.排除exclude()
# 排除用户名等于kevin1,输出剩下的用户对象
res = models.User.objects.exclude(username='kevin1')
print(res)


12.存在exists()
# 返回布尔值,存在该对象返回True,不存在返回False。
# res = models.User.objects.filter(pk=3).exists()
print(res)

查看原生SQL语句

query属性

注意:返回的结果只有是QuerySet对象的时候,才有query属性,才能看sql语句。

res = models.User.objects.values_list('username', 'password', 'age') 
print(res.query)
# SELECT "app01_user"."username", "app01_user"."password", "app01_user"."age" FROM "app01_user"
    

# res = models.User.objects.create(username='kevin', password='123', age=20)  # 插入成功的这条记录对象
# print(res.query) # 会报错

在settings中配置LOGGING日志

# 查看原生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',
        },
    }
}

实际应用:

创建表、QuerySet对象、记录对象 ... 都会输出原生的SQL语句。

res = models.User.objects.values_list('username', 'password', 'age')  
print(res)
# (0.000) SELECT "app01_user"."username", "app01_user"."password", "app01_user"."age" FROM "app01_user" LIMIT 21; args=()

res = models.User.objects.create(username='kevin', password='123', age=20)  # 插入成功的这条记录对象
# (0.008) INSERT INTO "app01_user" ("username", "password", "age") VALUES ('kevin', '123', 20); args=['kevin', '123', 20]

基于双下划线查询

属性__gt=值:大于值
属性__lt=值:大于值
属性__gte=值:大于等于值
属性__lte=值:大于等于值
属性__in=[值1,值2,值3]:值在列表中
属性__range=[11,40]:值位于区间[11,40]之间
属性__contains='s':模糊查找,属性值中有s
属性__startswith='s':模糊查找,属性值以s开头
属性__endswith='s':模糊查找,属性值中有s结尾

# 针对时间有:
属性__year='2023':年
属性__month='5' :月
属性__day='28' :日
属性__week_day='5' :星期

实际应用:

# 1.年龄大于19岁的数据
# select * from user where age > 10
res = models.User.objects.filter(age__gt=19).all()
print(res)

# 2.年龄小于19岁的数据
res = models.User.objects.filter(age__lt=19).all()
print(res)

# 年龄大于等于20岁的数据 e---------->equal
res = models.User.objects.filter(age__gte=20).all()
# 小于等于20岁
res = models.User.objects.filter(age__lte=20).all()

# 年龄是 16 或者 20 或者 23
# select * from user where age in (11, 20, 23)
res = models.User.objects.filter(age__in=[11,20,23]).all()
print(res)

# 年龄在18到40岁之间的,首尾都要
# select * from user where age between 18 and 40
res = models.User.objects.filter(age__range=[11,40])
print(res)

# 查询出名字里面含有s的数据,模糊查询
# select * from user where username like '%s%'
res = models.User.objects.filter(username__contains='s').all()
print(res)

# 用户名以s开头的
# select *from user where username like 's%'
res = models.User.objects.filter(username__startswith='s').all()
# 以s结尾
res = models.User.objects.filter(username__endswith='s').all()
print(res)



models.py
User表中新增一个注册时间字段
reg_time = models.DateField(default='2023-5-1')



# 数据类型
models.DateField(auto_now=True, auto_now_add=True)   # 年月日
models.DateTimeField()  # 年月日 时分秒
""""
auto_now=False,:当你往表里面更新一条记录的时候,这个字段的值会自动把当前时间每次都更新,你不用自己写了
auto_now_add=False: 当你往表里面新插入一条记录的时候,这个字段的值会自动把当前时间写进入,你不用自己写了
"""


# 查询出注册时间是 2023 5月
# select date_format(reg_time, '%Y-%m') from user where date_format(reg_time, '%Y-%m') = '2023-05'
res =  models.User.objects.filter(reg_time__month='5') # 查5月的
res =  models.User.objects.filter(reg_time__month='5', reg_time__year='2023') # 查2023年5月的
print(res)

多表查询之表关系操作

前期表准备

# 用图书表,出版社表,作者表,作者详情表
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)

    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)

一对多的外键增删改查数据

一定要先填写没有外键的表。

1.增加

# 如何增加一本书?

# 第一种方式:直接指定外键字段的id值
models.Book.objects.create(title='洋哥自传', price=99999, publish_id=1)  # 日期是自增的

# 第二种方式:先查出对应出版社的记录对象,book表中添加对象
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.create(title='洋哥自传1', price=99, publish=publish_obj)

2.修改

# 第一种方式
models.Book.objects.filter(pk=5).update(title='哈哈哈', publish_id=2)

# 第二种方式
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.filter(pk=1).update(publish=publish_obj)

多对多外键增删改查

多对多本质上就是在操作第三张表。

# 增加
对象.外键字段.add()
# 删除
对象.外键字段.remove()
# 修改
对象.外键字段.set([ ])
# 查询
对象.外键字段.all()[0]

1.增加

# 给书籍主键为2的图书添加一个作者
book_obj = models.Book.objects.filter(pk=3).first()
print(book_obj.authors)  # app01.Author.None------------>就相当于已经到了第三张表

book_obj.authors.add(3)  # 增加了pk=3的作者

# 图书和作者是不是多对多,一本书可以有多个作者
book_obj.authors.add(2, 1)

2.删除

book_obj.authors.remove(1)
book_obj.authors.remove(2, 3)

3.修改

# book_obj.authors.set(1, 3)  # 这样写是有问题的
# TypeError: set() takes 2 positional arguments but 3 were given

book_obj.authors.set([1, 3])

book_obj.authors.set([2])  # 单个序号也要放到列表中

4.查询

# 查询图书主键为1的所有作者
print(book_obj.authors)  # app01.Author.None
print(book_obj.authors.all())  
# <QuerySet [<Author: Author object>, <Author: Author object>, <Author: Author object>]>
# QuerySet列表中对象的顺序是从表中从上到下取到赋值的

author_obj = book_obj.authors.all()[0]
print(author_obj.name)  # tank

多表查询

多表查询的方式有两种:子查询和连表查询。

正反向的概念

  • 正向:外键在我手上,我查你,就是正向查询

  • 反向:外键在我手上,你查我,就是反向查询

eg:
	book----------外键字段在book表中------------>publish-------------->正向查询
    publish-------外键字段在book表中------------->book----------------->反向查询

'''
	判断出来正反向之后,如何查询
		正向查询按字段(外键字段)
		反向查询按表名小写或者表名小写_set
'''

子查询

# 1.查询书籍主键为1的出版社 --- 一对多
# 先查询书籍,赋值给一个对象
book_obj = models.Book.objects.filter(pk=1).first()
# book --> publish   是正向查询 ----> 正向查询使用外键字段
# book对象点外键字段(publish) ---> 就相当于是出版社对象了
print(book_obj.publish)
publish_obj = book_obj.publish  # Publish object
# 输出出版社的信息
print(publish_obj.name)  # 人民出版社
print(publish_obj.addr)  # 上海
    

# 2.查询书籍主键为2的作者 --- 多对多
# book --> author   是正向查询 ----> 正向查询使用外键字段
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.authors)  # app01.Author.None
print(book_obj.authors.all())  # <QuerySet [<Author: Author object>, <Author: Author object>]>
    

# 3.查询作者jerry的电话号码 --- 一对一
# author --> authordetail   是正向查询 ----> 正向查询使用外键字段
author_obj = models.Author.objects.filter(name='jerry').first()
print(author_obj)  # Author object
author_detail_obj = author_obj.author_detail
print(author_detail_obj)  # AuthorDetail object
print(author_detail_obj.phone)  # 120




# 4.查询出版社是北京出版社出版的书 --- 一对多
# 先查出出版社的对象
publish_obj = models.Publish.objects.filter(name='北京出版社').first()
# publish --> book   是反向查询 ----> 反向查询按表名小写或者_set
print(publish_obj.book_set)  # app01.Book.None
print(publish_obj.book_set.all())  # <QuerySet [<Book: Book object>, <Book: Book object>]>
book_obj = publish_obj.book_set.all() 
print(book_obj[0].title)  # 笑傲
print(book_obj[1].title)  # 洋哥自传


# 5.查询作者是jerry写过的书  --- 多对多
author_obj = models.Author.objects.filter(name='jerry').first()
# author --> book   是反向查询 ----> 反向查询按表名小写或者_set
print(author_obj.book_set)  # app01.Book.None
print(author_obj.book_set.all())  # <QuerySet [<Book: Book object>, <Book: Book object>]>
book_obj = author_obj.book_set.all()
print(book_obj[0].title)  # 红楼梦
print(book_obj[1].title)  # 余华


# 6.查询手机号是110的作者姓名  --- 一对一
# authordetail表
author_detail_obj = models.AuthorDetail.objects.filter(phone='110').first()
# authordetail --> author   是反向查询 ----> 反向查询按表名小写或者_set
print(author_detail_obj.author)  # Author object
print(author_detail_obj.author.name)  # kevin

连表查询(基于双下划线)

基于双下划线的查询的关键是:
可以使用双下滑线语法来指定多个表之间的关系。

  • 正向,按照外键字段名__字段。
  • 反向,是表名小写__字段。

可以在查询中使用多双下划线构成一个嵌套属性路径,以便从一个表到达另一个表的字段。

# 1.查询jerry的手机号和作者姓名 
# author --> author_detail   是正向查询  ---> 外键字段
author_obj = models.Author.objects.filter(name='jerry').values('name', 'author_detail__phone')  # 外键字段__字段名
print(author_obj)  # QuerySet对象
# <QuerySet [{'name': 'jerry', 'author_detail__phone': 120}]>


# author_detail --> author   是反向查询 ---> 表名小写
author_detail_obj = models.AuthorDetail.objects.filter(author__name='jerry').values('author__name', 'phone')
print(author_detail_obj)
# <QuerySet [{'author__name': 'jerry', 'phone': 120}]>



# 2.查询书籍主键为1的出版社名称和书的名称
# book --> publish   是正向查询  ---> 外键字段
book_obj = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(book_obj)
# <QuerySet [{'title': '红楼梦', 'publish__name': '人民出版社'}]>


# publish --> book   是反向查询  ---> 表名小写
publish_obj = models.Publish.objects.filter(book__pk=1).values('book__title', 'name')
print(publish_obj)
# <QuerySet [{'book__title': '红楼梦', 'name': '人民出版社'}]>
    
    
# 3.查询书籍主键为1的作者姓名
# book --> author   是正向查询  ---> 外键字段
models.Book.objects.filter(pk=1).values('authors__name')
print(book_obj)
# <QuerySet [{'authors__name': 'jerry'}, {'authors__name': 'kevin'}, {'authors__name': 'tank'}]>


# publish --> book   是反向查询  ---> 表名小写
author_obj = models.Author.objects.filter(book__pk=1).values('name')
print(author_obj)
# <QuerySet [{'authors__name': 'jerry'}, {'authors__name': 'kevin'}, {'authors__name': 'tank'}]>


# 4.查询书籍主键是1的作者的手机号
# book ---> author ---> author_detail  正向查询   ---> 外键字段
author_detail_obj = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(author_detail_obj)
# <QuerySet [{'authors__author_detail__phone': 120}, {'authors__author_detail__phone': 110}, {'authors__author_detail__phone': 130}]>
    

 # 反向查询 ---> 表名小写
book_obj = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')
print(book_obj)
# <QuerySet [{'phone': 120}, {'phone': 110}, {'phone': 130}]>
    
    
    '''当表特别多的时候,ORM语句其实并不好写,如果你真遇到这种不好写的语句的时候,就是要原生sql语句'''

聚合查询和分组查询

聚合查询(aggregate)

就是指聚合函数:sum min max avg count

名字 函数
max 最大值
min 最小值
sum 合计
avg 平均值
count 计数
# 在orm中如何使用聚合函数
    1.关键字:aggregate
    2.需要在django.db.models中导入
    3.结果是个字典形式

实际应用:

# 求书籍表中得书的平均价格
from django.db.models import Max, Min, Avg, Sum, Count

# 单个使用
res = models.Book.objects.aggregate(Avg('price'))  # {'price__avg': 380.0}
res = models.Book.objects.aggregate(Max('price'))  # {'price__max': Decimal('658.00')}
res = models.Book.objects.aggregate(Min('price'))  # {'price__min': Decimal('99.00')}
res = models.Book.objects.aggregate(Sum('price'))  # {'price__sum': Decimal('1900.00')}
res = models.Book.objects.aggregate(Count('price'))  # {'price__count': 5}

# 也可以一起使用
res = models.Book.objects.aggregate(Max('price'),Min('price'),Sum('price'),Avg('price'),Count('price'))
# {'price__max': Decimal('658.00'), 'price__min': Decimal('99.00'), 'price__sum': Decimal('1900.00'), 'price__avg': 380.0, 'price__count': 5}
print(res)  # {'price__avg': 20248.2}

分组查询(annotate)

MySQL:

# group by分组
select * from book group by id

# 分组之后只能取得分组的依据,其他的字段不能拿到
# 需要设置一个严格模式:sql_mode='only...' 

Django:

# 1.关键字:annotate

# 2.annotate就是对models后面的表进行分组
models.Book.objects.annotate()  # 就是按照书来分组的
models.Book.objects.values('字段').annotate()  # 按照Book表中的具体字段分组的

分组查询

from django.db.models import Count, Sum, Max, Min, Avg
1.统计每一本书的作者个数
    # 书---->作者----->正向----->外键字段
    # 聚合函数一般都是配合分组使用的
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
print(res)


2.统计每个出版社卖的最便宜的书的价格
    # 1. 按照出版社分组
    # 2. 聚合查询书的价格
    # 3. 出版社查书 ------> 反向查询 -----> 表名小写
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)


3.统计不止一个作者的图书
    # 1. 统计每一本的作者个数
    # 2. 在过滤出作者个数大于1的就可以了

# 书 ------> 作者 ------> 正向 ------> 外键字段
# models.Book.objects.annotate(Count('authors'))  # 计数的参数是主键,可以不写__pk,但是建议写上
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title', 'author_num')
# res只要返回的结果是queryset,就可以一直往下点 queryset提供的方法
print(res)


4.查询每个作者出的书的总价格
    # 1. 按照作者分组
    # 2. 作者查书
    # 总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
print(res)

F与Q查询

F查询

F查询够帮你直接获取到表中的某个字段对应的值,具体应用如下

from django.db.models import F

# 1.查询卖出数大于库存数的书籍
# select * from book where sale_num > kucun;
res = models.Book.objects.filter(sale_num__gt=F('kucun'))
print(res)

# 2.将所有书籍的价格提升500块
# update app01_book set price = price+500;
res = models.Book.objects.update(price=F('price')+500)

# 3.将所有书的名称后面加上爆款两个字
# update app01_book set title = title + 'haha' ;
# models.Book.objects.update(title=F('title')+'haha') # 不能这样写
    # 1.先导入两个模块
    # 2.原有字段包裹在F中
    # 3.新的字符包裹在Value中

from django.db.models.functions import Concat
from django.db.models import Value

models.Book.objects.update(title=Concat(F('title'), Value('haha'))) 

Q查询

对于filter()方法内逗号分隔开的多个条件,都是and关系,如果想用or或者not关系,则需要使用Q

# 1.查询卖出数大于100或者价格小于600的书籍
# select * from book where sale_num > 100 or price < 600;
res = models.Book.objects.filter(sale_num__gt=100, price__lt=600) # and关系
res = models.Book.objects.filter(sale_num__gt=100).filter(price__lt=600) # and关系


# 导入Q
from django.db.models import Q
res = models.Book.objects.filter(Q(sale_num__gt=100), Q(price__lt=600))  # , 是and关系
res = models.Book.objects.filter(Q(sale_num__gt=100)|Q(price__lt=600))  # | 是or关系
res = models.Book.objects.filter(~Q(sale_num__gt=100)|Q(price__lt=600))  # ~ 是非的关系
print(res)

Q查询的高阶用法:能够以字符串作为查询字段

res = models.Book.objects.filter(Q(sale_num__gt=100) | Q(price__lt=600))

# 前端传给后端额的是字符串'price'
requests.GET.get('sort') # price
# res = models.Book.objects.filter(price__gt=100)
# res = models.Book.objects.filter('price'+'__gt'=100) 出错

q = Q()
q.connector = 'or' # 把多个查询条件改为OR关系了
q.children.append(('maichu__gt', 100))
q.children.append(('sale_num__lt',100))
# 不同条件之间使用,隔开 --- and关系 
res = models.Book.objects.filter(q)  
print(res)

想学习Q的更高级用法,自行百度

django开启事务

mysql中的事务,ACID特性。开启事务的步骤:

复习知识:事务

1、 开启事务
	start transaction;
2、提交事务
	commit;
3、回滚事务
	rollback;

django中如何开启事务

from django.db import transaction

try:
    with transaction.atomic():
        # with内部写入SQL语句
        # sql1
        models.Book.objects.create()
        # sql2
        models.Publish.objects.update()
        ...
except Exception as e:
	print(e) # 当sql语句出现异常的时候,可以在这里打印错误信息
	transaction.rollback()  # 回滚事务

posted @ 2023-04-28 16:11  星空看海  阅读(34)  评论(0编辑  收藏  举报