Django之模型层

测试环境搭建

需求:需要练习django中的ORM操作,但django只支持通过网页去调后台函数,如果我们想在一个py文件中单独测试sql效果时,就需要使用tests.py了。
配置方法:

# 1. 打开manage.py,将下面几行代码粘贴至tests.py文件中
import os


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject2.settings')
	# 2. 再添加两行代码
    import django
    django.setup()
	# 3. 注意以上顺序不能动。之后就可以进行使用,比如要测试ORM时,可以导入models.py
    from app01 import models
	# 这里是内容区域

# 4. 所有测试内容都写在main()函数中所以需要再在最下面调用一下这个函数
main()

查看ORM执行的SQL的方法

方法一:
如果值为一个QuerySet对象,可以直接以点的方式,使用query方法查看SQL语句
缺点:不是QuerySet对象就无法查看SQL,比如create返回值就不是QuerySet对象

res = models.User.objects.create(name='Jack', age='15')
# 返回值为:<QuerySet [<User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>]>,可以确认为一个QuerySet
print(res.query)
# 返回的SQL
'''
SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_date` FROM `app01_user` WHERE (`app01_user`.`age` = 15 AND `app01_user`.`name` = Jack)
'''

# 测试非QuerySet对象使用
res = models.User.objects.create(name='Rose', age=14)
print(res)  # 返回:用户名>>>:Rose
print(res.query)  # 报错:AttributeError: 'User' object has no attribute 'query'

方法二:
如果要查看所有ORM底层的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',
        },
    }
}

之后所有的ORM操作都会打印日志

res = models.User.objects.create(name='Maria', age=20)
print(res)
'''
返回结果为:
用户名>>>:Maria
(0.001) SELECT @@SQL_AUTO_IS_NULL; args=None  # 这个是准备工作,不需要看
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None  # 这个是准备工作,不需要看
(0.002) INSERT INTO `app01_user` (`name`, `age`, `register_date`) VALUES ('Maria', 20, '2023-03-07 03:15:14.589445'); args=['Maria', 20, '2023-03-07 03:15:14.589445']  # 返回的SQL语句
'''

ORM执行SQL语句

当ORM执行效率较低时,可以直接直接使用SQL语句进行操作。
方法一:

res = models.User.objects.raw('select * from app01_user;')
print(list(res))  # 或者使用for循环

方法二:

# 其实使用的pymysql中的方法,django封装了一下,其它方法可以参考pymysql的方法。
from django.db import connection
cursor = connection.cursor()
cursor.execute('select * from app01_user;')
print(cursor.fetchall())

其它方法还有extra等方法,可以自行查询

ORM常用字段

AutoField

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

IntegerField

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

BigIntegerField

整数类型,范围在 [-9223372036854775808,9223372036854775807]

DecimalField

浮点数类型,注意max_digits中是包含decimal_places的,所以max_digits必须大于decimal_places

参数:
max_digits=10      # 定义一共10位
decimal_places=2   # 小数点后占2位

CharField

字符类型,必须提供max_length参数, max_length表示字符长度。
在Django中CharField对应在mysql中表示varhchar类型,Django中没有char类型,可以通过自定义来定义一个char类型。

DateField

日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
配置上auto_now=True,每次更新数据记录的时候会更新该字段。

DateTimeField

日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例。
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
配置上auto_now=True,每次更新数据记录的时候会更新该字段。

BooleanField

传入布尔值,True会以数字1的形式保存在数据库中,False会以数字0的形式保存到数据库中

TextField

存储大段文本,比如文章内容。

EmailField

存储邮箱字段,本质还是CharField

IPAddressField

存储IP字段,本质还是CharField

FileField

上传文件后,会自动保存到提前配置好的路径下并存储该路径信息

  • 将路径保存在数据库,文件上传到指定目录
  • 参数:
upload_to = ""  # 上传文件的保存路径
storage = None  # 存储组件,默认django.core.files.storage.FileSystemStorage

ImageField

与FileField类似,只是多两个参数

upload_to = ""      # 上传文件的保存路径
storage = None      # 存储组件,默认django.core.files.storage.FileSystemStorage
width_field_None    # 上传图片的高度保存的数据库字段名(字符串)
height_field=None   # 上传图片的宽度保存的数据库字段名(字符串)

字段合集:

