07 模型层

一. 配置测试脚本

当你只是想测试django中的某一个py文件内容 那么你可以不用书写前后端交互的形式, 而是直接写一个测试脚本即可:

# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
    import django
    django.setup()
    # 在这个代码块的下面就可以测试django里面的单个py文件了(注意: 导模块也要写在这下面)

二. ORM常用字段类型和参数

1. 常用字段

常用字段 描述 与MySQL字段对应关系
AutoField 必须指定参数primary_key=True指定主键. 如果没有设置主键, 默认创建并以id名作为主键 integer auto_increment
IntegerField 整型字段. 存储宽度4Bytes. 无符号: 0~2^32 有符号: -232/2~232-1 int 或 integer
BigIntegerField 整型字段. 存储宽度8Bytes. 无符号: 0~2^64 有符号: -264/2~264-1 bigint
DeciamlField 浮点字段. 必须指定参数max_digits设置总长度. decimal_places设置小数位长度 numeric(%(max_digits)s, %(decimal_places)s)
EmailField 字符字段. Django Admin以及ModelForm中提供验证机制
CharField 字符字段. 必须指定参数max_length参数设置字符存储个数. Django中的CharField对应的MySQL数据库中的varchar类型,没有设置对应char类型的字段,但是Django允许我们自定义新的字段. varchar(%(max_length)s)
DateField 日期字段. 格式: 年-月-日. 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 date
DateTimeField 日期字段. 格式: 年-月-日 时:分:秒 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 datetime

2. 字段类型(联想记忆: 与MySQL字段对应关系)

2-1. 自增长字段

models.AutoField(primary_key=True)     # 必须填入参数 primary_key=True
models.BigAutoField(primary_key=True)  # 必须填入参数 primary_key=True


# 与MySQL字段对应关系
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',

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

2-2. 二进制字段

models.BinaryField()


# 与MySQL字段对应关系
'BinaryField': 'longblob',

2-3. 布尔型字段

models.BooleanField()      # 该字段传布尔值(False/True) 数据库里面存0/1
models.NullBooleanField()


# 与MySQL字段对应关系
'BooleanField': 'bool',

Django提供了两种布尔类型的字段,上面这种不能为空,下面这种的字段值可以为空。

2-4. 整型字段

掌握
models.IntegerField()                  # 整数列(有符号的) -2147483648 ~ 2147483647
models.BigIntegerField()               # 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807


# 与MySQL字段对应关系
'IntegerField': 'integer',    
'BigIntegerField': 'bigint',    
了解
models.PositiveSmallIntegerField()     # 正小整数 0 ~ 327672147483647
models.PositiveIntegerField()          # 正整数 0 ~ 2147483647

models.SmallIntegerField()             # 小整数 -32768 ~ 32767


# 与MySQL字段对应关系
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SmallIntegerField': 'smallint',  

2-5. 字符串类型

models.CharField(max_length)  # 以varchar类型存储   注: 必须提供max_length参数, max_length表示字符长度
models.TextField()            # longtext  文本类型  该字段可以用来存大段内容(文章、博客...)  没有字数限制

        
# 与MySQL字段对应关系        
'CharField': 'varchar(%(max_length)s)'
'TextField': 'longtext',   
Django Admin以及ModelForm中提供:

邮箱

EmailField()                  # Django Admin以及ModelForm中提供验证机制.  以varchar(254)形式存储

地址

IPAddressField()              # 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"
                    
                    
# 与MySQL字段对应关系     
'IPAddressField': 'char(15)',    
'GenericIPAddressField': 'char(39)',

URL

URLField()                    # Django Admin以及ModelForm中提供验证 URL 

文件

FileField()                   # 给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中 /data/a.txt
    # 参数:
        upload_to = ""             上传文件的保存路径
        storage = None             存储组件,默认django.core.files.storage.FileSystemStorage
     
    
FilePathField()               # Django Admin以及ModelForm中提供读取文件夹下文件的功能
    # 参数:
        path,                      文件夹路径
        match=None,                正则匹配
        recursive=False,           递归下面的文件夹
        allow_files=True,          允许文件
        allow_folders=False,       允许文件夹        
        
        
# 与MySQL字段对应关系     
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',    

图片

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

其他

SlugField()                   # Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField()  # 格式必须为逗号分割的数字
UUIDField()                   # Django Admin以及ModelForm中提供对UUID格式的验证  



# 与MySQL字段对应关系
'SlugField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)'       
'UUIDField': 'char(32)', 

2-6. 时间类型

models.DateField()      # 年-月-日
models.DateTimeField()  # 年-月-日 时:分:秒
models.DurationField()  # 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型


# 与MySQL字段对应关系
'DateField': 'date'
'TextField': 'longtext'    
'DateTimeField': 'datetime'
'DurationField': 'bigint'

2-7. 浮点型

models.FloatField()
models.DecimalField()  # 10进制小数
    # 参数:
        max_digits,小数总长度
        decimal_places,小数位长度
        
        
# 与MySQL字段对应关系    
'FloatField': 'double precision'
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)'

2-8. 关系型字段

详细(第六节): https://www.cnblogs.com/yang1333/articles/12962250.html

# 前提: 先建立基表 再建立外键关系 无序同mysql中操作需要考虑外键关系的创建以及记录的插入顺序

# 一对多关系建表: 建立在多的一方
    publish = models.ForeignKey(to='Publish)

# 多对多关系建表: 建立在查询频率高的一方
    提示: 无序在mysql中操作需要建立中间表, models会默认帮你创建虚拟中间表表
    authors = models.ManToManyField(to='Book')

# 一对一关系建表: 建立在查询频率高的一方
    author_detail = models.OneToOneField(to='AuthorDetail')

# 补充: 建立一对多, 一对一关系的外键关联表, 关联表中默认会在建立的外键字段之后拼接_id, 我们无序指定.

3. 字段参数

3-1. 所有字段都具有的参数

# 更改字段名          
    db_colum=''
    
# 设置主键           
    primary_key=True,默认为False
    
# 给字段设置别名(备注) 
    verbose_name=''
    
# 为字段设置默认值
    default
    
# 字段的唯一键属性    
    unique=True,设置之后,这个字段的没一条记录的每个值是唯一的
    
# 允许字段为空        
    null=True(数据库中字段可以为空),blank=True(网页表单提交内容可以为空),切记不可以将null设置为Fasle的同时还把blank设置为True。会报错的。
    
# 给字段建立索引      
    db_index=True
    
# 在表单中显示说明    
    help_text=''
    
# 字段值不允许更改
    editable=False,默认是True,可以更改。

3-2. 个别字段才有的参数

# CharField(max_length=100)
    字段长度为utf8编码的100个字符串
    
# DateField(unique_for_date=True)
    这个字段的时间必须唯一
    
# DecimalField(max_digits=4, decimal_places=2)
    前者表示整数和小数总共多少数,后者表示小数点的位数

3-3. auto_now 和 auto_now_add (面试)

# 提示: 一般作为DateField和DateTimeField参数
# auto_now=True
    对这条记录内容更新的时间
    
# auto_now_add=True 
    插入这条记录的时间

3-4. 关系型字段的参数

# to
    设置要关联的表    
    
unique=True
	ForeignKey(unique=True)   ===			OneToOneField()
  # 你在用前面字段创建一对一 orm会有一个提示信息 orm推荐你使用后者但是前者也能用
    
    
# to_field    
    置要关联的表的字段  默认不写关联的就是另外一张的主键字段.

# on_delete=models.CASECADE 和 on_update=models.CASECADE
    设置级联更新级联删除. 同等与SQL语言中的ON DELETE CASCADE等约束 (提示: 该操作为Django1.X版本的默认操作, 2.X和3.X需要手动指定)

# db_index
    如果db_index=True 则代表着为此字段设置索引
        
# db_constraint: 
    注意:db_constraint参数只适用于一对一, 或者一对多的关系. 至于多对多也是由双向的一对多关系组合而成, 是在一对多的关系上使用
    是否在数据库中创建外键约束,默认为True。

其余字段参数

models.DO_NOTHING
删除关联数据,引发错误IntegrityError

models.PROTECT
删除关联数据,引发错误ProtectedError

models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)

