Django框架:模型层

目录

模型层


一、前期准备

1.关于数据库

  • 自带的sqlite3数据库对时间字段不够敏感,展示的时候会错乱

  • 使用数据库比如MySQL的时候,django的orm不会自动创建库,需要自己创建库

2.测试环境

django默认需要整个项目跑起来才能运行功能函数,如果想要测试某个py文件(主要指模型层models.py)需要准备测试环境

  • 测试环境1:pycharm中的python console

    但是这个测试环境是命令行式的环境,看起来不够直观

image-20221214112843612

  • 测试环境2:自己搭建

(1)打开manage.py文件,复制前四行

(2)并添加两行内容

import django
django.setup()
  • 最终test.py中的代码
from django.test import TestCase

# Create your tests here.
import os


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djmodel.settings')
    import django
    django.setup()

3.查看执行的SQL语句

由于Django的orm底层还是SQL语句,我们可以通过两种方法查看

  • (1)如果我们手上是一个QuerySet对象,那么可以直接点query查看SQL语句

    但是不是queryset对象就不能点query

  • (2)直接配置settings

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.create

  • 该方法有返回值,返回值是实例对象

    创建数据并且直接获取当前创建的数据对象

models.User.objects.create(name='duo', age=18)
models.User.objects.create(name='bo', age=22)
res = models.User.objects.create(name='hong',age=35)
print(res)  # 用户对象的对象是:hong

2.filter()

  • 结果是QuerySet 列表套对象
  • 括号内支持多个条件,但是是and关系
res = models.User.objects.filter(name='duo')
print(res) 

# <QuerySet <QuerySet [<User: 用户对象的对象是:duo>]>   
# 当类中没有双下str方法的时候 res为<QuerySet [<User: User object (1)>]>

image-20221214212334463

3.first() last()

QuerySet支持所用取值,但是只支持正数,并且orm不建议使用索引取值

使用索引取值没有的情况下会报错,但是使用first last方法,没有数据的情况或返回none不会报错

res = models.User.objects.filter()[1]
# res = models.User.objects.filter(pk=100)[0]  # 没有数据索引会报错
res1 = models.User.objects.filter(pk=100).first()  # first方法不会报错 
res2 = models.User.objects.filter().last()
print(res)  # 用户对象的对象是:bo
print(res1)  # None
print(res2)  # 用户对象的对象是:hong

4.update()

  • 更新数据

    根据筛选条件更新不同的个数

# 批量更新
models.User.objects.filter().update()  

# 单个更新
res = models.User.objects.filter(id=1).update(age=22)  
print(res)  # 1

# sql语句
UPDATE `app01_user` SET `age` = 22 WHERE `app01_user`.`id` = 1'; args=(22, 1)

5.delete()

  • 删除数据

    根据筛选条件删除不同的个数

models.User.objects.filter().delete()      批量删除
models.User.objects.filter(id=1).delete()  单个删除

6.all()

  • 查询所有数据

    结果是QuerySet [数据对象1,数据对象2]

    与filter不同

models.User.objects.all()

<QuerySet [<User: 用户对象的对象是:duo>, <User: 用户对象的对象是:bo>...]>

7.values()

  • 指定字段获取 数据,结果是<QuerySet [{},{},{}]> 列表套字典

  • all().values()

  • filter.values()

  • values()

models.User.objects.all().values()
models.User.objects.filter(id=1).values()
models.User.objects.values()
print(res)  # <QuerySet [{},{},{}]>   列表套字典

8.values_list()

  • 根据指定字段获取数据
  • 结果是QuerySet[(),(),(),()]列表套元组
models.User.objects.values_list('name')
------
<QuerySet [('duo',), ('bo',), ('duoduo',), ('bo',), ('hong',)]>

9.distinct()

  • 去重,但是得完全一致

image-20221214121614666

10.order_by()

  • 根据指定条件排序,默认升序

    在条件前面加个减号- 改为降序

image-20221214121746634

res = models.User.objects.all().order_by('age')

<QuerySet [<对象1>, <对象2>]>

11.get()

  • 根据条件筛选数据并直接获取到数据对象

    条件不存在拿不到数据,则会报错

models.User.objects.get(id=1)
---
用户对象的对象是:duo

12.exclude()排除

  • 去反操作
models.User.objects.exclude(age=22)
---
<QuerySet [<User: 用户对象的对象是:duoduo>, <User: 用户对象的对象是:hong>]>

13.reverse()

  • 颠倒顺序,但是被操作的顺序必须需要提前排序完成