AutoField(Field)
        - int自增列,必须填入参数 primary_key=True

    BigAutoField(AutoField)
        - bigint自增列,必须填入参数 primary_key=True

        注:当model中如果没有自增列,则自动会创建一个列名为id的列
        from django.db import models

        class UserInfo(models.Model):
            # 自动创建一个列名为id的且为自增的整数列
            username = models.CharField(max_length=32)

        class Group(models.Model):
            # 自定义自增列
            nid = models.AutoField(primary_key=True)
            name = models.CharField(max_length=32)

    SmallIntegerField(IntegerField):
        - 小整数 -3276832767

    PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正小整数 032767
    IntegerField(Field)
        - 整数列(有符号的) -21474836482147483647

    PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正整数 02147483647

    BigIntegerField(IntegerField):
        - 长整型(有符号的) -92233720368547758089223372036854775807

    BooleanField(Field)
        - 布尔值类型

    NullBooleanField(Field):
        - 可以为空的布尔值

    CharField(Field)
        - 字符类型
        - 必须提供max_length参数, max_length表示字符长度

    TextField(Field)
        - 文本类型

    EmailField(CharField):
        - 字符串类型,Django Admin以及ModelForm中提供验证机制

    IPAddressField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

    GenericIPAddressField(Field)
        - 字符串类型,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(CharField)
        - 字符串类型,Django Admin以及ModelForm中提供验证 URL

    SlugField(CharField)
        - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

    CommaSeparatedIntegerField(CharField)
        - 字符串类型,格式必须为逗号分割的数字

    UUIDField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

    FilePathField(Field)
        - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
        - 参数:
                path,                      文件夹路径
                match=None,                正则匹配
                recursive=False,           递归下面的文件夹
                allow_files=True,          允许文件
                allow_folders=False,       允许文件夹

    FileField(Field)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

    ImageField(FileField)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
            width_field=None,   上传图片的高度保存的数据库字段名(字符串)
            height_field=None   上传图片的宽度保存的数据库字段名(字符串)

    DateTimeField(DateField)
        - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

    DateField(DateTimeCheckMixin, Field)
        - 日期格式      YYYY-MM-DD

    TimeField(DateTimeCheckMixin, Field)
        - 时间格式      HH:MM[:ss[.uuuuuu]]

    DurationField(Field)
        - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

    FloatField(Field)
        - 浮点型

    DecimalField(Field)
        - 10进制小数
        - 参数:
            max_digits,小数总长度
            decimal_places,小数位长度

    BinaryField(Field)
        - 二进制类型

ORM字段与MySql字段对应关系:

对应关系:
    '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指定的值
        '''
        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常用字段参数

primary          # 主键
verbose_name     # 注释
max_length       # 最大长度
max_digits       # 小数总共多少位 
decimal_places   # 小数点后面的位数
auto_now         # 每次更新数据记录的时候会更新该字段
    auto_now=True
auto_now_add     # 创建数据记录的时候会把当前时间添加到数据库
    auto_now_add=True
null             # 允许字段为空
default          # 字段默认值
unique           # 唯一值
    unique=True
db_index         # 为该字段添加索引
    db_index=True
choices          # 当一个字段的可能性能够被列举完的情况下使用,比如:姓名、学历、工作状态
    比如:
    class User(models.Model)
        name = models.CharField(max_length=64)
        gender_choice = (
        (1, 'Man'),
        (2, 'Woman'),
        (3, 'Other'),
        )
        gender = models.IntegerField(choices=gender_choice)
    使用:
    数据库中存储对应字段为数字,1234等等,获取时使用:
    user_obj = models.User.objects.filter(pk=1)
    user_obj.gender   # 此时结果为数字 1
    user_obj.get_gender_display()  # 此时会获取对应数字的姓别
    # 注意:如果没有指定对应关系,不会报错,会获取对应数字
    # get_字段名_display()  这个为固定写法

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

ORM建表操作

# 1. 在models.py中创建表的class
class User(models.Model):
    name = models.CharField(max_length=32, verbose_name='姓名')
    age = models.IntegerField(verbose_name='年龄')
    register_date = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)

    # 函数作用为:在调用时会返回self.name字段对应的用户姓名,比较方便看懂
    def __str__(self):
        return f'用户名>>>:{self.name}'

# 2. 在项目根目录下(与manage.py目录同级目录),执行下面两个命令:
python manage.py makemigrations
python manage.py migrate

ORM新增数据

比如给User表添加数据

    res = models.User.objects.create(name='Jack', age='15')
    print(res)  # 返回值为:用户名>>>:Jack

ORM常用操作

filter过虑查询(QuerySet)

  • 根据条件筛选内容,返回一个QuerySet,显示为 [数据对象1, 数据对象2, 数据对象N] ,也就是列表套数据对象。
  • 根据上面的结论得出,列表无法以点的方式来作进一步操作,只有数据对象才可以。比如:
# filter括号内不传任何内容为查询所有
res = models.User.objects.filter()
print(res)
# 返回值:<QuerySet [<User: 用户名>>>:jerry>, <User: 用户名>>>:tom>, <User: 用户名>>>:Jack>]>
print(res.age)     # 报错:AttributeError: 'QuerySet' object has no attribute 'age'
print(res[0].age)  # 正常返回

filter之条件查询

  • filter中括号内可以添加多个条件,条件之间默认为and关系。不添加条件为查询所有
# 查询所有内容,与all()相同
res = models.User.objects.filter()
# 按条件查询
res = models.User.objects.filter(name='jerry', age=5)
print(res)         # 返回:<QuerySet [<User: 用户名>>>:jerry>]>

filter之first与last

  • Query支持索引取值,但只支持正数,且orm不建议使用索引取值
  • 建议使用first()与last()
res = models.User.objects.filter().first()  # 取查询结果的第一个值
res = models.User.objects.filter().last()   # 取查询结果的最后一个值
# 索引取值
res = models.User.objects.filter()
print(res[0])
  • 与索引取值的区别为,当索引取值范围超过了列表后,会直接报错,而使用first与last就不会
# 索引取值
res = models.User.objects.filter(pk=20)  # 库里面就3条数据,查询结果为空列表
print(res[0])  # 报错:IndexError: list index out of range

# first与last取值
res = models.User.objects.filter(pk=20)
print(res.first())  # 返回None
print(res.last())   # 返回None

all()(QuerySet)

  • 查询所有数据,返回一个QuerySet,显示为 [数据对象1, 数据对象2, 数据对象N] ,也就是列表套数据对象。
res = models.User.objects.all()
# 结果为<QuerySet [<User: 用户名>>>:jerry>, <User: 用户名>>>:tom>, <User: 用户名>>>:Jack>]>

values()

  • 根据指定字段获取数据
  • 返回结果为QuerySet,且为列表套字典的形式
res = models.User.objects.all().values('age')
# 返回值为:<QuerySet [{'age': 5}, {'age': 5}, {'age': 15}]>

res = models.User.objects.values('age')
# 返回值下上面相同

res = models.User.objects.filter(name='jerry').values()
返回值:<QuerySet [{'id': 1, 'name': 'jerry', 'age': 5, 'register_date': datetime.datetime(2023, 3, 7, 2, 9, 7, 448918, tzinfo=<UTC>)}]>

values_list()

  • 可以指定字段获取数据
  • 返回结果为QuerySet,且为列表套元组的形式
res = models.User.objects.all().values_list()
# 返回结果:<QuerySet [('jerry', 5), ('tom', 5), ('Jack', 15), ('Jack', 15), ('Rose', 14), ('Maria', 20)]>

distinct()(QuerySet)

  • 去重,所有数据值需要一模一样,如果有主键肯定不行(因为主键必须唯一),比如

    如上图中,索引3、4、7中,姓名均一致,但3与其他两个的register_date不一致,其余两个除索引外均一致
res = models.User.objects.filter(name='Jack')
# 返回值:<QuerySet [<User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>]>
res = models.User.objects.filter(name='Jack').distinct()
# 返回值:<QuerySet [<User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>, <User: 用户名>>>:Jack>]>
# 可以看到,上面的去重并未生效,因为QuerySet中是包含主键值的(想一下它为什么可以点出任何字段来)

# distinct()正确的用法
res = models.User.objects.filter(name='Jack').values('name', 'age').distinct()
# 返回值:<QuerySet [{'name': 'Jack', 'age': 15}]>

order_by()(QuerySet)

  • 根据指定条件排序,默认为升序排序,如果需要使用降序排序,需要在排序字段前加负号
res = models.User.objects.all().order_by('name')   # 按一个条件升序排序
res = models.User.objects.all().order_by('name', 'age')  # 指定多个条件升序排序
res = models.User.objects.all().order_by('-age')  # 指定一个条件降序排列
res = models.User.objects.all().order_by('name', '-age')  # 升序与降序可以混合使用

count()

  • 统计数据集中数据的个数,属于QuerySet方法
res = models.User.objects.all().count()

冷门方法

get()

  • 根据条件筛选数据并直接获取到数据对象,条件一旦不存在,直接报错!!!!
res = models.User.objects.get(pk=1)
# 返回结果:用户名>>>:jerry

res = models.User.objects.get(pk=99999)
# 直接报错

exclude()(QuerySet)

  • 排除指定条件的数据,相当于取反操作,支持多个条件,默认为and关系
res = models.User.objects.exclude(name='Jack')  # 单个条件
# 返回结果为除了name='Jack'以外的所有数据
res = models.User.objects.exclude(name='Jack', age=15)  # 多个条件

reverse()(QuerySet)

  • 对排序取反操作,必须在排序之后再使用才有效
res = models.User.objects.all().order_by('name').reverse()
# 这时会以降序进行排列

exists

  • 判断结果集中是否存在数据,如果有返回True,如果没有返回False
    注意:在python中所有数据自带布尔值,没有必要转换为True或False
res = models.User.objects.filter(pk=1).exists()      # 返回True
res1 = models.User.objects.filter(pk=100).exists()   # 返回False

双下划线查询

使用方法:

字段名__方法,如下:

# 如查找大于5岁的用户
res = models.User.objects.filter(age__gt=5)
  • 具体常用方法介绍
方法 释义 SQL
__gt 大于 where xxx > x;
__lt 小于 where xxx < x;
__gte 大于等于 where xxx >= x;
__lte 小于等于 where xxx <= x;
__in 成员运算,类似于mysql中的in where xxx in (x, y, j);
__range 取一个范围 where xxx between xxx;
__contains 包含(比如包含名字中有‘a’字母,注意:区分大小写) where xxx like BINARY '%a%';
__icontains 包含(不区分大小写) where xxx like '%a%';
__year 日期字段按年查询(比如查询2023年的数据) BETWEEN '2023-01-01 00:00:00' AND '2023-12-31 23:59:59.999999'
__month 日期字段按月查询(注意:不管年份,只管月份了,注意时区) WHERE EXTRACT(MONTH FROM CONVERT_TZ(app01_user.register_date, 'UTC', 'Asia/Shanghai')) = 3
__day 日期字段按日查询
__hour 日期字段按时查询
__minute 日期字段按分查询
__second 日期字段按秒查询

ORM外键字段创建

说明

  • 外键字段关系有三种:
1. 一对一   外键字段建在查询频率多的表中    在ORM中会自动添加_id的后缀
2. 一对多   外键字段建在多的表             在ORM中会自动添加_id的后缀
3. 多对多   外键字段建在第三张关系表        在ORM中不会在表中展示字段,会创建第三张表

创建三种外键字段语句

  • 创建外键表示例
class Book(models.Model):
    """图书表"""
    title = models.CharField(max_length=32, verbose_name='书名')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='价格')
    publish_time = models.DateField(auto_now_add=True, verbose_name='出版日期')
    """一个出版社可以对应多本书,所以出版社与图书属于一对多关系,一对多关系将字段建在多的一方"""
    """创建书籍与出版社的一对多外键字段"""
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
    """注意:Django1.x版本,外键字段默认都为级联更新删除,Django2.x版本及以上,需要自己申名"""

    """
    1. 一本书可以有多个作者,一个作者也可以有多本书,所以属于多对多关系。
    2. 对于多对多关系,需要将表建在第三张关系表中。在ORM中,可以将外键字段创建到查询频率高的表中,django内部会自动帮你创建第三张关系表
    3. 多对多关系属于虚拟字段,用于让ORM自动创建第三张表,不会在表中创建对应字段
    """
    authors = models.ManyToManyField(to='Author')

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


class Publish(models.Model):
    """出版社表"""
    name = models.CharField(max_length=32, verbose_name='出版社名称')
    address = models.CharField(max_length=64, 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=True)

    def __str__(self):
        return f'作者对象>>>:{self.name}'


class AuthorDetail(models.Model):
    """作者详情"""
    phone = models.CharField(max_length=11, verbose_name='手机号')
    address = models.CharField(max_length=64, verbose_name='家庭住址')

外键字段的增删改查

添加示例数据

# 1. 添加基础数据
models.AuthorDetail.objects.create(phone=1111, address='河北')
models.AuthorDetail.objects.create(phone=2222, address='河南')
models.Author.objects.create(name='Jerry', age=18, author_detail_id=1)
models.Author.objects.create(name='Tom', age=28, author_detail_id=2)
models.Publish.objects.create(name='北方出版社', address='北京')
models.Publish.objects.create(name='东方出版社', address='东京')
models.Publish.objects.create(name='南方出版社', address='南京')

# 2. 添加外键字段数据(一对多)
# 针对一对多,插入数据可以直接写表中的实际字段
models.Book.objects.create(title='三国演义', price=888.88, publish_id=1)
models.Book.objects.create(title='红楼梦', price=666.77, publish_id=2)
models.Book.objects.create(title='西游记', price=999.55, publish_id=1)
models.Book.objects.create(title='围城', price=888.66, publish_id=2)
models.Book.objects.create(title='人性的弱点', price=777.55, publish_id=1)
# 针对一对多,插入数据也可以写表所属类的字段名,值可以使用查询后的对象
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.crfeate(title='水浒传', price=555.66, publis=publish_obj)

# 3. 添加外键字段数据(多对多)
# 先拿到书籍对象,这里别忘记first()
book_obj = models.Book.objects.filter(pk=1).first()
# 再添加数据,给当前这本书绑定一个ID为1的作者
book_obj.authors.add(1)
book_obj.authors.add(2, 3)  # 也可以一次性绑定多个关系

# 修改对应关系,注意需要使用元组/字典等(需要可以被for循环)
book_obj.authors.set((1, 2))

# 删除一个对应关系(其实也可以使用set)
book_obj.authors.remove(1)  # 也可以一次性删除多个关系

# 清除一个对应关系
book_obj.authors.clear()   # 将book_obj对应的关系全部清除

ORM跨表查询

正向查询
    外键字段在表A中,由表A需要查询表B中的数据,这时称为正向查询
    由外键字段所在的表数据查询关联的表数据,称为正向查询
反向查询
    外键字段在表A中,由表B需要查询表A中的数据,这时称为反向查询
    没有外键字段所在的表数据查询关联的表数据,称为反向查询
总结:
    正反向的核心就是看外键字段在不在当前数据所在的表中

在ORM中:
    正向查询按外键字段
    反向查询按表名小写

基于对象的跨表查询

正向查询练习题:

# 1. 查询主键为1的书籍对应的出版社名称
# 1.1. 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=1).first()
# 1.2. 判断是正反向查询,由书查出版社,外键字段在Book表中,所以为正向查询。
print(book_obj.publish.name)

# 2. 查询主键为4的书籍对应的作者姓名
# 2.1. 先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=4).first()
# 2.2. 判断是正反向查询,由书查作者,外键字段在Book表中,所以为正向查询
print(book_obj.authors)  # 结果:app01.Author.None
# 2.3. 因为是多对多关系,所以需要使用下面的方法查询
print(book_obj.authors.all())

# 3. 查询Jerry的电话号码
# 3.1. 先根据条件获取数据对象
author_obj = models.Author.objects.filter(name='Jerry').first()
# 3.2. 判断正反向查询,由作者查作者详情,外键字段在作者表中,所以为正向查询
print(author_obj.author_detail.phone)

反向查询练习题:

# 1. 查询北方出版社出现过的书籍名称
# 1.1. 先根据条件获取数据对象
publish_obj = models.Publish.objects.filter(name='北方出版社').first()
# 1.2. 判断正反向查询,由出版社查询书籍,外键字段在书籍表中,所以为反向查询
print(publish_obj.book_set.all())  # 由于返回结果是多个,所以需要使用_set和all()方法

# 2. 查询Jerry写过的书籍
# 2.1. 先根据条件获取数据对象
author_obj = models.Author.objects.filter(name='Jerry').first()
# 2.2. 判断正反向查询,由作者查询书籍,外键字段在书籍表中,所以为反向查询,且一个作者可以写多本书,所以需要使用_set和all()方法
print(author_obj.book_set.all().values('title'))

# 3. 查询电话是1111的作者姓名
# 3.1. 先根据条件获取数据对象
author_detail_obj = models.AuthorDetail.objects.filter(phone=1111).first()
# 3.2. 判断正反向查询,由电话查询作者,外键字段在作者表中,所以为反向查询,且一个作者详情只对应一位作者,所以不需要使用_set和all()方法
print(author_detail_obj.author.name)

# 总结:
'''
如果查询结果为多个的时候,需要使用    表名_set.all()方法 
如果查询结果为一个的时间,不需要使用  表名_set.all()方法 
'''

基于双下划线的跨表查询

基于双下划线的正向查询

  • 这种方法与上面的正反向查询一样,但写法更简单
    练习:
# 1. 查询主键为1的书籍对应的出版社名称
# 1.1. 判断为正反查询后,只需要在values中写好出   版社表__字段名   即可
res = models.Book.objects.filter(pk=1).values('publish__name')  

# 2. 查询主键为4的书籍对应的作者姓名
# 2.1. 这里拿到了两个字段,title是book表中的书籍名称,再拿到对应的作者姓名
res = models.Book.objects.filter(pk=4).values('title', 'authors__name')

# 3. 查询Jerry的电话号码
res = models.Author.objects.filter(name='Jerry').values('author_detail__phone')

基于双下划线的反向查询

练习:

# 1. 查询北方出版社出现过的书籍名称和价格
res = models.Publish.objects.filter(name='北方出版社').values('book__title', 'book__price')

# 2. 查询Jerry写过的书籍
res = models.Author.objects.filter(name='Jerry').values('book__title')

# 3. 查询电话是1111的作者姓名
res = models.AuthorDetail.objects.filter(phone='1111').values('author__name')

正反向查询的进阶操作

练习:

# 1. 查询主键为1的书籍对应的出版社名称
# 1.1. 查询了出版了主键为1的出版社名称
res = models.Publish.objects.filter(book__pk=1).values('name')

# 2. 查询主键为4的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=4).values('name')

# 3. 查询Jerry的电话号码
res = models.AuthorDetail.objects.filter(author__name='Jerry').values('phone')

# 4. 查询北方出版社出现过的书籍名称和价格
res = models.Book.objects.filter(publish__name='北方出版社').values('title', 'price')

# 5. 查询Jerry写过的书籍
res = models.Book.objects.filter(authors__name='Jerry').values('title')

# 6. 查询电话是1111的作者姓名
res = models.Author.objects.filter(author_detail__phone=1111).values('name')

# 7. 查询主键为3的书籍对应的作者电话号码(跨三张表查询)
# 7.1. 正向查询
res = models.Book.objects.filter(pk=4).values('authors__author_detail__phone')
# 7.2. 反向查询
res = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
# 7.3. 正反向查询混用
res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')

多对多关系的三种创建方式

  • 全自动创建
  • 优点:
    1. 自动创建第三张表
    2. 提供了add、remove、set、clear四种操作
  • 缺点:第三张表无法创建更多字段,扩展性差
    全自动创建就是上面我们做练习时使用的方式,代码如下:
class Book(models.Model):
    """书籍表"""
    title = models.CharField(max_length=64, verbose_name='书籍名称')
    authors = models.ManyToManyField(to='Author')


class Authors(models.Model):
    """作者表"""
    name = models.CharField(max_length=32, verbose_name='作者姓名')
  • 纯手工创建

  • 优点

    1. 第三张表由自己来设计创建,扩展性强
  • 缺点

    1. 第三张表由自己来设计创建,编写繁琐
    2. 不再支持add、remove、set、clear四种操作
    3. 不再有正反向概念
      代码如下:
# 1. 创建书籍表
class Book(models.Model):
    """书籍表"""
    title = models.CharField(max_length=64, verbose_name='书籍名称')

# 2. 创建作者表
class Authors(models.Model):
    """作者表"""
    name = models.CharField(max_length=32, verbose_name='作者姓名')

# 3. 创建第三张多对多关系表
class Book2Author(models.Model):
    """书籍表与作者表的对应关系表"""
    book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
    author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
    others = models.CharField(max_length=32, verbose_name='xxx')
    create_time = models.DateField(auto_now_add = True, verbose_name='创建时间')
  • 半自动创建
    结合了自动创建与手工创建,在手工创建的基础上,在查询频率较高的表中添加ManyToManyField自动创建的参数,同时,需要添加两个参数:
    through='手工创建的第三张关系表' # 这个是告诉ORM哪张表是第三张关系对应表
    through_fields=('第三张关系表中对应的外键字段','第三张关系表中对应的外键字段') # 告诉ORM哪两个字段对应外键关系。两张表顺序不能错,哪个表查询频率更高,哪个表需要写在前面。
  • 优点
    1. 第三张表由自己创建,扩展性强。
    2. 支持正反向概念
  • 缺点
    1. 不再支持add、remove、set、clear四种操作
    2. 编写比较繁琐
      代码如下:
# 1. 创建书籍表
class Book(models.Model):
    """书籍表"""
    title = models.CharField(max_length=64, verbose_name='书籍名称')
    authors = models.ManyToManyField(to='Author', through='Book2Author', through_fields=('book', 'author'))

# 2. 创建作者表
class Authors(models.Model):
    """作者表"""
    name = models.CharField(max_length=32, verbose_name='作者姓名')

# 3. 创建第三张多对多关系表
class Book2Author(models.Model):
    """书籍表与作者表的对应关系表"""
    book = models.ForeignKey(to='Book', on_delete=models.CASCADE) 
    author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
    others = models.CharField(max_length=32, verbose_name='xxx')
    create_time = models.DateField(auto_now_add = True, verbose_name='创建时间')

聚合查询

非分组查询使用

常见聚合函数:Max Min Sum Count Avg
在ORM中支持单独使用聚合函数,需要配合aggregate参数

  • 聚合函数需要导入相应模块才可以使用
from django.db.models import Max, Min, Sum, Count, Avg
# 1. 查询价格最高的一本书
res = models.Book.objects.aggregate(Max('price'))

# 2. 查询价格最高和最低的两本书(也可以使用别名)
res = models.Book.objects.aggregate(max_price=Max('price'), min_price=Min('price'))

分组查询

  • 按表分组:models.表名.objects.annotate()

  • 按字段名分组: models.表名.objects.values('字段名').annotate()

# 1. 统计每本书的作者个数
# 1.1. 注意,这里使用models.哪张表,就是按哪个表分组。比如这里要统计每本书,所以需要按书名分组。
res = models.Book.objects.annotate(count=Count('authors__pk')).values('title', 'count')

# 2. 统计出每个出版社卖得最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')

# 3. 统计不止一个作者的书
# 3.1. 先查询出每本书作者的个数,再进行筛选
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title', 'author_num')

# 4. 统计每个作者出的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price')

# 5. 统计每个出版社出了几本书
res = models.Book.objects.values('publish_id').annotate(count=Count('pk')).values('publish_id', 'count')

F与Q查询

  • 当查询条件不是明确的,需要从数据库中获取时,就需要使用F查询

  • 当查询条件需要使用or连接而不是默认的and的时候,需要使用Q查询

  • 需要导入模块 from django.db.models import F, Q
    准备工作:

# 在book表中新增两个字段,并添加数据(添加数据不再演示)
stock = models.IntegerField(verbose_name='库存数')
sell = models.IntegerField(verbose_name='卖出数')

数据如下:

F查询

  • 当查询条件不是明确的,需要从数据库中获取时,就需要使用F查询
# 1. 查询库存数大于卖出数的书籍
from django.db.models import F, Q
res = models.Book.objects.filter(stock__gt=F('sell'))

# 2. 将每个商品的价格提高500块
models.Book.objects.update(price=F('price')+500)

# 3. 每本书名后面加上(新款)两个字,字符串的拼接需要导入两个模块
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F('title'), Value('(新款)')))

Q查询

  • 当查询条件需要使用or连接而不是默认的and的时候,需要使用Q查询
  • '~'符号是not,也就是取反;
  • '|' 符号是or关系
  • 逗号是and关系
from django.db.models import F, Q
# 1. 查询主键是1或者库存大于1500的书籍
res = models.Book.objects.filter(Q(pk=1) | Q(stock__gt=1500))

# 2. 查询主键不是1或者库存大于1500的书籍,取反操作使用'~'符号,如下:
res = models.Book.objects.filter(~Q(pk=1) | Q(stock__gt=1500))

Q查询的进阶操作

  • Q可以将查询条件的左侧的字段名,以字符串形式进行操作
  • 可以与用户交互,可以用于搜索功能
# 1. 导入Q模块
from django.db.models import Q
# 2. 产生对象
q_obj = Q()
# 3. 添加查询条件
q_obj.children.append(('pk', 1))
# 4. 可以添加多个查询条件
q_obj.children.append(('price__gt', 1100))
# 5. 查询支持直接填写Q对象
res = models.Book.objects.filter(q_obj)

ORM查询优化

only与defer

'''
1. ORM的查询都是惰性查询
    如果ORM语句写了,但是在后续的代码中都未使用,则不会真正的进行查询。