models.SET_DEFAULT
删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)

models.SET
删除关联数据,
    a. 与之关联的值设置为指定值,设置:models.SET(值)
    b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
   
# on_dalete实际应用
on_delete参数:
    1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
    2、断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
    3、断关联一定要通过逻辑保证表之间数据的安全,不要出现脏数据,代码控制
    4、断关联
    5、级联关系
          作者没了,详情也没:on_delete=models.CASCADE
          出版社没了,书还是那个出版社出版:on_delete=models.DO_NOTHING
          部门没了,员工没有部门(空不能):null=True, on_delete=models.SET_NULL
          部门没了,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT    

3-5. 自关联字段参数

需要在第一个参数中添加‘self’字符串,或写上它自己的表名(模型类名)。

parent = ForeignKey(to='self')

related_name

# related_name 子查询反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
# 例如:
class Classes(models.Model):
    name = models.CharField(max_length=32)

class Student(models.Model):
    name = models.CharField(max_length=32)
    theclass = models.ForeignKey(to="Classes")
    
# 当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:
models.Classes.objects.first().student_set.all()
    
# 当我们在ForeignKey字段中添加了参数 related_name 后
class Student(models.Model):
    name = models.CharField(max_length=32)
    theclass = models.ForeignKey(to="Classes", related_name="students")
    
# 当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写:
models.Classes.objects.first().students.all()

related_query_name

# related_query_name连表查询时, 反向查询操作时,使用的连接前缀,用于替换表

4. 自定义字段

class MyCharField(models.Field):
    # 1. 自定义独有参数
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length, *args, **kwargs)  # max_length一定要是关键字的形式传入

    # 2. 定义存储的类型及约束条件
    def db_type(self, connection):
        return 'Char(%s)' % self.max_length


# 自定义字段使用
class Text(models.Model):
    myfield = MyCharField(max_length=32, null=True)

二. 准备表和基本数据

1. 准备表

from django.db import models


# Create your models here.
class User(models.Model):
    name = models.CharField(max_length=32, verbose_name='用户名')
    age = models.IntegerField(verbose_name='年龄')
    # register_time = models.DateField(verbose_name='年月日')
    register_time = models.DateTimeField(auto_now_add=True, verbose_name='年月日时分秒')
    """
    提示: django自带的sqlite3数据库对日期格式不是很敏感 处理的时候容易出错
    DateField
    DateTimeField
        两个重要参数
        auto_now: 每次操作数据的时候 该字段会自动将当前时间更新
        auto_now_add:在创建数据的时候会自动将当前创建时间记录下来 之后只要不认为的修改 那么就一直不变
    """

    def __str__(self):
        return self.name


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

    authors = models.ManyToManyField(to='Author')
    publish = models.ForeignKey(to='Publish')

    def __str__(self):
        return self.title


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

    author_detail = models.OneToOneField(to='AuthorDetail')

    def __str__(self):
        return self.name


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()  # varchar(254)  该字段类型不是给models看的 而是给的校验性组件看的

    def __str__(self):
        return self.name


class AuthorDetail(models.Model):
    phone = models.BigIntegerField()
    addr = models.CharField(max_length=64)

    def __str__(self):
        return f'{self.phone}'

2. 基本数据

先往出版社表,作者表, 作者详情表准备一些数据。

因为一对一关系外键和一对多差不多,我们用一对多来操作,把一对一的表先建立好,不做操作。

出版社表是一个被图书表关联的表我们先建立这个被关联表。

publish
id email addr
1 东方出版社 东方
2 北方出版社 北方
author
id name age author_detail_id
1 jason 18 1
2 egon 84 2
3 tank 50 3
author_detail
id phone addr
1 110 芜湖
2 120 山东
3 130 惠州

三. 单表操作: 增删改

1. 增

# 1. 方式一: 自动增 .create
models.User.objects.create(name='jason', age=73, register_time='2020-11-11 11:11:11')

from datetime import datetime
models.User.objects.create(name='jason', age=73, register_time=datetime.now().strftime('%Y-%m-%d %X'))

import time
models.User.objects.create(name='jason', age=73, register_time=time.strftime('%Y-%m-%d %X'))


# 2. 方式二: 手动增 .save()
user_obj = models.User(name='alex', age=84, register_time='2020-11-11 11:11:11')
user_obj.save()

2. 删

# 1. 拿到QuerySet对象: 统一的删除
res = models.User.objects.filter(pk=9).delete()
print(res)   # (1, {'app01.User': 1})  第一个参数是被影响的行数
models.User.objects.all().delete()
"""
pk会自动查找到当前表的主键字段 指代的就是当前表的主键字段
用了pk之后 你就不需要指代当前表的主键字段到底叫什么了
    uid
    pid
    sid
    ...
"""


# 2. 拿到用户对象: 单一针对性的删除
user_obj = models.User.objects.filter(pk=11).first()
user_obj.delete()

3. 改

# 1. 方式一: .update
user_obj = models.User.objects.filter(pk=100).update(name='EGON_DSB')
print(user_obj)  # 0
user_queryset = models.User.objects.filter(pk=100)
print(user_queryset, bool(user_queryset))  # <QuerySet []>  False


# 2. 方式二: .get + 赋值 + .save()
user_obj = models.User.objects.get(pk=100)  # app01.models.DoesNotExist: User matching query does not exist.
user_obj.name = 'EGON_DSB1'
user_obj.save()
"""
get方法返回的直接就是当前数据对象
但是该方法不推荐使用
    一旦数据不存在该方法会直接报错
    而filter则不会
    所以我们还是用filter
"""

4. 总结

注意: create创建以后会返回创建的对象本身

res = models.User.objects.create(name='yang333', age=18, register_time='2020-11-11 08:00:00')
print(type(res), res)   # <class 'app01.models.User'> yang333
# 提示: 使用pk标识主键
# 增
    # 第一种方式: 自动保存
        models.User.objects.create(**kwargs).
    # 第二种方式: 手动.save()保存
        models.User(**kwargs).save()
# 删
    # 第一种方式: 统一的删除
        models.User.objects.filter(**Kwargs).delete()
    # 第二种方式: 单一的删除
        models.User.objects.filter(**Kwargs).first().delete()
# 改
    # 第一种方式: 统一的更新
        models.User.objects.filter(**Kwargs).update(**kwargs)
    # 第二种方式: 单一的更新
        user_obj = models.User.objects.filter(**Kwargs).first()
        user_obj.username = 'jason'
        user_obj.save()

# 查
    # 第一种方式: 获取所有.
        models.User.objects.filter()
        models.User.objects.all()
    # 第二种方式: 获取单个对象
        user_queryset = models.User.objects.filter(**kwargs)
        user_obj = user_queryset.first()
        user_obj = user_queryset.last()
        models.User.objects.get()
    # 注意:
        # 返回QuerySet对象. 没有值返回空的QuerySet对象
            .filter()
            .all()
        # 获取单个数据对象.
            QuerySet.first()  在QuerySet为空的情况下返回None
            QuerySet.last()   在QuerySet为空的情况下返回None
            .get()            指定的条件的返回结果有多个或者不存在抛出异常(不推荐使用)

四. 必知必会13条 (ORM提供的13条API)

1. all() 查询所有

'''
返回QuerySet对象. QuerySet对象内部包含所有数据对象
'''
user_queryset = models.User.objects.all()
print(user_queryset)  # <QuerySet [<User: User object>, <User: User object>,,...]>

