【9.0】Django框架之模型层

【一】前言

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": "1314521",
        # 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

    # (1)增加数据
    # register_time (1)支持自己指定传值
    res = models.User.objects.create(name="dream", age=18, register_time='2023-06-09')
    # 返回值为对象本身
    print(res)  # User object
    
    # register_time (2)支持传入日期对象
    import datetime
    # 生成一个当前的时间对象
    c_time = datetime.datetime.now()
    user_obj = models.User.objects.create(name="chimeng", age=18, register_time=c_time)
    # 保存数据
    user_obj.save()

【2】数据的删除

# 【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】数据的更改

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

【补充】查看内部SQL语句的方式

【1】方式一

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

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

【4】其他方法总结

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

    # # 【1】增加数据
    # # register_time (1)支持自己指定传值
    # res = models.User.objects.create(name="dream", age=18, register_time='2023-06-09')
    # # 返回值为对象本身
    # print(res)  # User object
    # # register_time (2)支持传入日期对象
    #
    # import datetime
    #
    # # 生成一个当前的时间对象
    # c_time = datetime.datetime.now()
    # user_obj = models.User.objects.create(name="chimeng", age=18, 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)方式二
    # # 返回的就是当前的数据对象
    # # 不推荐使用 : 如果查询的数据不存在会直接报错 ,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中的第一个元素
    # (5) last() - 拿queryset中的最后一个元素

    # (6) values() - 可以指定获取的数据字段
    res = models.User.objects.values('name')
    # 返回的数据格式为列表套字典 - 本质上是一个 QuerySet 对象 ,而不是真的列表
    print(res)  # <QuerySet [{'name': 'xiaomeng'}]>
    # (7) values_list() - 可以指定获取的数据字段
    res_list = models.User.objects.values_list('name', 'age')
    # 返回的数据格式为列表套元祖 - 本质上是一个 QuerySet 对象 ,而不是真的列表
    print(res_list)  # <QuerySet [('xiaomeng', 18)]>
    # 该语句可以查看当前执行命令的 SQL 语句 - 只有queryset对象才能使用该方法
    print(res_list.query)  # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`

    # (8) distinct() - 去重(带有主键就意味着数据存在不一样的地方,所以一定要去除主键后再去重)
    # 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)

【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)年龄大于等于35岁的数据
    res = models.User.objects.filter(age__gte=35)
    print(res)
    # (4)年龄小于等于35岁的数据
    res = models.User.objects.filter(age__lte=35)
    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)查询出名字中含有 n 的数据  -- 模糊查询
    res = models.User.objects.filter(name__contains='n')
    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)
    # 该字段不是给models看的,而是给校验行组件使用的
    email = models.EmailField()


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    # 一对一
    author_detail = models.OneToOneField(to='AuthDetail')


class AuthDetail(models.Model):
    phone = models.BigIntegerField()
    addr = models.CharField(max_length=40)
  • 重载数据库
python36 manage.py makemigrations

python36 manage.py migrate

【2】外键的增删改查

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

1.1 外键的增加

# (1)一对多的外键的增删改查
# (1.1)外键的增加 - 直接写实际字段
models.Book.objects.create(title="三国演义", price=1369.25, publish_id=1)
# (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 外键的修改

# (2)一对多的外键的修改
#  - 直接写实际字段
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 删除

支持多个参数/支持对象

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

2.3 更改

先删除后增加

# (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()
# 不要加任何参数
book_obj.authors.clear()

【补充】正反向的概念

  • 正向

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

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

正向查询按字段

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

​ __set

​ ...

【五】多表查询

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

# [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())  # <QuerySet [<Author: Author object>]>