2. ORM的查询都自带分页处理
    在查看ORM的SQL语句的时候,可以看到LIMIT 20这种字样,其实是ORM在自动做分页处理
3. only与defer
    需要获取数据库中表的数据对象,并且此数据对象只包含指定的字段对应的数据
      res = models.Book.objects.only('title', 'price')
      only是会将扩号内的字段封装到数据对象中,当去查询这些字段的值的时候(res.title),不会再走SQL查询,但如果想要查询不在only中的字段(res.publish_time),ORM会重新再走一遍SQL语句(要注意,在for循环中,每点一次会走一遍,也就是每次循环都会重新进行一次SQL查询)。
      defer与only相反,会将不是扩号内的字段封装到数据对象中,当去查询这些字段的值的时候(res.title),每次调用都会重新走一遍SQL,而去调用非扩号内的字段数据时,则不会再走SQL。
'''
# 场景:
res = models.Book.objects.all()
for i in res:
    # 当去查询没有在数据对象中的数据时,会每次循环都会执行一次SQL语句,如果数据量过大,效率会非常低
    print(i.publish.name)  

# 解决方案:
# 底层会走一个inner join,注意,这里面不可以使用多对多的字段
res = models.Book.objects.select_related('publish')
for i in res:
    print(i.publish.name)

# 解决方案二:
# 底层是select子查询,所以会走两条SQL,同样是不可以使用多对多字段
res = models.Book.objects.prefetch_related('publish')
for i in res:
    print(i.publish.name)

ORM事务操作

'''
事务的四大特性ACID
    原子性、一致性、隔离性、持久性