2. filter(**kwargs) 过滤

'''
返回QuerySet对象. 内部包含与所给筛选条件相匹配的数据对象
不指定参数默认查询所有
带有过滤条件的查询, 当结果不存在返回空的QuerySet对象, 布尔值为False
'''
user_queryset = models.User.objects.filter(name='alex')
print(user_queryset)  # <QuerySet [<User: User object>, <User: User object>]>

user_queryset = models.User.objects.filter()
print(user_queryset)  # <QuerySet [<User: User object>, <User: User object>,,...]>

user_queryset = models.User.objects.filter(pk=9999)
print(user_queryset, bool(user_queryset))  # <QuerySet []> False

3. get(**kwargs)

'''
直接获取数据对象
只能指定一个筛选条件. 如果指定的筛选条件返回的结果不唯一 或者 不存在 抛出异常
'''
user_queryset = models.User.objects.get(pk=1)
print(user_queryset)  # User object

# models.User.objects.get(name='alex')  # get() returned more than one User -- it returned 2!.

# models.User.objects.get(pk=9999999)     #  User matching query does not exist.

4. last()

'''
获取QuerySet列表中最后一个数据对象. 
用在QuerySet对象之后, 如果QuerySet对象为空, 再使用它返回None
'''
user_queryset = models.User.objects.filter(age=73)
print(user_queryset)  # <QuerySet [..., <User: rrr>, <User: qwe>]>

user_obj = models.User.objects.filter(age=73).last()
print(user_obj)       # qwe

user_obj = models.User.objects.filter(pk=99999).last()
print(user_obj)       # None

5. first()

'''
直接获取QuerySet列表中第一个数据对象. 
用在QuerySet对象之后, 如果QuerySet对象为空, 再使用它返回None
'''
user_queryset = models.User.objects.filter(age=73)
print(user_queryset)  # <QuerySet [<User: EGON_DSB1>, <User: egon>,...]>

user_obj = models.User.objects.filter(age=73).first()
print(user_obj)       # EGON_DSB1

user_obj = models.User.objects.filter(pk=99999).first()
print(user_obj)       # None

6. values(*field)

'''
返回QuerySet对象. 内部是一种列表套字典的格式. 字典的key就是指定的字段名
'''
user_queryset = models.User.objects.values('name', 'age')
print(user_queryset)  # <QuerySet [{'name': 'EGON_DSB1', 'age': 73}, ..., {'name': 'alex', 'age': 84}]>


# 注意!!!: 指定字段不存在抛出异常. 
# django.core.exceptions.FieldError: Cannot resolve keyword 'xxxxxxxxxx' into field. Choices are: age, id, name, register_time
user_queryset = models.User.objects.values('xxxxxxxxxx')
print(user_queryset)  # <Query

7. values_list(*field)

'''
返回QuerySet对象. 
内部是一种列表套元组的格式. 元组的第一个值就是指定的第一个字段对应的数据, 依此类推.
'''
user_queryset = models.User.objects.values_list('name', 'age')
print(user_queryset)  # <QuerySet [('EGON_DSB1', 73), ..., ('alex', 84)]>

# 注意!!!: 指定字段不存在抛出异常. 
# django.core.exceptions.FieldError: Cannot resolve keyword 'xxxxxxxxxx' into field. Choices are: age, id, name, register_time
user_queryset = models.User.objects.values_list('xxxxxxxxxx')
print(user_queryset)  # <Query

8. distinct() 去重

'''
注意!!!: 必须排除主键字段 或 唯一字段才会有意义
要排除使用filter无法筛选. 一般用在.values() 或 .value_list()后面
'''
user_queryset = models.User.objects.values('age')
print(user_queryset)       # <QuerySet [{'age': 73}, {'age': 73}, {'age': 73}, {'age': 84}, {'age': 84}]>
dis_user_queryset = user_queryset.distinct()
print(dis_user_queryset)   # <QuerySet [{'age': 73}, {'age': 84}]>

9. order_by(*field) 排序

'''
对查询结果排序.  默认升序.  如果想要降序在对应要查询的字段前指定`-`号
'''
user_queryset = models.User.objects.values('age')
print(user_queryset)     # <QuerySet [{'age': 73}, {'age': 84}]>

user_queryset_asc = user_queryset.order_by('age')
print(user_queryset_asc)  # <QuerySet [{'age': 73}, {'age': 84}]>

user_queryset_desc = user_queryset.order_by('-age')
print(user_queryset_desc)  # <QuerySet [{'age': 84}, {'age': 73}]>

10. reverse()

'''
注意!!!: 反转的前提是数据已经排过序. 没排过序reverse将不起作用.
'''
user_queryset_desc = models.User.objects.values('age').reverse()
print(user_queryset_desc)  # <QuerySet [{'age': 73}, {'age': 84}]>

user_queryset_desc = models.User.objects.values('age').order_by('-age')
print(user_queryset_desc)  # <QuerySet [{'age': 84}, {'age': 73}]>

user_queryset_desc_rev = user_queryset_desc.reverse()
print(user_queryset_desc_rev)  # <QuerySet [{'age': 73}, {'age': 84}]>

11. count()

'''
查询QuerySet内部所包含的数据对象的个数
'''
user_queryset = models.User.objects.all()
print(user_queryset)  # <QuerySet [<User: EGON_DSB1>, <User: alex>]>
all_user_queryset = models.User.objects.count()
print(all_user_queryset)  # 2

user_queryset = models.User.objects.filter(name='EGON_DSB1')
print(user_queryset)      # <QuerySet [<User: EGON_DSB1>]>
all_user_queryset = models.User.objects.filter(name='EGON_DSB1').count()
print(all_user_queryset)  # 1

12. exclude(**kwargs):

'''
排除. 查询与所给筛选条件不匹配的
'''
user_queryset = models.User.objects.exclude(age=73)
print(user_queryset)                # <QuerySet [<User: alex>, <User: alex>]>
print(user_queryset.values('age'))  # <QuerySet [{'age': 84}, {'age': 84}]

# 注意!!!: 排除不存在的返回空的QuerySet对象
user_queryset = models.User.objects.exclude(age=11111111111111)
print(user_queryset)  # <QuerySet []>

13. exists()

'''
查找所返回的QuerySet结果包含数据,就返回True,否则返回False
'''
user_queryset = models.User.objects.filter(pk=999999).exists()
print(user_queryset)  # False

user_queryset = models.User.objects.all().exists()
print(user_queryset)  # True

14. 总结

# 返回QuerySet对象的方法
    .all()          获取所有.
    .filter()       过滤. 不指定参数获取所有. 查询结果不存在返回空的QuerySet对象, 布尔值False
    .distinct()     去重. 必须排除主键字段 或 唯一字段才会有意义. filter无法筛选, 一般用在.values() 或 .value_list()后面
    .order_by()     排序. 默认升序. 指定降序在字段前加`-`
    .reverse()      反转. 只能对排序过后进行反转. 必须在.order_by()之后.
    .exclude()      排除. 排除指定的, 展示所有.

# 返回特殊QuerySet对象的方法
    .values()       返回QuerySet之内部列表套字典格式. 字典key就是指定的字段.
    .values_list()   返回QuerySet之内部列表套元组格式. 元组中值就是指定字段对应的值, 按照指定字段顺序显示.
    注意!!!: 指定的字段不存在, 将会抛出异常

# 返回数据对象的方法
    .get()          直接获取数据对象. 只能指定一个筛选条件. 如果指定的筛选条件返回的结果不唯一 或者 不存在 抛出异常
    .first()        获取QuerySet对象列表内第一个值.  用在QuerySet对象之后, 如果QuerySet对象为空, 再使用它返回None
    .last()         获取QuerySet对象列表内最后一个值. 用在QuerySet对象之后, 如果QuerySet对象为空, 再使用它返回None