models.User.objects.order_by('age').reverse()
---
<QuerySet [<User: 用户对象的对象是:hong>, <User: 用户对象的对象是:duo>, <User: 用户对象的对象是:bo>, <User: 用户对象的对象是:bo>, <User: 用户对象的对象是:duoduo>]>

14.count()

  • 统计结果集中数据的个数
models.User.objects.filter(age=22).count()
---
3

15.exists()

  • 判断结果集中是否含有数据,如果有则返回True 没有则返回False

    当时在Django中结果已经自带了布尔值,所有exists()显得有些多余

models.User.objects.filter(id=1).exists()
---
True

方法特点

  • 返回QuerySet对象的方法有
返回QuerySet对象
filter()
exclude()
order_by()
reverse()
distinct()
all()
  • 特殊的QuerySet
特殊的QuerySet 作用
values() 返回一个可迭代的字典序列
values_list() 返回一个可迭代的元祖序列
  • 返回具体对象的
返回具体对象的
first()
last()
get()
  • 返回布尔值的方法有:
返回布尔值
exists()
  • 返回数字的方法有
返回数字的方法
count()

三、ORM执行查询sql语句

1.方式一:通过ORM执行sql语句

通过raw方法执行查询sql语句,只支持查询,但是结果要转换成列表查看

方式1的结果,还是会被封装对象

res = models.User.objects.raw('select * from app01_user')
print(list(res))

------------
[<User: 用户操作的对象是:duo>, <User: 用户操作的对象是:bo>, <User: 用户操作的对象是:duoduo>, <User: 用户操作的对象是:bo>, <User: 用户操作的对象是:hong>]

2.方式二:通过django封装好了的pymysql模块执行sql语句

方式2的结果,会直接是数据值

  • 导入connection,产生游标对象cursor
from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursor.fetchall())

------------
(('duo',), ('bo',), ('duoduo',), ('bo',), ('hong',))

四、神奇的双下划线方法

  • QuerySet可以当做1个列表
  • QuerySet对象,是一个生成器
  • QuerySet用.all()取值的时候,不会一下子全部取出来,LIMIT21,每次只取21条

对于QuerySet对象,就可以无限制的点QuerySet对象的方法

1.__gt大于

结果是一个QuerySet对象

  • 年龄大于30
res = models.Author.objects.filter(age__gt=30)
print(res)   

----------
<QuerySet [<Author: 作家对象:余华>, <Author: 作家对象:莫言>, <Author: 作家对象:东野圭吾>]>

2.__lt小于

结果是一个QuerySet对象

res = models.Author.objects.filter(age__lt=40)
print(res)

----------
<QuerySet [<Author: 作家对象:路遥>]>

3.__gte大于等于和__lte小于等于

"只要是querset对象,就可以无限制的点queryset对象点方法"
# 查询年龄大于18的用户名的名字
res = models.User.objects.filter(age__gt=18).filter(id=2)
print(res)  # <QuerySet [<User: 用户操作的对象是:bo>]>

4.__in成员运算

res = models.Author.objects.filter(age__in=(60, 30, 50))
print(res)

----------
<QuerySet [<Author: 作家对象:余华>, <Author: 作家对象:路遥>, <Author: 作家对象:东野圭吾>]>

5.__range范围之间查询

range后元组内填的数据,左右都包含

"range填的数据,左右都包含"
res = models.Author.objects.filter(age__range=(30, 60))
print(res)

6.__contains__icontains模糊查询

  • __contains 查询字段名中含有某个字母的数据 (区分大小写)

  • __icontains查询字段名中含有某个字母的数据 (不会区分大小写)

res1 = models.Author.objects.filter(author_detail__manner__contains='s')
print(res1)

----------
__contains查询时区分大小写 


res2 = models.Author.objects.filter(author_detail__manner__icontains='s')
print(res2)

----------
__icontains查询时候不区分大小写

7.查询时间关键字

  • __year
  • __month
  • __day
  • __hour
431671101851_.pic
res = models.Book.objects.filter(publish_time__year=2022).all()
print(res)

五、ORM外键字段的创建

1.mysql中的外键关系

  • 一对多:外键字段建立在多的一方

  • 多对多:外键字段统一建在第三张关系表中,在django中不用自己创建,只需创建外键后django会帮我们自动创建一个表出来

  • 一对一:建在任何一方都可以,但是应该建立在热数据表也就是查询频率较高的表中

关系的判断采用换位思考原则

image-20221215154012573

2.orm外键字段的创建

  • 先创建基表,再创建外键字段