相关SQL关键字
    start transaction;
    rollback;
    commit;
    savepoint;

相关重要概念
    脏读
    幻读
    不可重复读
    MVCC多版本控制
'''

1 全局开启

在Web应用中,常用的事务处理方式是将每个请求都包裹在一个事务中。这个功能使用起来非常简单,你只需要将它的配置项ATOMIC_REQUESTS设置为True。

它是这样工作的:当有请求过来时,Django会在调用视图方法前开启一个事务。如果请求却正确处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db1',
        'HOST': '192.101.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': '123',
        'OPTIONS': {
            "init_command": "SET default_storage_engine='INNODB'",
       #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", # 配置开启严格sql模式
        }
        "ATOMIC_REQUESTS": True, # 全局开启事务,绑定的是http请求响应整个过程
        "AUTOCOMMIT":False, # 全局取消自动提交,慎用
    },
  'other':{
    'ENGINE': 'django.db.backends.mysql', 
            ......
  }  # 还可以配置其他数据

对部分视图函数取消事务

from django.db import transaction

@transaction.non_atomic_requests
def my_view(request):
    do_stuff()

@transaction.non_atomic_requests(using='other')
def my_other_view(request):
    do_stuff_on_the_other_database()

注意:只要在视图函数中,执行到了return就算一个事务执行完成,就会commit数据

def my_view_func(request):
    models.User.object.create(name='jjj', age=10)
    return 123321   # 执行到这里时会报错,但执行到了return,也算是一个事务成功完成

2 局部开启

需要导入模块: from django.db import transaction
atomic(using=None, savepoint=True)[source] ,参数:using=‘other’,就是当你操作其他数据库的时候,这个事务才生效,看上面我们的数据库配置,除了default,还有一个other,默认的是default。savepoint的意思是开启事务保存点。
 
原子性是数据库事务的一个属性。使用atomic,我们就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。

被atomic管理起来的代码块还可以内嵌到方法中。这样的话,即便内部代码块正常运行,如果外部代码块抛出异常的话,它也没有办法把它的修改提交到数据库中。

用法1:给视图函数装饰器

from django.db import transaction

@transaction.atomic
def my_func(request):
    # This code executes inside a transaction.
    pass

用法2:作为上下文管理器来使用

from django.db import transaction

def my_func(request):
    with transaction.atomic():   #保存点
        pass

    return xxxx

一旦把atomic代码块放到try/except中,完整性错误就会被自然的处理掉了,比如下面这个例子:

from django.db import IntegrityError, transaction

@transaction.atomic
def my_func(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

用法3:还可以嵌套使用,函数的事务嵌套上下文管理器的事务,上下文管理器的事务嵌套上下文管理器的事务等

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
       #other_task()  #还要注意一点,如果你在事务里面写了别的操作,只有这些操作全部完成之后,事务才会commit,也就是说,如果你这个任务是查询上面更改的数据表里面的数据,那么看到的还是事务提交之前的数据。
    except IntegrityError:
        handle_exception()

    add_children()

批量插入数据

使用ORM的create向数据库中批量插入数据效率非常低,我做了一个前面页面,当访问这个路由时,就会向数据库中插入数据,完成后再展示到前台。
经测试,访问页面后约五秒只插入2W多行数据。(这还只是一个字段的数据,放在生产中不可想象会有多慢)
代码如下:

# 1. 路由层:
path('test/', views.test_func)
# 2. 模型层
class Test(models.Model):
    """书籍表"""
    name = models.CharField(max_length=64, verbose_name='测试字段')
# 3. 视图层
def test_func(request):
    # 1. 向测试表中插入10万条数据
    for i in range(1, 100000):
        models.Test.objects.create(name=f'第%s条数据' % i)
    # 2. 查询出插入的所有数据并展示给前端
    book_queryset = models.Test.objects.all()
    return render(request, 'test.html', locals())
# 4. 模板层
{% for obj in test_queryset %}
    <p>{{ obj.name }}</p>
{% endfor %}
  • ORM中提供了一个批量插入的方法bulk_create,代码如下:
def test_func(request):
    # 1. 向测试表中插入10万条数据
    test_obj_list = []
    for i in range(1, 100000):
        test_obj = models.Test(name='第%s条数据' % i)
        test_obj_list.append(test_obj)

    models.Test.objects.bulk_create(test_obj_list)
    # 2. 查询出插入的所有数据并展示给前端
    test_queryset = models.Test.objects.all()
    return render(request, 'test.html', locals())
posted @   树苗叶子  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示