# 返回数字的方法
    .count()        统计QuerySet对象列表内数据对象的个数

# 返回布尔值的方法
    .exist()        没软用

五. 查看内部封装的sql语句的2种形式

# 第一种: QuerySet.query
user_queryset = models.User.objects.values_list('name', 'age')
print(user_queryset.query)  # SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`


# 第二种: 执行脚本时打印日志显示到终端. 复制以下日志内容到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. 参数介绍

'''
field__gt=value  field__lt=value
field__gte=value field__lte=value
field__in=[value1, value2, ...]
field__range=[value1, value2]
field__contains='substring'     field__icontains='substring'
field__startswith='substring'   field__endswith='substring'
field__istartswith='substring'  field__iendswith='substring'
field__year='2020'  field__month='1'  field__day='30'
field__year=2020    field__month=1'   field__day=30
'''

2. 实例操作

# 查询年龄大于35岁的数据
models.User.objects.filter(age__gt=35)

# 查询年龄小于35岁的数据
models.User.objects.filter(age__lt=35)

# 查询年龄大于等于33岁  查询年龄小于等于30岁
models.User.objects.filter(age__gte=30)
models.User.objects.filter(age__lte=30)

# 查询年龄是22 或者 30 或者 73
models.User.objects.filter(age__in=[22, 30, 73])

# 查询年龄在18到40岁之间的  首尾都要
models.User.objects.filter(age__range=[18, 40])

# 模糊查询: 查询出名字里面含有`s`的数据(区分大小写)   查询出名字里面含有`s`的数据(忽略大小写)
models.User.objects.filter(name__contains='s')
models.User.objects.filter(name__icontains='s')

# 以`j`开头(区分大小写)  以`j`开头(不区分大小写)   以`5`结尾(区分大小写)
models.User.objects.filter(name__startswith='j')
models.User.objects.filter(name__istartswith='j')
models.User.objects.filter(name__endswith='5')

# 查询出注册时间是 2020 1月的
models.User.objects.filter(register_time__year=2020)

3. 总结

# 注意: 争对字段使用. 如: field__gt
__gt __lt __gte __glt
__in=[]        __range=[start, stop]
__contains     __icontains  i全称忽略ignore
__startswith   __istartswith
__endswith     __iendswith
__year='2020'  __year=2020
__month='1'    __month=1
__day='20'     __day=20

七. 一对多外键增删改

1. 2种设置外键字段的方式

'''
2种设置外键字段的方式
    1. 实际字段指定id  publish_id=id
    2. 虚拟字段指定对象 publish=publish_obj
'''

2. 增 create

models.Book.objects.create(title='论语', price='333.33', publish_id=1)
models.Book.objects.create(title='孟子', price='444.44', publish_id=2)
models.Book.objects.create(title='老子', price='555.55', publish_id=2)

publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.create(title='三字经', price='666.66', publish=publish_obj)

3. 删 delete

models.Book.objects.filter(pk=1).delete()  # 级联删除

4. 改 update

models.Book.objects.filter(pk=2).update(publish_id=1)

publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.filter(pk=2).update(publish=publish_obj)

5. 总结

# 增 create
    # 实际
        models.Book.objects.create(publish_id=id, **kwargs)
    # 虚拟
        publish_obj = models.Publish.objects.filter(pk=1).first()
        models.Book.objects.create(publish=publish_obj, **kwargs)

# 删 delete
    models.Book.objects.filter(pk=1).delete()  # 级联删除

# 改 update
    # 实际
        models.Book.objects.filter(pk=1).update(publish_id=id)
    # 虚拟
        publish_obj = models.Publish.objects.filter(pk=1).first()
        models.Book.objects.filter(pk=1).update(publish=publish_obj)

# 查
    # 实际

    # 虚拟

八. 多对多外键增删改: 本质就是操作虚拟中间表(第三种表)

1. 增 add

'''
add给第三张关系表添加数据
括号内既可以传数字也可以传对象 并且都支持多个
'''
book_obj = models.Book.objects.filter(pk=3).first()
# print(book_obj.authors)    # app01.Author.None(就类似于你已经到了第三张关系表了)
# book_obj.authors.add(1)    # 添加一个book_id是3对应的author_id是1的记录
# book_obj.authors.add(2, 3)

book_obj = models.Book.objects.filter(pk=3).first()
authors1 = models.Author.objects.filter(pk=1).first()
authors2 = models.Author.objects.filter(pk=2).first()
authors3 = models.Author.objects.filter(pk=3).first()
book_obj.authors.add(authors1)
book_obj.authors.add(authors2, authors3)

2. 删 remove

'''
括号内既可以传数字也可以传对象 并且都支持多个
'''
book_obj = models.Book.objects.filter(pk=2).first()
book_obj.authors.remove(1)     # 删除虚拟表中book_id是2的对应的author_id是1的记录
book_obj.authors.remove(2, 3)

3. 改 set

'''
括号内必须传一个可迭代对象,该对象内既可以数字也可以对象 并且都支持多个
注意!!!: set操作是一种新增操作. 执行set一上来先将括号内没有指定的全部清除, 如果有则保留, 没有则新增. 如果想修改某一个数据, 必须对源数据进行指定, 不然源数据将会被清除.
'''
book_obj = models.Book.objects.filter(pk=3).first()
book_obj.authors.set([1, 2])  # 先删除author_id不是等于1,2的, 如果没有1, 2则添加

book_obj = models.Book.objects.filter(pk=3).first()
authors1 = models.Author.objects.filter(pk=1).first()
authors2 = models.Author.objects.filter(pk=2).first()
book_obj.authors.set([authors1, authors2])

4. 清空 clear

'''
括号内不要加任何参数
'''
book_obj = models.Book.objects.filter(pk=3).first()
book_obj.authors.clear()   # 清空book_id为3所对应的所有author_id

5. 总结

# 增 add 可以指定多个
    # 提示: .authors时已经到第三张表了
    # 实际
        models.Book.objects.filter(pk=1).first().authors.add(1, 2)
    # 虚拟
        author = models.Author.objects.filter(pk=1).first()
        models.Book.objects.filter(pk=1).first().authors.add(author)
# 删 remove 可以指定多个
    # 提示: .authors时已经到第三张表了
    # 实际
        models.Book.objects.filter(pk=1).first().authors.remove(1, 2)
    # 虚拟
        author = models.Author.objects.filter(pk=1).first()
        models.Book.objects.filter(pk=1).first().authors.remove(author)
# 改 set 放可迭代对象, 可迭代对象中可指定多个
    # 注意: 是一种新增操作. 执行set一上来先将括号内没有指定的全部清除, 如果有则保留, 没有则新增. 如果想修改某一个数据, 必须对源数据进行指定, 不然源数据将会被清除.
    # 实际
        models.Book.objects.filter(pk=1).first().authors.set([1, 2])
    # 虚拟
        author = models.Author.objects.filter(pk=1).first()
        models.Book.objects.filter(pk=1).first().authors.set([author])

# 清空 clear
    models.Book.objects.filter(pk=1).first().authors.clear()

九. 查询前必须要搞清楚的一件事

# 正向查询
    正向查询按字段.
    正向: 外键字段在的一方查不在的一方
    如果返回结果多个还需要.all()
# 反向查询
    反向查询按表名小写.
    反向: 外键字段不在的一方查在的一方
    如果返回结果多个还需要连接_set.all()
# 提示:
    书籍与作者, 外键字段在书籍.
    作者与作者详情, 外键字段在作者.
    书籍与出版社外键字段在书籍.

十. 子查询: 基于对象的跨表查询

1. 正向查询