# (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()
        当查询的结果只有一个的时候就不需要加
    '''

# (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)  # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>

# (5)查询作者是dream写过的书
# 先拿到作者对象
author_obj = models.Author.objects.filter(name="dream").first()
# 作者查书 - 主键在书 - 反向
res = author_obj.book_set.all()
print(res)  # <QuerySet [<Book: Book object>]>

# (5)查询手机号是 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】联表查询(基于双下划线的跨表查询)

# [2] 基于双下划线的跨表查询
# (1)查询dream的手机号和作者的姓名
# 正向:先查询到作者信息再 .value(需要查询信息的表__需要查询的字段,其他字段)
res = models.Author.objects.filter(name="dream").values('author_detail__phone', 'name')
print(res)  # <QuerySet [{'author_detail__phone': 110, 'name': 'dream'}]>
# 反向:先拿到详情,再用作者详情关联作者表,通过 __字段的方法 过滤出我们想要的指定数据
res = models.AuthDetail.objects.filter(author__name="dream").values('phone', 'author__name')
# AuthDetail.objects.filter(author__name="dream")
print(res)  # <QuerySet [<AuthDetail: AuthDetail object>]>
# AuthDetail.objects.filter(author__name="dream").values('phone','author__name')
print(res)  # <QuerySet [{'phone': 110, 'author__name': 'dream'}]>

# (2)查询书籍主键ID为1的出版社名字和书的名字
# 正向:先过滤出书籍ID为1的书籍对象,再去关联出版者表,利用__字段取值
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)  # <QuerySet [{'title': '三国演义', 'publish__name': '东方出版社'}]>
# 反向:先查询到指定出版社,再从出版社反向找到书籍名字
res = models.Publish.objects.filter(book__id=1).values('name', 'book__title')
print(res)  # <QuerySet [{'name': '东方出版社', 'book__title': '三国演义'}]>

# (3)查询书籍主键ID为1的作者姓名
# 先拿到 书籍主键ID为1的对象,再关联作者信息表,通过__字段取值
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)  # <QuerySet [{'authors__name': 'dream'}]>
# 反向 : 先拿到 书籍ID为1的作者数据再去取作者的名字
res = models.Author.objects.filter(book__id=1).values('name')
print(res)  # <QuerySet [{'name': 'dream'}]>

# 查询书籍主键是1的作者的手机号
# book author authordetail
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)  # <QuerySet [{'authors__author_detail__phone': 110}]>

'''
    只要掌握了正反向的概念
    以及双下划线查询
    就可以无限跨表
    '''

【六】聚合查询

    # 聚合查询
    # 聚合查询通常情况下都是配合分组一起使用的
    '''
    只要是和数据库相关的模块
        基本上都在 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}

【七】分组查询

# 分组查询
'''
    MySQL中的分组查询
        分组之后只能获取到分组的依据,组内其他字段都无法获取
            严格模式中可以修改
    '''

# (1)统计每一本书的作者个数
# models 后面跟的是什么,就是按什么分组
# res = models.Book.objects.annotate()
#
# '''
# author_number 是我们自己定义的字段,用来存储统计出来的每本书的作者个数
# '''
# res = models.Book.objects.annotate(author_number=Count('authors')).values('title','author_number')
# # 等价于
# # res = models.Book.objects.annotate(author_number=Count('authors__pk')).values('title','author_number')
# print(res) # <QuerySet [{'title': '三国演义', 'author_number': 1}, {'title': '红楼梦', 'author_number': 0}, {'title': '水浒传', 'author_number': 0}, {'title': '论语', 'author_number': 0}, {'title': '孙子兵法', 'author_number': 0}, {'title': '镇魂街', 'author_number': 0}]>

# (2)统计每个出版社最便宜的书的价格
# res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
# print(res) # <QuerySet [{'name': '东方出版社', 'min_price': Decimal('555.25')}, {'name': '北方出版社', 'min_price': Decimal('888.25')}]>

# (3)统计不止一个作者的图书
# (3.1)先按照图书分组
# (3.2)过滤出不止一个作者的图书
# 我的数据有限,我统计的是大于 0 的作者的图书
# res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=0).values('title',
# '''
# 只要ORM语句得到的是 一个 queryset 对象
#     那么就可以继续无限制的调用封装 的方法
# '''                                                                                              'author_num')
# print(res)  # <QuerySet [{'title': '三国演义', 'author_num': 1}]>

# (4)查询每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')
print(res)  # <QuerySet [{'name': 'dream', 'sum_price': Decimal('1369.25')}, {'name': 'hope', 'sum_price': None}, {'name': 'sad', 'sum_price': None}]>

'''
    如果想按照指定的字段分组该如何处理
        # 如果 annotate 前面没东西 则会按照 Book 分组 ,如果前面有参数 就会按照前面的参数进行分组 price
        models.Book.objects.values('price').annotate()
    '''

【八】F与Q查询