(1)一对多,ORM与MySQL

  • orm会自动帮我们把外键字段后加_id的后缀,所以我们不用自己添加_id后缀

(2)多对多

  • ORM比MySQL更多变化
  • ORM:外键字段可以直接建在某张查询频率较高的表中在orm内部会自动帮我们创建第三张表这个外键字段是虚拟字段在表中并不会显示,只是告诉orm帮我们创建第三张关系表
  • MySQL:自己创建第三张关系表并创建外键字段

(3)一对一

  • 外键字段可以直接建在某张查询频率较高的表中

通过ForiegnKeyOneToOne创建外键的关键字,同步到表中之后,会自动添加_id的后缀

而多对多的外键关键字MangToMany 创建的是虚拟外键,是告诉ORM帮我们创建多对多的关系表

image-20221215163052422

(实例)图书、出版社、作者数据表

"""先创建几张基表,再再几张基表之上去创建表与表之间的关系"""


class Book(models.Model):
    """图书表"""
    title = models.CharField(max_length=32, verbose_name='书名')
    price = models.DecimalField(max_digits=8, decimal_places=3, verbose_name='价格')
    publish_time = models.DateTimeField(auto_now_add=True, verbose_name='出版日期')

    "书籍表和出版社表是一对多的关系,也就是一本书对应一个出版社,一个出版社对应多本书,所以外键字段建立在多的那方"
    # 与出版社的外键,通过ForeignKey,参数to为建立关系的类名
    publish = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
    "CASCADE对应的是及联更新删除,在django1.X版本都是默认及联更新删除,但是在django2以上版本需要自己声明 "

    "书籍表与作者表的关系是多对多,一本书可以对应多个作者,一个作者也可以对应多本书,所以外键字段建立在那方都可以,但是书籍表更为常用,所以建立在数据表中"
    # 与作者的外键,但是对于Book表来说这是一个虚拟字段,相当于是告诉ORM在MySQL数据库中去建立一张新表书籍与作者的关系表
    author = models.ManyToManyField(to='Author')
    "多对多的表不需要建立及联更新删除"

    def __str__(self):
        return f"书籍对象:{self.title}"



class Publisher(models.Model):
    """出版社表"""
    name = models.CharField(max_length=32, verbose_name='出版社名')
    location = models.CharField(max_length=32, verbose_name='出版社地址')


    def __str__(self):
        return f"出版社对象:{self.name}"

class Author(models.Model):
    """作者表"""
    name = models.CharField(max_length=32, verbose_name='作者名')
    age = models.IntegerField(verbose_name='年龄')

    "作者表和作者详情表是一对一的关系,一个作者可以对应一个作者详情,一个作者详情对应一个作者,外键建在哪方都可以,但是建立在热数据的表中更好"
    author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)

    def __str__(self):
        return f"作家对象:{self.name}"

class AuthorDetail(models.Model):
    """作者详情表"""
    phone = models.BigIntegerField(verbose_name='手机号')
    manner = models.CharField(max_length=32, verbose_name='写作风格')
    def __str__(self):

        return f"详情对象:{self.manner}"

3.增删改查

  • 一对多

    book表

针对一对多,插入数据可以之间填写表中的实际数据

image-20221215110138781

也可以填写表中的虚拟字段(类中的字段名)

image-20221215110128660

一对一与一对多一致

  • 多对多

第三张orm自动创建的关系表,在orm中点不出来,想要添加关系得通过 含有多对多外键字段的对象去添加 多对多关系

(1)添加数据

对于一对多关系

  • 1.直接插入数据
models.Book.objects.create(title='兄弟', price=999.87, publish_id=3)
models.Book.objects.create()
  • 2.插入对象
    publisher_obj = models.Publisher.objects.filter(pk=2).first()
    models.Book.objects.create(title='红高粱', price=888.68, publish=publisher_obj)
    models.Book.objects.filter(pk=10).delete()
    models.Book.objects.filter(pk=8).delete()

(2)多对多绑定关系

多对多

  • 1.通过对象来添加数据
book_obj = models.Book.objects.filter(pk=3).first()
book_obj.author.add(3,2)
  • 2.通过对象来添加对象
book_obj1 = models.Book.objects.filter(pk=4).first()

author_obj1 = models.Author.objects.filter(pk=1).first()
author_obj2 = models.Author.objects.filter(pk=2).first()
book_obj1.author.add(author_obj1,author_obj2)

(3)修改关系

obj.表名小写.set()中set的参数应该是一个可迭代对象列表或者元组

  • 1.参数为数据