'''
正向查询按字段
    当你的结果可能有多个的情况下就需要加.all()
        book_obj.authors.all()
    如果是一个则直接拿到数据对象
        book_obj.publish
        author_obj.author_detail
'''
# 1. 查询书籍主键为1的出版社
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish.name)  # 北方出版社

# 2. 查询书籍主键为2的作者: 一本书可能被多个作者出版使用.all()
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.authors)        # app01.Author.None
print(book_obj.authors.all())  # <QuerySet [<Author: jason>, <Author: egon>]>

# 3. 查询作者jason的电话号码:
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.author_detail)        # AuthorDetail object
print(author_obj.author_detail.phone)  # 110

2. 反向查询

'''
反向查询按表名小写
    如果查询结果可以有多个就必须需要加_set.all()
        publish_obj.book_set.all()
        author_obj.book_set.all()
    如果查询结果只有一个的时候就不需要加_set.all()
        author_detail_obj.author
'''
# 4. 查询出版社是东方出版社出版的书: 一个出版社可以出版多本书使用.all()
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
print(publish_obj.book_set)          # app01.Book.None
print(publish_obj.book_set.all())    # <QuerySet [<Book: 论语>, <Book: 三字经>]>

# 5. 查询作者是jason写过的书: 一个作者jason可以写过多本书
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set)            # app01.Book.None
print(author_obj.book_set.all())      # <QuerySet [<Book: 夕阳西下>]>

# 6. 查询手机号是110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
print(author_detail_obj.author.name)  # jason

3. 总结

'''子查询: 基于对象的跨表查询'''
# 正向: 查询书籍主键为1的出版社
    models.Book.objects.filter(pk=1).first().publish.name
# 正向: 查询书籍主键为2的作者
    models.Book.objects.filter(pk=2).first().authors.all()
# 正向: 查询作者jason的电话号码
    models.Author.objects.filter(name='jason').first().author_detail.phone
# 反向: 查询出版社是东方出版社出版的书
    models.Publish.objects.filter(name='东方出版社').first().publish_set.all()
# 反向: 查询作者是jason写过的书
    models.Author.objects.filter(name='jason').first().book_set.all()
# 反向: 查询手机号是110的作者姓名
    models.AuthorDetail.objects.filter(phone=110).first().author.name

十一. 连表查询: 基于双下划线的连表查询

1. 查询jason的手机号和作者姓名

# 正向
res = models.Author.objects.filter(name='jason').values('author_detail__phone', 'name')
print(res)  # <QuerySet [{'author_detail__phone': 110, 'name': 'jason'}]>

# 反向
res = models.AuthorDetail.objects.filter(author__name='jason')
print(res)  # <QuerySet [<AuthorDetail: 110>]>
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone', 'author__name')
print(res)  # <QuerySet [{'phone': 110, 'author__name': 'jason'}]>

2. 查询书籍主键为1的出版社名称和书的名称

# 正向
res = models.Book.objects.filter(pk=1).values('title', 'publish__name')
print(res)  # <QuerySet [{'title': '红龙盟', 'publish__name': '北方出版社'}]>

# 反向
res = models.Publish.objects.filter(book__pk=1).values('name', 'book__title')
print(res)  # <QuerySet [{'name': '北方出版社', 'book__title': '红龙盟'}]>

3. 查询书籍主键为1的作者姓名

# 正向
res = models.Book.objects.filter(pk=1).values('authors__name')
print(res)  # <QuerySet [{'authors__name': 'tank'}]>

# 反向
res = models.Author.objects.filter(book__pk=1).values('name')
print(res)  # <QuerySet [{'name': 'tank'}]>

4. 查询书籍主键是1的作者的手机号

# 正向
res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)  # <QuerySet [{'authors__author_detail__phone': 130}]>

# 反向
res = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')
print(res)  # <QuerySet [{'phone': 130}]>

5. 总结

'''连表查询: 基于双下划线的连表查询'''
# 查询jason的手机号和作者姓名
    # 正向
        models.Author.objects.filter(name='jason').values('author_detail__phone', 'name')
    # 反向
        models.AuthorDetail.objects.filter(author__name='jason').values('phone', 'author__name')

# 查询书籍主键为1的出版社名称和书的名称
    # 正向
        models.Book.objects.filter(pk=1).values('publish__name', 'title')
    # 反向
        models.Publish.objects.filter(book__pk=1).values('name' 'book__title')

# 查询书籍主键为1的作者姓名
    # 正向
        models.Book.objects.filter(pk=1).values('authors__name')
    # 反向
        models.Author.objects.filter(book_pk=1).values('name')

# 查询书籍主键是1的作者的手机号
    # 正向
        models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
    # 反向
        models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')

十二. 总结1

# 配置测试脚本
    manage.py中3行语句. 再写入如下内容:
    import django
    django.setup()



# 单表操作: 数据的增删改查
    # 提示: 使用pk标识主键
    # 增
        # 第一种方式: 自动保存
            models.User.objects.create(**kwargs).
        # 第二种方式: 手动.save()保存
            models.User(**kwargs).save()
    # 删
        # 第一种方式: 统一的删除
            models.User.objects.filter(**Kwargs).delete()
        # 第二种方式: 单一的删除
            models.User.objects.filter(**Kwargs).first().delete()
    # 改
        # 第一种方式: 统一的更新
            models.User.objects.filter(**Kwargs).update(**kwargs)
        # 第二种方式: 单一的更新
            user_obj = models.User.objects.filter(**Kwargs).first()
            user_obj.username = 'jason'
            user_obj.save()

    # 查
        # 第一种方式: 获取所有.
            models.User.objects.filter()
            models.User.objects.all()
        # 第二种方式: 获取单个对象
            user_queryset = models.User.objects.filter(**kwargs)
            user_obj = user_queryset.first()
            user_obj = user_queryset.last()
            models.User.objects.get()
        # 注意:
            # 返回QuerySet对象. 没有值返回空的QuerySet对象
                .filter()
                .all()
            # 获取单个数据对象.
                QuerySet.first()  在QuerySet为空的情况下返回None
                QuerySet.last()   在QuerySet为空的情况下返回None
                .get()            指定的条件的返回结果有多个或者不存在抛出异常(不推荐使用)

# 必知必会13条
    # 返回QuerySet对象的方法
        .all()          获取所有.
        .filter()       过滤. 不指定参数获取所有. 查询结果不存在返回空的QuerySet对象, 布尔值False
        .distinct()     去重. 必须排除主键字段 或 唯一字段才会有意义. filter无法筛选, 一般用在.values() 或 .value_list()后面
        .order_by()     排序. 默认升序. 指定降序在字段前加`-`
        .reverse()      反转. 只能对排序过后进行反转. 必须在.order_by()之后.
        .exclude()      排除. 排除指定的, 展示所有.

    # 返回特殊QuerySet对象的方法
        .values()       返回QuerySet之内部列表套字典格式. 字典key就是指定的字段.
        .values_list()   返回QuerySet之内部列表套元组格式. 元组中值就是指定字段对应的值, 按照指定字段顺序显示.
        注意!!!: 指定的字段不存在, 将会抛出异常

    # 返回数据对象的方法
        .get()          直接获取数据对象. 只能指定一个筛选条件. 如果指定的筛选条件返回的结果不唯一 或者 不存在 抛出异常
        .first()        获取QuerySet对象列表内第一个值.  用在QuerySet对象之后, 如果QuerySet对象为空, 再使用它返回None
        .last()         获取QuerySet对象列表内最后一个值. 用在QuerySet对象之后, 如果QuerySet对象为空, 再使用它返回None

    # 返回数字的方法
        .count()        统计QuerySet对象列表内数据对象的个数

    # 返回布尔值的方法
        .exist()        没软用

# 查看内部封装的sql语句的2种形式
    第一种: QuerySet.query
    第二种: 执行脚本时打印日志显示到终端. 复制日志内容到settings.py中