【1】F查询

# F与Q查询

from django.db.models import F, Q

# (1)查出卖出数大于库存数的书籍
# F 查询 : 帮助我们直接获取到表中的某个字段对应的数据
res = models.Book.objects.filter(sales__gt=F('stock'))
print(res)  # <QuerySet [<Book: 水浒传>]>

# (2) 将所有书籍的价格提升 50
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) # 6 - 影响到了 6 条数据

【2】Q查询

# F与Q查询

from django.db.models import F, Q

# (1)查询卖出数大于100或者价格小于500的书籍

# (1.1)直接使用 filter 查询数据,逗号隔开,里面放的参数是 and 关系
res = models.Book.objects.filter(sales__gt=100, price__lt=500)
print(res)  # <QuerySet []>

# (1.2)直接使用 Q 查询数据,逗号隔开,里面放的参数还是 and 关系
res = models.Book.objects.filter(Q(sales__gt=100), Q(price__lt=500))
print(res)  # <QuerySet []>

# (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)  # <QuerySet [<Book: 三国演义爆款>, <Book: 水浒传爆款>, <Book: 论语爆款>, <Book: 孙子兵法爆款>]>

# (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)

【九】Django中如何开启事务

【1】ACID是数据库事务的四个关键特性

  • 它代表了原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

    • 原子性(Atomicity):

      • 事务被视为一个不可分割的原子操作单元。

      • 这意味着要么全部操作成功并永久保存,要么全部操作失败并回滚到事务开始前的状态,不存在部分成功或部分失败的情况。

    • 一致性(Consistency):

      • 事务在执行前后,数据库都必须保持一致状态。

      • 这意味着事务执行前后,数据库中的数据必须满足所有定义的完整性约束,例如列级别的约束、外键关系等。

    • 隔离性(Isolation):

      • 事务之间应该相互隔离,每个事务的执行应该与其他事务的执行相互独立,互不干扰。

      • 隔离性确保了多个事务可以并发执行,而不会产生不一致的结果。

    • 持久性(Durability):

      • 一旦事务成功提交后,其所做的修改将永久保存在数据库中,即使发生系统故障或重启,数据也能够恢复到提交后的状态。
      • 持久性通过将事务日志写入非易失性存储介质来实现,如硬盘驱动器或固态硬盘。

【2】Django中如何开启事务

from django.db import transaction

try:
    with transaction.atomic():
        # sql 1
        # sql 2
        # 在with代码块内书写所有的orm操作
        # 这些操作都属于同一个事务
except Exception as e:
     print(e)
     print('执行其他操作')

【十】ORM中常用的字段及参数

AutoField

  • int自增列,必须填入参数 primary_key=True。
  • 当model中如果没有自增列,则自动会创建一个列名为id的列。

IntegerField

  • 一个整数类型
  • 范围在 -2147483648 to 2147483647。(一般不用它来存手机号(位数也不够),直接用字符串存,)

BigIntegerField(IntegerField)

  • 长整型(有符号的)
  • 范围在 -9223372036854775808 ~ 9223372036854775807

CharField

  • 字符类型,必须提供max_length参数, max_length表示字符长度。

  • verbox_name 标识字段的注释

EmailField(CharField)

  • varchar(254)

DecimalField(Field)

  • max_digits,小数总长度
  • decimal_places,小数位长度

TextField(Field)

  • 文本类型
  • 支持大段内容,无字数限制

FileField(Field)

  • 字符串,路径保存在数据库,文件上传到指定目录
  • 参数:
    • upload_to = "/data"
      • 给该字段传一个文件对象,会自动将文件保存到 /data 目录下,然后将文件路径保存到数据库中

BooleanField(Field)

  • 字段为布尔值
  • 数据库里面可以存 0/1

DateField

  • 日期字段
  • 日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。

DateTimeField

  • 日期时间字段
  • 格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。

DateField和DateTimeField

auto_now_add

  • 配置auto_now_add=True
  • 创建数据记录的时候会把当前时间添加到数据库。

auto_now

  • 配置上auto_now=True
  • 每次更新数据记录的时候会更新该字段。

ForeignKey

  • 外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在 '一对多'中'多'的一方。
  • ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。

to

  • 设置要关联的表

to_field

  • 设置要关联的表的字段