book_obj3 = models.Book.objects.filter(pk=3).first()
book_obj3.author.set([3])
  • 2.参数为对象
book_obj3 = models.Book.objects.filter(pk=3).first()

author_obj2 = models.Author.objects.filter(pk=2).first()
author_obj1 = models.Author.objects.filter(pk=1).first()

book_obj3.author.set((author_obj2, author_obj1))

(4)删除关系

  • 1.参数为数据
book_obj3 = models.Book.objects.filter(pk=3).first()
book_obj3.author.remove(1)

  • 2.参数为对象
author1 = models.Author.objects.filter(pk=1).first()
book_obj4 = models.Book.objects.filter(pk=4).first()
book_obj4.author.remove(author1)

六、ORM的跨表查询

  • 一个数据库应该尽量只对应一个Django项目,不要出现多个django项目使用同一额数据库,会出现报错

1.mysql中的跨表查询思路

(1)子查询

  • 分布操作,将一个查询结果当作另一个表的条件

(2)连表查询

  • inner join
  • left join
  • right join

2.正反向查询的概念

  • 概念

正向查询:由外键字段所在的表数据查询关联的表数据

反向查询:没有外键字段的表数据查询关联的表数据

  • ORM跨表查询的口诀(重要):
    正向查询按外键字段
    反向查询按表名小写

3.基于对象的跨表查询(子查询)

相当于mysql中的子查询

  • (1)先根据已知条件获取到一个具体的数据对象

  • (2)基于该数据对象运用正反向查询的口诀完成

1.正向

  • 1.查询主键为1的书籍对应的出版社名称
    # 先根据条件获取数据对象
    book_obj = models.Book.objects.filter(pk=1).first()
    # 在判断正反向的概念,由书获取出版社 --》从由主键的表差没主键的表是正向
    res = book_obj.publish.name
    print(res) # 长江文艺
  • 2.查询主键为4的书籍对应的作者姓名
# 先根据条件获取对象
book_obj = models.Book.objects.filter(pk=4).first()
res = book_obj.author.name
print(res) 
这个时候点name得到的结果是None,然后只要点all就可以拿到所有点对象,在通过values 获取想要点字段名点值

res = book_obj.author.all().values('name')
print(res)  #<QuerySet [{'name': '莫言'}, {'name': '路遥'}]>
  • 3.查询余华的电话号码
# 先根据条件获取对象
author_obj = models.Author.objects.filter(name='余华').first()
res = author_obj.author_detail.phone
print(res)  #111

2.反向

  • 4.查询长江文艺出版过的书籍
# 先根据条件获取数据对象
publish_obj = ls.Publisher.objects.filter(name='长江文艺').first()
# 根据数据对象判断  出版社没有外键字段,所以是反向,所以要点表名小写,然后
res = publish_obj.book_set.all().values('title')
print(res)

--------------
<QuerySet [{'title': '活着'}, {'title': '平凡的世界'}, {'title': '嫌疑人x的献身'}]>
  • 5.查询余华写过的书籍
# 先根据条件获取数据对象
author_obj = models.Author.objects.filter(name='余华').first()
# 作者表中没有外键,所以是反向,要点获取表名的小写
res = author_obj.book_set.all().values('title')
print(res)

--------------
<QuerySet [{'title': '活着'}, {'title': '细雨中的呼喊'}, {'title': '兄弟'}]>
  • 6.查询电话号码是111的作者姓名
# 先根据条件获取数据对象
author_detail_obj = models.AuthorDetail.objects.filter(phone=111).first()
res = author_detail_obj.author.name
print(res)
# 余华

4.基于上下划线的跨表查询

  • 反向的口诀也使用与values或者values_list中使用

一条出结果

1.正向

  • 1.查询主键为1的书籍对应的出版社名称
res = models.Book.objects.filter(pk=1).values('publish__name')

print(res) #<QuerySet [{'publish__name': '长江文艺'}]>
  • 2.查询主键为4的书籍对应的作者姓名
res = models.Book.objects.filter(pk=4).values('author__name')
print(res)
# <QuerySet [{'author__name': '莫言'}, {'author__name': '路遥'}]>
  • 3.查询余华的电话号码
res = models.Author.objects.filter(name='余华').values('author_detail__phone')
print(res)

#  <QuerySet [{'author_detail__phone': 111}]>

2.反向

  • 4.查询长江文艺出版过的书籍