# 神奇的双下划线查询
    # 注意: 争对字段使用. 如: field__gt
    __gt __lt __gte __glt
    __in=[]        __range=[start, stop]
    __contains     __icontains  i全称忽略ignore
    __startswith   __istartswith
    __endswith     __iendswith
    __year='2020'  __year=2020
    __month='1'    __month=1
    __day='20'     __day=20


# 2种外键字段指定的方式
    第一种: 实际字段指定id  publish_id=id
    第二种: 虚拟字段指定对象 publish=publish_obj


# 一对多外键增删改查
    # 增 create
        # 实际
            models.Book.objects.create(publish_id=id, **kwargs)
        # 虚拟
            publish_obj = models.Publish.objects.filter(pk=1).first()
            models.Book.objects.create(publish=publish_obj, **kwargs)

    # 删 delete
        models.Book.objects.filter(pk=1).delete()  # 级联删除

    # 改 update
        # 实际
            models.Book.objects.filter(pk=1).update(publish_id=id)
        # 虚拟
            publish_obj = models.Publish.objects.filter(pk=1).first()
            models.Book.objects.filter(pk=1).update(publish=publish_obj)

    # 查
        # 实际

        # 虚拟

# 多对多外键增删改查: 本质就是操作虚拟中间表(第三种表)
    # 增 add 可以指定多个
        # 提示: .authors时已经到第三张表了
        # 实际
            models.Book.objects.filter(pk=1).first().authors.add(1, 2)
        # 虚拟
            author = models.Author.objects.filter(pk=1).first()
            models.Book.objects.filter(pk=1).first().authors.add(author)
    # 删 remove 可以指定多个
        # 提示: .authors时已经到第三张表了
        # 实际
            models.Book.objects.filter(pk=1).first().authors.remove(1, 2)
        # 虚拟
            author = models.Author.objects.filter(pk=1).first()
            models.Book.objects.filter(pk=1).first().authors.remove(author)
    # 改 set 放可迭代对象, 可迭代对象中可指定多个
        # 注意: 是一种新增操作. 执行set一上来先将括号内没有指定的全部清除, 如果有则保留, 没有则新增. 如果想修改某一个数据, 必须对源数据进行指定, 不然源数据将会被清除.
        # 实际
            models.Book.objects.filter(pk=1).first().authors.set([1, 2])
        # 虚拟
            author = models.Author.objects.filter(pk=1).first()
            models.Book.objects.filter(pk=1).first().authors.set([author])

    # 清空 clear
        models.Book.objects.filter(pk=1).first().authors.clear()


# 查询语法
    # 正向查询
        正向查询按字段.
        正向: 外键字段在的一方查不在的一方
        如果返回结果多个还需要.all()
    # 反向查询
        反向查询按表名小写.
        反向: 外键字段不在的一方查在的一方
        如果返回结果多个还需要连接_set.all()
    # 提示:
        书籍与作者, 外键字段在书籍.
        作者与作者详情, 外键字段在作者.
        书籍与出版社外键字段在书籍.

# 子查询: 基于对象的跨表查询
    # 正向: 查询书籍主键为1的出版社
        models.Book.objects.filter(pk=1).first().publish.name
    # 正向: 查询书籍主键为2的作者
        models.Book.objects.filter(pk=2).first().authors.all()
    # 正向: 查询作者jason的电话号码
        models.Author.objects.filter(name='jason').first().author_detail.phone
    # 反向: 查询出版社是东方出版社出版的书
        models.Publish.objects.filter(name='东方出版社').first().publish_set.all()
    # 反向: 查询作者是jason写过的书
        models.Author.objects.filter(name='jason').first().book_set.all()
    # 反向: 查询手机号是110的作者姓名
        models.AuthorDetail.objects.filter(phone=110).first().author.name

# 连表查询: 基于双下划线的连表查询
    # 查询jason的手机号和作者姓名
        # 正向
            models.Author.objects.filter(name='jason').values('author_detail__phone', 'name')
        # 反向
            models.AuthorDetail.objects.filter(author__name='jason').values('phone', 'author__name')

    # 查询书籍主键为1的出版社名称和书的名称
        # 正向
            models.Book.objects.filter(pk=1).values('publish__name', 'title')
        # 反向
            models.Publish.objects.filter(book__pk=1).values('name' 'book__title')

    # 查询书籍主键为1的作者姓名
        # 正向
            models.Book.objects.filter(pk=1).values('authors__name')
        # 反向
            models.Author.objects.filter(book_pk=1).values('name')

    # 查询书籍主键是1的作者的手机号
        # 正向
            models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
        # 反向
            models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')

十三. 聚合查询 aggregate

"""
介绍: 聚合查询通常情况下都是配合分组一起使用的. 如果你只想使用聚合函数, 但是不想分组, 那么就应该使用aggregate.
使用: 直接在objects后面链接. 
返回: 返回字典格式的数据. 如果是对price字段求平均值, 那么返回格式是: {'price__avg': 值} 

记忆跟数据库相关的模块的方法:  
    基本上都在django.db.models里面
    如果上述没有那么应该在django.db里面
"""

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

# 1. 所有书的平均价格
res = models.Book.objects.aggregate(Avg('price'))
print(res)   # {'price__avg': 555.55}

# 2. 上述方法一次性使用
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk'))
print(res)   #  {'price__max': Decimal('888.88'), 'price__min': Decimal('333.33'), 'price__sum': Decimal('3888.85'), 'price__avg': 555.55, 'pk__count': 7}

十四. 分组查询 annotate

1. 介绍

"""
分组注意事项: 分组只能拿到分组得依据. 按照什么分组就只能拿到什么组. 其他字段不能直接获取, 需要借助一些方法(聚合函数) 
提示: 可以指定多个分组, 指定多个分组, 当然就可以获取多个分组之间的分组依据.
MySQl中设置全局生效得分组严格模式: set global sql_mode='only_full_group_by';

使用步骤:  
    第一步: 指定分组的依据
        第一种情况: 默认分组. annotate直接在objects后面链接时, models后面点什么就按照什么分组.
            例子: 按照书分组
            models.Book.objects.annotate(sum_price=Sum)
        第二种情况: 指定分组. annotate跟在values后面, values中指定什么字段就按照什么分组
            例子: 按照书中的价格分组. 
            models.Book.objects.values('price').annotate()
    第二步: 为分组的字段取别名
    第三步: 在annotate后面使用values可以获取分组的依据 和 分组举和的结果
    
返回: 返回QuerySet对象
    提示: 只要你的orm语句得出的结果还是一个queryset对象, 那么它就可以继续无限制的点queryset对象封装的方法.
    
特殊情况使用: 使用Count进行获取分组举和的结果应该是要对主键 或者 唯一字段进行的统计. 所以使用Count进行统计时比如: authors__id 就可以写成 authors    
"""

2. 统计每一本书的作者个数

"""
原生SQL语句:  !!!注意!!! 可能作者没有写过书. 
select book_id, count(author_id) from app01_book_authors group by book_id;

select app01_book.title,t1.author_count from 
    app01_book 
    left join 
    (select book_id, count(author_id) as author_count from app01_book_authors group by book_id) as t1
    on t1.book_id=app01_book.id;
"""
# res = models.Book.objects.annotate(book_count=Count('authors__id')).values('title', 'book_count')
res = models.Book.objects.annotate(book_count=Count('authors')).values('title', 'book_count')
print(res)  # <QuerySet [{'title': '红龙盟', 'book_count': 1}, ... , {'title': '三字经', 'book_count': 0}]>

3. 统计每个出版社卖的最便宜的书的价格