on_delete

  • 当删除关联表中的数据时,当前表与其关联的行的行为。

models.CASCADE

  • 删除关联数据,与之关联也删除

OneToOneField

  • 一对一字段。
  • 通常一对一字段用来扩展已有字段。(通俗的说就是一个人的所有信息不是放在一张表里面的,简单的信息一张表,隐私的信息另一张表,之间通过一对一外键关联)

to

  • 设置要关联的表。

to_field

  • 设置要关联的字段。

on_delete

  • 当删除关联表中的数据时,当前表与其关联的行的行为。(参考上面的例子)

【字段参数】

null

用于表示某个字段可以为空。

unique

如果设置为unique=True 则该字段在此表中必须是唯一的 。

Foregin(unique = True) ---->  OneToOneField

db_index

如果db_index=True 则代表着为此字段设置索引。

default

为该字段设置默认值。

【对应关系】

'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',

【支持自定义字段】

from django.db import models

# Create your models here.
#Django中没有对应的char类型字段,但是我们可以自己创建
class FixCharField(models.Field):
    '''
    自定义的char类型的字段类
    '''
    def __init__(self,max_length,*args,**kwargs):
        self.max_length=max_length
        super().__init__(max_length=max_length,*args,**kwargs)

    def db_type(self, connection):
        '''
        限定生成的数据库表字段类型char,长度为max_length指定的值
        :param connection:
        :return:
        '''
        return 'char(%s)'%self.max_length
#应用上面自定义的char类型
class Class(models.Model):
    id=models.AutoField(primary_key=True)
    title=models.CharField(max_length=32)
    class_name=FixCharField(max_length=16)
    gender_choice=((1,'男'),(2,'女'),(3,'保密'))
    gender=models.SmallIntegerField(choices=gender_choice,default=3)

【十一】数据库查询优化

  • ORM语句的特点
    • 惰性查询:如果仅仅是书写了ORM语句,在后面没有使用到的话就不会对数据库进行查询,当我们需要用到查询的数据时,ORM会从数据库查询数据并返回数据
  • only/defer
'''
    ORM语句的特点
        书写完ORM语句后,如果我们没有用到需要查询的数据,ORM语句就不会去数据库查询数据
        当我们需要数据时,ORM语句才回去数据库查询数据并将数据返回
    '''
	# 运行这句话的时候没有SQL语句执行
	models.Book.objects.all()

	# 这样运行的时候就会返回相应的数据
	res = models.Book.objects.all()
	print(res)  # 只有需要用到真正的数据时,才会走数据库查询数据

	# (1)获取所有书籍表中书的名字
	res = models.Book.objects.all()
	for i in res:
    	print(res.get('title'))

    # 新需求:实现获取到一个数据对象,通过 .属性 拿到值并且无其他字段
    res = models.Book.objects.only('title')
    print(res)
    for k in res:
        print(k.title)  # .only括号内的字段不会走数据库
        print(k.price)  # only中没有的字段,会重新走数据库并逐一从数据库中查找

        '''
    defer  和  only  相反
        defer 括号内放的字段不在查询的对象里面 ,查询该字段需要重新走数据库
        而如果查询的是非括号内的字段 则不需要走数据库
    '''
        res = models.Book.objects.defer('title')
        print(res)
        for s in res:
            print(res.price)
  • select_related /prefetch_related
    # select_related / prefetch_related : 与跨表操作有关
    res = models.Book.objects.all()
    for i in res:
        print(i.publish.name)  # 每循环一次就要走一次数据库查询
    '''
    select_related
        内部直接将book表和publish表的数据连起来,一次性将所有数据封装到查询出来的对象
        这个时候对象可以直接点去取两个变中的数据而不需走数据库
    select_related
        参数只能是外键字段
        但是一对一或一对多或多对多的外键字段不行
        
    '''
    res = models.Book.objects.select_related('publish')  # INNER JOIN:联表操作
    print(res)
    for i in res:
        print(i.publish.name)

    res = models.Book.objects.prefetch_related('publish')  # 子查询
    for i in res:
        print(i.publish.name)  # 每循环一次就要走一次数据库查询
    '''
    prefetch_related
        将子查询查询出来的所有结果也是封装到一个对象中返回
        但是在使用的时候给我们的感觉是一次性完成的
        较联表操作多了一步
    '''