res = models.Publisher.objects.filter(name='长江文艺').values('book__title')
print(res)
#<QuerySet [{'book__title': '活着'}, {'book__title': '平凡的世界'}, {'book__title': '嫌疑人x的献身'}]>
  • 5.查询余华写过的书籍
res = models.Author.objects.filter(name='余华').values('book__title')
print(res)  
# <QuerySet [{'book__title': '活着'}, {'book__title': '细雨中的呼喊'}, {'book__title': '兄弟'}]>
  • 6.查询电话号码是111的作者姓名
res = models.AuthorDetail.objects.filter(phone=111).values('author__name')
print(res)

5.进阶操作

  • 1.查询主键为1的书籍对应的出版社名称
res = models.Publisher.objects.filter(book__pk=1).first().name
print(res)  # 长江文艺
res2 = models.Publisher.objects.filter(book__pk=1).values('name')
print(res2)  # <QuerySet [{'name': '长江文艺'}]>
  • 2.查询主键为4的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=1).first().name
print(res)  # 余华
res1 = models.Author.objects.filter(book__id=4).values('name')
print(res1)  # <QuerySet [{'name': '莫言'}, {'name': '路遥'}]>
  • 3.查询余华的电话号码
res = models.AuthorDetail.objects.filter(author__name='余华').values('phone')
print(res)  
# <QuerySet [{'phone': 111}]>
  • 4.查询长江文艺出版过的书籍
    res = models.Book.objects.filter(publish__name='长江文艺').first().title
    print(res)  # 活着
    res1 = models.Book.objects.filter(publish__name='长江文艺').values('title')
    print(res1)
    # <QuerySet [{'title': '活着'}, {'title': '平凡的世界'}, {'title': '嫌疑人x的献身'}]>
  • 5.查询余华写过的书籍
res = models.Book.objects.filter(author__name='余华').values('title')
print(res)
# <QuerySet [{'title': '活着'}, {'title': '细雨中的呼喊'}, {'title': '兄弟'}]>
res1 = models.Book.objects.filter(author__name='余华').first().title 
print(res1)
# 活着
  • 6.查询电话号码是111的作者姓名
res = models.Author.objects.filter(author_detail__phone=111).values('name')
print(res)
# <QuerySet [{'name': '余华'}]>
  • 查询主键为4的书籍对应的作者的电话号码
# 通过作者表查询
res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')
print(res)
# 通过书籍表查询
res1 = models.Book.objects.filter(pk=4).values('author__author_detail__phone')
print(res1)
# 通过作者详情表查询
res2 = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
print(res2)
   
------------
<QuerySet [{'author_detail__phone': 222}, {'author_detail__phone': 33}]>
<QuerySet [{'author__author_detail__phone': 222}, {'author__author_detail__phone': 33}]>
<QuerySet [{'phone': 222}, {'phone': 33}]>
  • 修改外键字段的增删改查

七、图书管理系统讲解

1.表设计

  • 先考虑普通字段,再考虑外键字段

2.网页搭建

  • 首页搭建
  • 图书列表展示页面

image-20221219084011512

八、 聚合查询

聚合函数:max min sum count avg,一般和分组查询配合使用

# 导入聚合函数
from django.db.models import Max,Min,Sum,Count,Avg

在ORM中指出单独使用聚合函数,通过关键字aggregate

from django.db.models import Max, Min, Sum, Count, Avg

models.Book.objects.aggregate(Max('price'), Count('id'), 最小价格=Min('price'), )

--------
{'最小价格': Decimal('32.450'), 'price__max': Decimal('999.870'), 'id__count': 8}

九、分组查询

通过关键字annotate,进行分组查询

1.分组依据

  • models后面点什么就是按照什么分组

    分组依据:

    models.表名.objects.annotate()       按照整张表分组
    models.表名.objects.values('字段名').annotate   按照values括号内指定的字段分组
    

2.例

  • 1.查询每一本书的作者
models.Book.objects.annotate(author_sum=Count('author__pk')).values('title','author_sum')


-------
 <QuerySet [{'title': '活着', 'author__num': 1}, {'title': '细雨中的呼喊', 'author__num': 1}, {'title': '蛙', 'author__num': 1}, {'title': '平凡的世界', 'author__num': 2}, {'title': '嫌疑人x的献身', 'author__num': 1}, {'title': '白夜行', 'author__num': 1}, {'title': '兄弟', 'author__num': 1}, {'title': '红高粱', 'author__num': 1}]>
  • 2.筛选作者数量大于1的书
models.Book.objects.annotate(author__num=Count('author__pk')).filter(author__num__gt=1).values('title')