"""
原生SQL语句:
select publish_id, title, min(price) from app01_book group by publish_id;

select app01_publish.name, t1.* from 
    app01_publish
    inner join 
    (select publish_id, title, min(price) from app01_book group by publish_id) as t1
    on app01_publish.id=t1.publish_id;
"""
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(res)   # <QuerySet [{'name': '东方出版社', 'min_price': Decimal('333.33')}, {'name': '北方出版社', 'min_price': Decimal('444.44')}]>

4. 统计不止一个作者的图书

"""
第一步: 对书分组, 求作者的个数
第二步: 过滤出作者个数大于1的.

原生SQL语句:
select book_id, count(author_id) from app01_book_authors group by book_id having count(author_id)>1;

select app01_book.title, t1.author_count
    from app01_book 
    inner join 
    (select book_id, count(author_id) as author_count from app01_book_authors group by book_id having count(author_id)>1) as t1
    on t1.book_id=app01_book.id;
"""
res = models.Book.objects.annotate(author_count=Count('authors')).filter(author_count__gt=1).values('title', 'author_count')
print(res)  # <QuerySet [{'title': '夕阳西下', 'author_count': 2}]>

5. 查询每个作者出的书的总价格

"""
原生SQL语句:
select app01_book_authors.author_id, sum(app01_book.price) 
    from app01_book 
    inner join 
    app01_book_authors
    on app01_book_authors.book_id=app01_book.id
    group by app01_book_authors.author_id;
    
select app01_author.name, t2.sum_price
    from app01_author
    inner join 
    (select app01_book_authors.author_id as author_id, sum(app01_book.price) as sum_price 
        from app01_book 
        inner join 
        app01_book_authors
        on app01_book_authors.book_id=app01_book.id
        group by app01_book_authors.author_id) as t2     
    on t2.author_id=app01_author.id;
"""
res = models.Author.objects.annotate(price_sum=Sum('book__price')).values('name', 'price_sum')
print(res)  # <QuerySet [{'name': 'jason', 'price_sum': Decimal('444.44')}, {'name': 'egon', 'price_sum': Decimal('444.44')}, {'name': 'tank', 'price_sum': Decimal('888.88')}]>
'''

十五. 总结2

# 聚合查询 aggregate
    # 作用: 在不分组的前提下也能使用聚合函数
    # 使用
        from django.db.models import Min, Max, Sum, Avg, Count
        单个: models.Book.objects.aggregate(Min('price'))
        多个: models.Book.objects.aggregate(Min('price'), Max('price'), Sum('price'), Avg('price'), Count('pk'))
    # 返回字典类型: {'price__avg': 值}

# 分组查询 annotate
    # 作用: 分组.
    # 分组注意事项: 分组只能拿到分组的依据 以及 分组举和的结果. (拓展: 可多个分组, 多个分组就含有多个分组的依据.)
    # MySQL设置全局分组严格模式: set global sql_mode=only_full_group_by;
    # 使用:
        from django.db.models import Min, Max, Sum, Avg, Count
        # 默认分组: 对models点后面的表进行分组
            models.Book.objects.annotate(price_avg=Avg('price')).values('title', 'price_avg')

        # 特殊情况使用: Count一般统计唯一字段 或 主键. 使用时可以简写
            models.Book.objects.annotate(author_count=Count('authors__id')).values('title', 'author_count')
            简写:  models.Book.objects.annotate(author_count=Count('authors')).values('title', 'author_count')

        # 指定分组: 分组依据publish__id
            models.Book.objects.values('publish__id').annotate(publish_count=Count('title')).values('publish_count', 'publish__id', 'publish__name')

十六. choices参数(数据库字段设计常见)

1. choices使用

class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    """
    作用: 针对可以被预测到结果的字段对应的数据, 那么可以为该字段定制choices参数, 取数据的时候就可以通过`对象.get_设置choices的字段_display()`获取到定制的数据.
    例如:  性别 学历 工作经验 是否结婚 是否生子 客户来源等等. 针对某个可以列举完全的可能性字段我们用choices存储.
    使用: 
        1. 为即将创建的字段定制choices参数. 
            参数是一个二元组的形式 ((), ()). 
            元组中第一个参数要满足存储字段的约束条件
        2. 存: 指定对设置了choices的字段存数据
            数据存得时候没有列举出来的数字也能存. 范围还是按照字段类型决定
        3. 取: 指定对设置了choices的字段取数据.  
            方法: 对象.get_设置choices的字段_display()
            如果取得时候如果没有对应关系 那么字段是什么还是展示什么
    """
    # 性别
    gender_choices = (
        (0, '女'),
        (1, '男'),
        (2, '其他'),
    )
    gender = models.IntegerField(choices=gender_choices)

    # 成绩
    score_choices = (
        ('A', '优秀'),
        ('B', '良好'),
        ('C', '及格'),
        ('D', '不合格'),
    )
    # 保证字段类型跟列举出来的元祖第一个数据类型一致即可
    score = models.CharField(choices=score_choices, null=True)


user_obj1 = models.User.objects.create(username='jason', age=18, gender=1)
models.User.objects.create(username='egon', age=85, gender=2)
models.User.objects.create(username='tank', age=40, gender=3)
# 存: 存的时候 没有列举出来的数字也能存(范围还是按照字段类型决定)
user_obj4 = models.User.objects.create(username='tony', age=45, gender=4)

# 取: 只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
print(user_obj1.get_gender_display())  # 男
print(user_obj4.get_gender_display())  # 4 (如果没有对应关系 那么字段是什么还是展示什么)

2. 实际项目案例

# CRM相关内部表
class School(models.Model):
    """
    校区表
    如:
        北京沙河校区
        上海校区

    """
    title = models.CharField(verbose_name='校区名称', max_length=32)

    def __str__(self):
        return self.title

class Course(models.Model):
    """
    课程表
    如:
        Linux基础
        Linux架构师
        Python自动化开发精英班
        Python自动化开发架构师班
        Python基础班
        go基础班
    """
    name = models.CharField(verbose_name='课程名称', max_length=32)

    def __str__(self):
        return self.name

class Department(models.Model):
    """
    部门表
    市场部     1000
    销售       1001

    """
    title = models.CharField(verbose_name='部门名称', max_length=16)
    code = models.IntegerField(verbose_name='部门编号', unique=True, null=False)

    def __str__(self):
        return self.title

class UserInfo(models.Model):
    """
    员工表
    """

    name = models.CharField(verbose_name='员工姓名', max_length=16)
    email = models.EmailField(verbose_name='邮箱', max_length=64)
    depart = models.ForeignKey(verbose_name='部门', to="Department",to_field="code")
    user=models.OneToOneField("User",default=1)
    def __str__(self):
        return self.name

class ClassList(models.Model):
    """
    班级表
    如:
        Python全栈  面授班  5期  10000  2017-11-11  2018-5-11
    """
    school = models.ForeignKey(verbose_name='校区', to='School')
    course = models.ForeignKey(verbose_name='课程名称', to='Course')
    semester = models.IntegerField(verbose_name="班级(期)")


    price = models.IntegerField(verbose_name="学费")
    start_date = models.DateField(verbose_name="开班日期")
    graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)
    memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )

    teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo',limit_choices_to={'depart':1002})
    tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo',related_name="class_list",limit_choices_to={'depart':1006})


    def __str__(self):
        return "{0}({1}期)".format(self.course.name, self.semester)


class Customer(models.Model):
    """
    客户表
    """
    qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')

    name = models.CharField(verbose_name='学生姓名', max_length=16)
    gender_choices = ((1, '男'), (2, '女'))
    gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)

    education_choices = (
        (1, '重点大学'),
        (2, '普通本科'),
        (3, '独立院校'),
        (4, '民办本科'),
        (5, '大专'),
        (6, '民办专科'),
        (7, '高中'),
        (8, '其他')
    )
    education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )
    graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)
    major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)

    experience_choices = [
        (1, '在校生'),
        (2, '应届毕业'),
        (3, '半年以内'),
        (4, '半年至一年'),
        (5, '一年至三年'),
        (6, '三年至五年'),
        (7, '五年以上'),
    ]
    experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)
    work_status_choices = [
        (1, '在职'),
        (2, '无业')
    ]
    work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,
                                      null=True)
    company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)
    salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)

    source_choices = [
        (1, "qq群"),
        (2, "内部转介绍"),
        (3, "官方网站"),
        (4, "百度推广"),
        (5, "360推广"),
        (6, "搜狗推广"),
        (7, "腾讯课堂"),
        (8, "广点通"),
        (9, "高校宣讲"),
        (10, "渠道代理"),
        (11, "51cto"),
        (12, "智汇推"),
        (13, "网盟"),
        (14, "DSP"),
        (15, "SEO"),
        (16, "其它"),
    ]
    source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)
    referral_from = models.ForeignKey(
        'self',
        blank=True,
        null=True,
        verbose_name="转介绍自学员",
        help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",
        related_name="internal_referral"
    )
    course = models.ManyToManyField(verbose_name="咨询课程", to="Course")

    status_choices = [
        (1, "已报名"),
        (2, "未报名")
    ]
    status = models.IntegerField(
        verbose_name="状态",
        choices=status_choices,
        default=2,
        help_text=u"选择客户此时的状态"
    )

    consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultanter',limit_choices_to={'depart':1001})

    date = models.DateField(verbose_name="咨询日期", auto_now_add=True)
    recv_date = models.DateField(verbose_name="当前课程顾问的接单日期", null=True)
    last_consult_date = models.DateField(verbose_name="最后跟进日期", )

    def __str__(self):
        return self.name

class ConsultRecord(models.Model):
    """
    客户跟进记录
    """
    customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')
    consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={'depart':1001})
    date = models.DateField(verbose_name="跟进日期", auto_now_add=True)
    note = models.TextField(verbose_name="跟进内容...")

    def __str__(self):
        return self.customer.name + ":" + self.consultant.name

class Student(models.Model):
    """
    学生表(已报名)
    """
    customer = models.OneToOneField(verbose_name='客户信息', to='Customer')
    class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)

    emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')
    company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)
    location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)
    position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)
    salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)
    welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)
    date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)
    memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)

    def __str__(self):
        return self.customer.name

class ClassStudyRecord(models.Model):
    """
    上课记录表 (班级记录)
    """
    class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")
    day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")
    teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo',limit_choices_to={'depart':1002})
    date = models.DateField(verbose_name="上课日期", auto_now_add=True)

    course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)
    course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)
    has_homework = models.BooleanField(default=True, verbose_name="本节有作业")
    homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)
    homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)
    exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)

    def __str__(self):
        return "{0} day{1}".format(self.class_obj, self.day_num)

class StudentStudyRecord(models.Model):
    '''
    学生学习记录
    '''
    classstudyrecord = models.ForeignKey(verbose_name="第几天课程", to="ClassStudyRecord")
    student = models.ForeignKey(verbose_name="学员", to='Student')







    record_choices = (('checked', "已签到"),
                      ('vacate', "请假"),
                      ('late', "迟到"),
                      ('noshow', "缺勤"),
                      ('leave_early', "早退"),
                      )
    record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
    score_choices = ((100, 'A+'),
                     (90, 'A'),
                     (85, 'B+'),
                     (80, 'B'),
                     (70, 'B-'),
                     (60, 'C+'),
                     (50, 'C'),
                     (40, 'C-'),
                     (0, ' D'),
                     (-1, 'N/A'),
                     (-100, 'COPY'),
                     (-1000, 'FAIL'),
                     )
    score = models.IntegerField("本节成绩", choices=score_choices, default=-1)
    homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)
    note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)

    homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)
    stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)
    date = models.DateTimeField(verbose_name='提交作业日期', auto_now_add=True)

    def __str__(self):
        return "{0}-{1}".format(self.classstudyrecord, self.student)

十七. MTV与MVC模型

'''
MTV 全称 Models Templates Views  模型模板视图
MVC 全称 Models Views Controller 模型视图控制