【1】only与defer

  • 在数据库查询中,onlydefer是两个常用的方法
  • 用于优化查询性能和减少数据传输量。

only

  • only方法允许我们指定只查询我们需要的字段,而忽略其他字段。通过只选择必要的字段,可以减少数据库返回的数据量,从而提高查询速度和减少网络传输开销。在Django ORM中,可以使用.only()方法来限定查询字段。

例如,假设我们有一个名为Product的模型,其中包含许多字段,但我们只对名称和价格感兴趣。我们可以使用only方法来只选择这两个字段:

products = Product.objects.only('name', 'price')

defer

  • defer方法与only相反,它允许我们延迟加载某些字段,将它们从查询中排除。这在我们有一些大的、不常用的字段时非常有用,可以避免在每次查询时都加载这些字段,从而提高查询性能。在Django ORM中,可以使用.defer()方法来延迟加载字段。

例如,假设我们有一个名为Product的模型,其中包含一个很大的description字段,但我们只在特定情况下使用它。我们可以使用defer方法将其从查询中排除:

products = Product.objects.defer('description')

在数据库查询中,select_relatedprefetch_related是两个常用的方法

用于解决查询中的关联对象性能问题。

  • select_related方法用于减少数据库查询次数,通过在查询时同时加载相关联的对象。例如,如果我们有一个Product模型,其中包含一个外键字段category,我们可以使用select_related方法来在查询产品时一起加载其所属的类别信息:
products = Product.objects.select_related('category')

这样一来,当我们访问每个产品的category属性时,不会再触发额外的数据库查询,提高了查询效率。

  • prefetch_related方法则是用于解决一对多或多对多关系的性能问题。当我们查询包含关联对象集合(例如一个订单和其包含的商品)时,prefetch_related方法可以一次性加载所有的关联对象,避免了N+1查询问题。
orders = Order.objects.prefetch_related('products')

这样一来,在访问每个订单对应的产品集合时,不会再触发额外的查询操作,大大提高了查询性能。

需要注意的是,使用select_relatedprefetch_related方法时,需谨慎选择要加载的关联对象,避免过度加载导致的性能问题,同时也要注意数据库索引的优化以提高查询效率。

【十二】choice参数(数据库字段设计常见)

【1】引入

以一张信息表为例

性别
学历
工作经验
是否婚配
是否生子
客户来源
...
  • 针对某个可以列举完全的可能性字段,我们应该如何存储?

  • 只要某个字段的可能性是列举完全的,那么一般情况下都会采用choice参数

【2】数据准备

from random import choices

from django.db import models


# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    # 性别
    gender_choices = (
        (1, 'male'),
        (2, 'female'),
        (3, 'other'),
    )
    score = (
        ('1', 'male'),
        ('2', 'female'),
        ('3', 'other'),
    )
    # 保证字段类型跟列举出来的元祖第一个数据类型一致即可
    gender = models.IntegerField(choices=gender_choices)

    '''
    该gender字段存的还是数字 但是如果存的数据在上面元祖列举的范围之内
    那么可以非常轻松的获取到数字对应的真正内容
    '''

1.gender字段存的数字不在上述元祖列举的范围内,会如何?
2.如过数字在 如何获取对应的中文注释?

【3】问题解决

(1)gender字段存的数字在上述元祖列举的范围内

from django.test import TestCase

# !/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day08.settings")
    import django

    django.setup()

    from app01 import models

    models.User.objects.create(username="dream", age=18, gender=1)
    models.User.objects.create(username="mengmeng", age=28, gender=2)
    models.User.objects.create(username="chimeng", age=39, gender=3)
    # 存的时候,没有列举出来的数字也能被存进去
    models.User.objects.create(username="hope", age=44, gender=4)

没有报错,且第四条已经插入到数据库中

(2)gender字段存的数字在上述元祖列举的范围内并获取

# 取 - 如果有对应关系
user_obj = models.User.objects.filter(pk=1).first()
print(user_obj.gender)  # 1
# 只要是 choice字段的注释,如果想要获取到注释的信息,固定写法 get_字段名_display()
print(user_obj.get_gender_display())  # male

(3)gender字段存的数字不在上述元祖列举的范围内