-----------
 <QuerySet [{'title': '平凡的世界'}]>
  • 3.查询每个作者的书的总价格
models.Author.objects.annotate(总价=Sum('book__price')).values('name','总价')

-----------
<QuerySet [{'name': '余华', '总价': Decimal('1198.080')}, {'name': '莫言', '总价': Decimal('976.110')}, {'name': '路遥', '总价': Decimal('32.450')}, {'name': '东野圭吾', '总价': Decimal('151.070')}]>

image-20221216121500313

如果执行orm分组查询报错,并且有关键字sql_

show variables like '%mode%'

image-20221216115857999

十、F查询与Q查询

1.F查询

当查询条件不是明确的,也需要从数据库中获取的时候,就需要使用F查询,获取表中指定字段对应的数据作为查询条件

from django.db.models import F
image-20221218154511657
  • 1.将所有书的价格减少800
models.Book.objects.update(price=F('price')-800)
image-20221218154549598
  • 2.将所有书的名称后面追加爆款

    针对字符串类型的数据拼接需要使用模块:Concat字符串拼接 和Value

from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(title=Concat(F('title'), Value('新款')))

2.Q查询

可以将多个查询条件的关系做修改,Q查询中间的符号逗号是and,|是or,~是not

res = models.Book.objects.filter(Q(pk=1), Q(price__gt=80))
res2 = models.Book.objects.filter(Q(pk=1)| Q(price__gt=80))
res3 = models.Book.objects.filter(~Q(pk=1)| Q(price__gt=80))
print(res)
print(res2)
print(res3)
      
      
      
<QuerySet [<Book: 书籍对象:活着>]>
<QuerySet [<Book: 书籍对象:活着>, <Book: 书籍对象:细雨中的呼喊>, <Book: 书籍对象:兄弟>, <Book: 书籍对象:红高粱>]>
<QuerySet [<Book: 书籍对象:活着>, <Book: 书籍对象:细雨中的呼喊>, <Book: 书籍对象:蛙>, <Book: 书籍对象:平凡的世界>, <Book: 书籍对象:嫌疑人x的献身>, <Book: 书籍对象:白夜行>, <Book: 书籍对象:兄弟>, <Book: 书籍对象:红高粱>]>    

3.Q查询进阶操作

可以使得查询条件的左侧变成字符串

查询条件的左边是字段名/变量名

q_obj = Q()  # 1.产生q对象
q_obj.connector = 'or'  # 默认多个条件的连接是and可以修改成or
q_obj.children.append(('pk', 1))  # 2.添加查询条件
q_obj.children.append(('price__gt', 100))  # 支持添加多个查询条件
res = models.Book.objects.filter(q_obj)  # 查询支持直接添写q对象
res1 = models.Book.objects.filter(q_obj).first()  # 查询支持直接添写q对象

print(res)
<QuerySet [<Book: 书籍对象:活着>, <Book: 书籍对象:细雨中的呼喊>, <Book: 书籍对象:兄弟>, <Book: 书籍对象:红高粱>]>

print(res1)
书籍对象:活着

print(res1.author.all())
<QuerySet [<Author: 作家对象:余华>]>

十一、ORM查询优化

1.ORM的查询特点

(1)ORM的查询默认都是惰性查询

当写了一条ORM,在我们不用的时候,ORM就不会启用这条ORM语句进行查询,要用的时候才会去访问数据库

image-20221219153344555

(2)ORM的查询自带分页处理

ORM每次查询的时候,最多21条

image-20221219153504533

(3)查询关键字only与defer (去反操作)

  • only

    only可以将括号内列举的字段封装到数据对象内,当我们通过点点方式获取括号内的字段对应的值时,直接通过封装好的对象获取当我们没有想获取括号外的字段时,就会通过sql语句进行查询

image-20221219155349841
  • defer

    与only相反

    defer可以将括号内列举的字段封装到数据对象内,当我们通过点点方式获取括号内的字段对应的值时,会通过sql语句查询当我们没有想获取括号外的字段时,则直接获取

image-20221219155535357

(4)sql优化

  • select_related 底层是连表操作

    对于一对一字段(OneToOneField)和外键字段(ForeignKey,一对多),可以使用select_related 来对QuerySet进行优化

    在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库

model.XXX.objects.all().select_related('外键字段')
model.XXX.objects.all().select_related('外键字段__外键字段')

image-20221219161204980

  • prefetch_related 底层子查询

    对于一对一字段(OneToOneField)和一对多字段(ForeignKey),可以使用prefetch_related()来进行优化。

    prefetch_related()括号内填写的一对一、一对多字段,基于子查询将其结果数据封装成对象。