MTV: Django号称是MTV模型
MVC: 其实django本质也是MVC
拓展: vue框架:MVVM模型
'''

参考: https://www.cnblogs.com/Dr-wei/p/11823597.html

十八. 多对多三种创建方式

1. 全自动

'''
优点: 
    1. 第三张表以及对应的外键关联字段不需要书写.
    2. 可以使用ORM提供的多对多关系表之间的增删改查清空方法remove,add,set,clear和正反向+双下划线查询
缺点: 可扩展性极差. 无法对ORM自动生成的第三种虚拟中间表进行增添字段的操作     
'''
class Book(models.Model):
    name = models.CharField(max_length=32)
    book = models.ManyToManyField(to='Author')


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

2. 纯手动

'''
优点: 可扩展性强: 第三张表完全取决于自己 
缺点: 需要书写的代码量大. 并且ORM提供的多对多关系的增删改查清空方法, 以及正反向+双下划线查询都不支持一律都不能使用.
'''
class Book(models.Model):
    name = models.CharField(max_length=32)


class Author(models.Model):
    name = models.CharField(max_length=32)
    
    
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book')        
    author = models.ForeignKey(to='Author') 

3. 半自动

'''
优点: 既可以对第三张表进行格外增添字段的操作, 又能使用ORM提供的正反向+双下划线查询
缺点: 不能使用ORM提供的多对多表关系增删改清空方法. add, set, remove, clear

ManyToManyField中参数指定介绍: 
    to: 需要建立多对多外键关系的表. 本来需要指定to_field, 但是默认关联的就是被关联表的主键, 因此不需要指定.
    through: 建立多对多关系的第三张表
    through_fields: 指定元组形式
        注意: 参数的指定有顺序
        判断顺序指定的本质: 
            通过第三张表反向查对应建立多对多外键关联的表需要用到的哪个字段 哪个字段就放在前面
        简化: 谁是建立多对多外键关联的表, 就把对应的关联字段放前面            
'''
class Book(models.Model):
    name = models.CharField(max_length=32)
    authors = models.ManyToManyField(
        to='Author',
        through='Book2Author',
        through_fields=('book', 'author'))


class Author(models.Model):
    name = models.CharField(max_length=32)
    # books = models.ManyToManyField(
    #     to='Book',
    #     through='Book2Author',
    #     through_fields=('author', 'book'))


class Book2Author(models.Model):
    book = models.ForeignKey(to='Book')
    author = models.ForeignKey(to='Author')

十九. 总结3

# choices参数
    # 作用: 提供了一种定制数据的方式, 通过存的数据编号与自定义数据隐射. 实现取的时候能拿到自定义设计的数据展示方式. 节约了取数据时与数据库的数据查找IO时间. 更加的高效.
    # 范围: 只要是可以预测结果, 那就可以列出对应关系.
        例如: 性别 学历 成绩 在职情况 报名状态 签到情况等等
    # 使用:
        1. 定义choices参数格式. 是一个二元组形式. 如: gender_choices=((参数1, 参数2), ())
            参数1: 需要与指定字段的类型 和 约束条件一致
            参数2: 指定的字符串格式的数据展示格式
        2. 为需要指定的字段设置参数choices=gender_choices
        3. 存: 存的时候可以不采用定义的gender_choices中的隐射关系. 只要满足基本的字段类型存储限制就行.
        4. 取: 通过"对象.get_字段_display()"获取. 如果是没有对应关系的记录, 那么数据怎么存的就是怎么展示.


# MTV与MVC模型
    Django号称MTV. 本质还是MVC


# 多对多三种创建方式
    # 自动
    # 半自动
    # 手动
posted @ 2020-05-31 13:48  给你加马桶唱疏通  阅读(326)  评论(0编辑  收藏  举报