# 取 - 如果没有对应关系
user_obj = models.User.objects.filter(pk=4).first()
# 如果没有对应关系 字段是什么 返回的就是什么
print(user_obj.get_gender_display())  # 4

【4】总结

  • choice参数使用场景非常广泛

  • 例如

    • 支付方式的选择
    • 生源的来源地
    • 分数的分类
    • 学历的分类
    • ...

【十三】MTV与MVC模型

  • MTV模型和MVC模型是两种常见的软件设计模式,用于组织和管理用户界面和应用程序的逻辑。
  • 虽然它们存在一些相似之处,但它们在设计和应用上有一些不同。

【1】MTV模型:

MTV模型是指Model-Template-View(模型-模板-视图)模型,是Django框架中采用的一种设计模式。它的核心思想是将应用程序分为三个主要部分:

  • 模型(Model):

    • 模型表示应用程序中处理数据的结构和行为。
    • 它通常与数据库交互,并定义了数据的存储和操作方式。
  • 模板(Template):

    • 模板负责处理用户界面的显示。
    • 它定义了应用程序的外观和布局,并将动态数据与静态页面结合在一起,生成最终的用户界面。
  • 视图(View):

    • 视图处理应用程序的逻辑和业务流程。
    • 它接收用户的请求,从模型中获取数据,将数据传递给模板进行渲染,并生成响应返回给用户。

MTV模型的优点在于它可以很好地将应用程序的逻辑和用户界面进行分离,使代码更容易维护和扩展。

【2】MVC模型:

MVC模型是指Model-View-Controller(模型-视图-控制器)模型,是一种常见的软件设计模式,广泛应用于Web开发和其他应用程序中。

  • 模型(Model):

    • 模型负责处理应用程序的数据逻辑。
    • 它包含了数据的存储和操作方式,并定义了数据在应用程序内部如何交互和被操作。
  • 视图(View):

    • 视图是用户界面的表示,负责展示数据给用户并接收用户的输入操作。
    • 它通常从模型中获取数据,并将其显示给用户。
  • 控制器(Controller):

    • 控制器处理用户的交互和请求,并根据用户的行为作出相应的响应。
    • 它接收用户的输入,并更新模型和视图以反映用户的操作。

MVC模型的优点在于它可以很好地分离应用程序的不同组件,使得代码更易于维护、测试和重用。

【3】总结:

  • MTV模型主要用于Django框架中,通过将应用程序分为模型、模板和视图,提供了一种清晰的架构方案。
  • MVC模型则是一个通用的设计模式,广泛应用于各种类型的应用程序中。
  • 无论使用哪种模型,都能帮助开发者更好地组织和管理代码,并实现可扩展和可维护的应用程序。

【十四】多对多三种创建方式

【1】全自动

  • 利用ORM自动帮我们创建第三张表关系
class Book(models.Model):
    name = models.CharField(max_length=32)
    # 全自动
    authors = models.ManyToManyField(to='Author')


class Author(models.Model):
    name = models.CharField(max_length=32)
  • 优点
    • 代码不需要自己写,非常方便,还支持ORM提供操作第三张表的方法
  • 缺点
    • 第三张关系表的扩展性极差(没办法添加额外字段)

【2】纯手动(不建议使用)

class Book(models.Model):
    name = models.CharField(max_length=32)

class Author(models.Model):
    name = models.CharField(max_length=32)

class BookAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
  • 优点
    • 第三张表完全取决于自己进行额外的拓展
  • 缺点
    • 需要写代码较多
    • 不能使用ORM提供的相关方法

【3】半自动

class Book(models.Model):
    name = models.CharField(max_length=32)
    # 全自动
    # through_fields : 当前表是谁,第一个参数就是谁
    # 判断的本质:通过第三张表查询对应的表,需要用到哪个字段就把哪个字段放在前面
    authors = models.ManyToManyField(to='Author', through='BookAuthor', through_fields=('book', 'author'))


class Author(models.Model):
    name = models.CharField(max_length=32)


class BookAuthor(models.Model):
    book_id = models.ForeignKey(to='Book')
    author_id = models.ForeignKey(to='Author')
  • 这样虽然可以使用ORM的正反向查询,但是没法使用add,set,remove,clear这四个方法
posted @ 2023-07-17 11:31  Chimengmeng  阅读(65)  评论(0编辑  收藏  举报