但是两者在使用上的时候差不多

image-20221219161510218

预取prefetch

v.预先载入 n.预先载入

十二、ORM事务操作

https://docs.djangoproject.com/zh-hans/2.2/topics/db/transactions/#tying-transactions-to-http-requests

1.事务的四大特性 ACID

A:atomicity原子性一个事务是一个不可分割的工作单位,要么同时成功,要么同时失败

C:consistency一致性,事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

I:isolation隔离性,一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

D:durability持久性,持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

2.SQL相关

(1)关键字

  • 开启事务
START TRANSACTION; 
  • 手动提交事务
COMMIT;
  • 取消事务(即回滚)
ROLLBACK;
  • 设置保存点
SAVEPOINT

(2)事务的隔离级别

MySQL中,事务有4种隔离级别,分别为READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和SERIALIZABLE(串行化)。

  • READ UNCOMMITTED 未提交读-脏读

image-20221219163721897

事务A读取到了事务已经修改但未提交的数据,这种数据就叫脏数据,是不正确的

  • READ COMMITTED 读已提交

image-20221219163918937

不可重复读:对于事务A多次读取同一个数据时,由于其他是事务也在访问这个数据,进行修改且提交,对于事务A,读取同一个数据时,有可能导致数据不一致,叫不可重复读

  • REPEATABLE READ 可重复读-幻读

image-20221219164008166

幻读:原因:因为mysql数据库读取数据时,是将数据放入缓存中,当事务B对数据库进行操作:例如删除所有数据且提交时,事务A同样能访问到数据,这就产生了幻读。
问题:解决了可重复读,但是会产生一种问题,错误的读取数据,对于其他事务添加的数据也将访问不到

  • SERIALIZABLE 串行化

image-20221219164159427

串行化:事务A和事务B同时访问时,在事务A修改了数据,而没有提交数据时,此时事务B想增加或修改数据时,只能等待事务A的提交,事务B才能够执行。

3.ORM事务相关操作

  • 方式一:配置文件数据库添加相关键值对

    有效范围:全局有效

"ATOMIC_REQUESTS":True

每次请求涉及的orm操作同属于一个事务

如果报错会自动回滚

  • 方式二:通过装饰器配置事务

    有效范围:局部有效

from django.db import transaction
@transaction.atomic
def 函数名(request):
    orm/sql 
  • 方式三:with上下文管理

    有效范围:代码块有效

    将with体代码中的orm或者sql语句支持事务

def 函数名(request):
    with transacion.atomic():
        orm/sql               

十三、ORM模型层字段类型

1.常用字段

字段类型 作用 特点
AutoField 自增 必须填入参数primary_key=True,如果没有该自增列django则会自动创建
CharField 字符串类型 必须提供提供max_length参数,来表示字符长度
IntergerField 整形 范围:-2147483648 ~ 2147483647
BigIntergerField 长整型 -9223372036854775808 ~ 9223372036854775807
DecimalField 10进制小数 参数:
max_digits:最大长度
decimal_places:小数点后几位
DateField 日期类型 格式:YYYY-MM-DD
年-月-日
DateTimeField 日期类型 格式:YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
年-月-日 时-分-秒
BooleanField 布尔值类型 传布尔值自动存0或者1
FileField 文件类型
属于字符串类型
传文件对象,自动保存到提前配置好的路径下,并在数据库保存路径
upload_to = "":上传文件的保存路径
storage = None:存储组件,默认django.core.files.storage.FileSystemStorage
TextField 文本类型 存储大文本
EmailField 邮箱类型
属于字符串类型
Django Admin以及ModelForm中提供验证机制
ImageField 图片类型
属于字符串类型
路径保存在数据库,文件上传到指定目录
upload_to = "":上传文件的保存路径
storage = None:存储组件,默认django.core.files.storage.FileSystemStorage width_field=None:上传图片的高度保存的数据库字段名(字符串) height_field=None:上传图片的宽度保存的数据库字段名(字符串)

2.不常用字段

字段 类型 特点
BigAutoField bigint自增列 必须填入参数 primary_key=True 当model中如果没有自增列,则自动会创建一个列名为id的列
SmallIntegerField 小整数 范围:-32768 ~ 32767
PositiveSmallIntegerField 正小整数 范围:0 ~ 32767
PositiveIntegerField 正整数 范围:0 ~ 2147483647
BigIntegerField 长整型(有符号的) 范围:-9223372036854775808 ~ 9223372036854775807
BooleanField 布尔值 有True和False
NullBooleanField 可以为空的布尔值 可以为空
IPAddressField IP类型 属于 字符串类型 Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField 属于 字符串类型 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 URL类型 属于 字符串类型 Django Admin以及ModelForm中提供验证 URL
SlugField 属于 字符串类型 Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField 属于 字符串类型 格式必须为逗号分割的数字
UUIDField 属于 字符串类型 Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField 文件路径类型 属于 字符串类型 Django Admin以及ModelForm中提供读取文件夹下文件的功能 path:文件夹路径 match=None:正则匹配 recursive=False:递归下面的文件夹 allow_files=True:允许文件 allow_folders=Fals:允许文件夹
TimeField 时间类型 HH:MM[:ss[.uuuuuu]] 时分秒
DurationField 长整数 时间间隔,数据库中按照bigint存储 ORM中获取的值为datetime.timedelta类型
FloatField 浮点型 可以保存小数
BinaryField 二进制型 存储二进制

3.自定义字段

(1)继承models.Field

(2)使用两个方法__init__db_type

class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length, *args, **kwargs)  # max_length需要按照关键字参数的方式传进去

    def db_type(self, connection):
        return 'char(%s)' % self.max_length

十四、常用字段参数

1.常用属性

字段 sql语句
null null=True
该字段可以为空
unique unique=True
该字段是唯一的
default 设置默认值,一般设置可以为null
db_index 建立索引字段加快搜索
max_digits 小数最大长度
max_length 最大长度
decimal_places 小数点后面几位数
auto_now 当修改的时候自动保存当前时间
auto_now_add 当创建时自动保存当前时间
primary_key 主键
verbose_name 注释
choices 当某个字段的可能性能够被列举完全的情况下使用
class MyCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length, *args, **kwargs)  # max_length需要按照关键字参数的方式传进去

    def db_type(self, connection):
        return 'char(%s)' % self.max_length


# choices 列举可能的完全性
class User(models.Model):
    name = models.CharField(max_length=32)
    info = MyCharField(max_length=64)
    # 提前列举好对应关系
    gender_choice = (
        (1, '男性'),
        (2, '女性'),
        (3, '其他'),
    )
    # 存取的还是数字,但是只要是在gender_choice中有对应关系的,取出来就是男性或者女性
    gender = models.IntegerField(choices=gender_choice,null=True)
  • choices

    在pycharm的console中演示

>>> from app01 import models
>>> user_obj = models.User.objects.filter(pk=1).first()
>>> user_obj.get_gender_display()

'男性'

通过

obj.get_关联字段_display()

的方式来获取对应关系

2.外键字段参数

外键字段参数 作用
to 设置要关联的表
to_field 设置要关联的表的字段
on_delete 当删除关联表中的数据时,当前表与其关联的行为
  • on delete的参数配置

    常用:models.CASCADE 删除关联数据,与之关联也删除

1、models.CASCADE
    级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
2、models.SET_NULL
    当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
3、models.PROTECT
    当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
4、models.SET_DEFAULT
    当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
5、models.SET()
    当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
6、models.DO_NOTHING
    什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似

十五、多对多三种创建方式

1.全自动创建

class Book(models.Model):
    ...
    author = models.ManyToManyField(to='Author')
class Author(models.Model):
    ...
  • 优势:自动创建第三章表,并且提供了add,remove,set,clear四种操作关系
  • 劣势:第三表扩展性很差,无法创建更多的字段

2.纯手动创建

class Book(models.Model):
    ...
class Author(models.Model):
    ...
    
# 第三张表
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to='Author')
    # 其他更多字段
    ...
    
  • 优势:第三张表完全由自己创建,扩展性强
  • 劣势:编写繁琐,并且不支持add,remove,set,clear四种操作关系,也没有正反向的概念

3.半自动创建

class Book(models.Model):
    ...
    # 在表中创建外键字段,但是要通过参数告诉orm我们已经创建了外键关系的第三章表
    author = models.ManyToManyField(to='Author',
                                    trough='Book2Author',
                                    trough_fields=('book','author')
                                   )
class Author(models.Model):
    ...
    
# 第三张表
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to='Author')
    # 其他更多字段
    ...
    
  • 优势:第三张表完全自己创建,扩展性强,正反向的概念可以运用
  • 劣势:但是不支持add,remove,set,clear四种操作关系
posted @ 2022-12-14 22:09  Duosg  阅读(66)  评论(0编辑  收藏  举报