• Django之Models


    1. 数据库的配置和使用

    ORM是 “对象-关系-映射” 的简称。(Object Relational Mapping,简称ORM)

    1.1 配置settings文件

    django默认使用sqlite的数据库,并默认自带sqlite的数据库驱动

    如果要更改数据库为MySQL,需要配置如下:

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql', 
            'NAME': 'books',  # 数据库名称,必须事先创建好
            'USER': 'root',   # 数据库用户名
            'PASSWORD': '',   # 数据库密码
            'HOST': '',       # 数据库主机,留空默认为localhost
            'PORT': '3306',   # 数据库端口
        }
    }

    1.2 更改MySQL驱动

    django默认的MySQL驱动为MySQLdb,而MySQLdb在py3中有问题,所以还需要更改MySQL驱动为pymysql

    # 找到项目名文件下的__init__,在里面写入:
    
    import pymysql
    pymysql.install_as_MySQLdb()

    1.3 在models中通过类创建数据库表

    在对应app的models文件中创建数据库表

    • 每个类就对应一张表
    • 类的每个实例就对应于表中的一条记录
    • 每个类中的属性就对应每张表中对应的每个字段
    from django.db import models
    # Create your models here.
    
    class UserInfo(models.Model):               # 这里类就对应于一张表,且必须继承models.Model
        id = models.AutoField(primary_key=True) # 当model中如果没有自增列,则自动会创建一个列名为id的列
        name = models.CharField(max_length=16)
        age = models.IntegerField()
        current_date = models.DateField()
    
        """
        上面的几个类的属性通过ORM映射就对应成了:
        create table userinfo(
            id int primary key auto_increment,
            name varchar(16),
            age int,
            current_date date)
        """

    1.4 在数据库中生成表结构

    将上面的类生成真生的数据库中的表结构 

    python manage.py makemigrations
    # Django 会在相应的 app 的migrations文件夹下面生成 一个python脚本文件 
    
    python manage.py migrate
    # 生成数据库表
    
    # 此时,对应app下面的migrations目录中出现一个0001_initial.py的文件,这个文件就是执行了上述指令之后产生的脚本文件,这个文件就是一个记录

    2. 字段和参数

    2.1 字段

    CharField
        # 字符串字段, 用于较短的字符串
        # CharField 必须有一个参数 maxlength, 限制该字段所允许的最大字符数.
    
    IntegerField
        # 用于保存一个整数
    
    DecimalField
        # 一个浮点数. 必须 提供两个参数:
        #    max_digits 总位数(不包括小数点和符号)
        #    decimal_places 小数位数
        #        要保存最大值为 999 (小数点后保存2位):
        #            models.DecimalField(..., max_digits=5, decimal_places=2)
        #        要保存最大值一百万(小数点后保存10位): 
        #            models.DecimalField(..., max_digits=17, decimal_places=10) 
        #                max_digits大于等于17就能存储百万以上的数了
        # admin 用一个文本框(<input type="text">)表示该字段保存的数据
    
    AutoField
        # 一个 IntegerField, 添加记录时它会自动增长,通常不需要直接使用这个字段
        # 自定义一个主键:my_id=models.AutoField(primary_key=True)
        # 如果不指定主键,系统会自动添加一个主键字段到 model
    
    BooleanField
        # A true/false field
        # admin 用 checkbox 来表示此类字段
    
    TextField
        # 一个容量很大的文本字段
        # admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框)
    
    EmailField
        # 一个带有检查Email合法性的 CharField,不接受 maxlength 参数
    
    DateField
        # 一个日期字段
        # 有下列额外的可选参数:
        #    auto_now    
        #        当对象被保存时(更新或者添加),自动将该字段的值设置为当前时间.
        #        通常用于表示 "last-modified" 时间戳.
        #    auto_now_add    
        #        当对象首次被创建时,自动将该字段的值设置为当前时间.
        #        通常用于表示对象创建时间.
        #    (仅仅在admin中有意义...)
    
    DateTimeField
        # 一个日期时间字段. 类似 DateField 支持同样的附加选项
    
    ImageField
        # 类似 FileField, 不过要校验上传对象是否是一个合法图片
        # 它有两个可选参数:height_field和width_field,如果提供这两个参数,则图片将按提供的高度和宽度规格保存
        
    FileField
        # 一个文件上传字段.
        # 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 
        # 这个路径必须包含 strftime #formatting,
        # 该格式将被上载文件的 date/time替换(so that uploaded files don't fill up the given directory).
        # admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) .
        #
        # 在一个 model 中使用 FileField 或 ImageField 需要以下步骤:
        #    1) 在 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件
        #            出于性能考虑,这些文件并不保存到数据库
        #            定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对WEB服务器用户帐号是可写的.
        #    2) 在 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,
        #            以告诉 Django使用 MEDIA_ROOT 的哪个子目录保存上传文件.
        #            数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT).
        #        如果 ImageField 叫作 mug_shot, 就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径
    
    URLField
        # 用于保存 URL. 
        # 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应).
        # admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框)
    
    NullBooleanField
        # 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 
        # 推荐使用这个字段而不要用 BooleanField 加 null=True 选项
        # admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据
    
    XMLField
        # 一个校验值是否为合法XML的 TextField
        # 必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema 的文件系统路径.
    
    FilePathField
        # 可选项目为某个特定目录下的文件名. 
        # 支持三个特殊的参数, 其中第一个是必须提供的.这三个参数可以同时使用.
        #    path    
        #        必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目.
        #        Example: "/home/images".
        #    match    
        #        可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 
        #        注意这个正则表达式只会应用到 base filename 而不是路径全名. 
        #        Example: "foo.*.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif.
        #    recursive
        #        可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录.
        #
        # match 仅应用于 base filename, 而不是路径全名
        #    FilePathField(path="/home/images", match="foo.*", recursive=True)
        #    会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif
    
    IPAddressField
        # 一个字符串形式的 IP 地址, (i.e. "24.124.1.30").
        
    CommaSeparatedIntegerField
        # 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.

    2.2 参数

    null
        # 如果为True,Django 将用NULL 来在数据库中存储空值,默认值是 False
    
    db_column
        # 数据库中字段的列名
    
    default 
        # 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用
        # 如果字段没有设置可以为空,将来如果后添加一个字段,这个字段就要给一个default值
     
    primary_key
        # 如果为True,那么这个字段就是模型的主键。
        # 如果没有指定任何一个字段的primary_key=True,Django会自动添加一个IntegerField字段做为主键
        #     所以除非想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。
     
    unique 
        # 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
        # 就是建立唯一索引
    
    db_index
      # 如果db_index=True 则代表着为此字段设置数据库索引
    
    
    
    verbose_name
        # Admin中显示字段名称
    
    blank
        # Admin中是否允许用户输入为空
        # 如果为True,该字段允许不填。默认为False。
    
    editable
        # Admin中是否可以编辑
    
    help_text
        # Admin中该字段的提示信息
    
    
    
    choices
        # 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项
        # 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,
        #    而且这个选择框的选项就是choices 中的选项
        # 如:gf = models.IntegerField(choices=[(0, 'shit'),(1, 'fuck'),],default=1)
    
    error_messages
        # 自定义错误信息(字典类型),从而定义想要的验证规则
        # 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date                         
        # 如:{'null': "不能为空.", 'invalid': '格式错误'}
    
    DatetimeFieldDateFieldTimeField 这个三个时间字段,都可以设置如下属性:
        auto_now_add
        #     配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
        # 
        auto_now
        #     配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间
    
    validators          
        # 自定义错误验证(列表类型),从而定制想要的验证规则
           from django.core.validators import RegexValidator
           from django.core.validators import EmailValidator,URLValidator,DecimalValidator,
           MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
             如:
               test = models.CharField(
                   max_length=32,
                   error_messages={
                       'c1': '优先错信息1',
                       'c2': '优先错信息2',
                       'c3': '优先错信息3',
                    },
                   validators=[
                       RegexValidator(regex='root_d+', message='错误了', code='c1'),
                       RegexValidator(regex='root_112233d+', message='又错误了', code='c2'),
                       EmailValidator(message='又错误了', code='c3'), ]
               )

    2.3 元信息

     class UserInfo(models.Model):
            nid = models.AutoField(primary_key=True)
            username = models.CharField(max_length=32)
    class Meta: # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 db_table = "table_name" # 联合索引 index_together = [ ("pub_date", "deadline"), ] # 联合唯一索引 unique_together = (("driver", "restaurant"),) # admin中显示的表名称 verbose_name # verbose_name加s verbose_name_plural

    3. 多表关系及参数

    3.1 外键关联

     ForeignKey(ForeignObject) # ForeignObject(RelatedField)
            to,                         # 要进行关联的表名
            to_field=None,              # 要关联的表中的字段名称
           
            related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
            related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
            limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                        # 如:
                                                - limit_choices_to={'nid__gt': 5}
                                                - limit_choices_to=lambda : {'nid__gt': 5}
    
                                                from django.db.models import Q
                                                - limit_choices_to=Q(nid__gt=10)
                                                - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                                - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
            db_constraint=True          # 是否在数据库中创建外键约束
            parent_link=False           # 在Admin中是否显示关联数据

    关于db_contraint

    不加外键也是可以表示两张表之间的关系的,但是这样就不能使用ORM外键相关的方法了

    所以如果单纯的讲外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的

    db_contraint只加两者的关系,而没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果要求建立外键但是不能有强制的约束关系,那么就可以将这个参数改为False

    customer = models.ForeignKey(verbose_name='关联客户', to='Customer', db_constraint=False)

    on_delete参数

    on_delete=None,         # 当删除关联表中的数据时,当前表与其关联的行的行为(级联删除)
        - models.CASCADE,删除关联数据,与之关联也删除
        - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
        - models.PROTECT,删除关联数据,引发错误ProtectedError
        - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
        - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
        - models.SET,删除关联数据,
              a. 与之关联的值设置为指定值,设置:models.SET(值)
              b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
    
                def func():
                    return 10
    
                class MyModel(models.Model):
                    user = models.ForeignKey(
                        to="User",
                        to_field="id"
                        on_delete=models.SET(func),)

    3.2 一对一关联

    一对一其实就是 一对多 + 唯一索引

    OneToOneField(ForeignKey)
           to,                         # 要进行关联的表名
           to_field=None               # 要关联的表中的字段名称
           on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为

    当两个类之间有继承关系时,默认会创建一个一对一字段

    如下会在A表中额外添加一个c_ptr_id列且唯一:

    class C(models.Model):
        nid = models.AutoField(primary_key=True)
        part = models.CharField(max_length=12)
    
    class A(C):
        id = models.AutoField(primary_key=True)
                                code = models.CharField(max_length=1)

    3.3 多对多关联 

    ManyToManyField(RelatedField)
        to,                         # 要进行关联的表名
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}
    
                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                    # 做如下操作时,不同的symmetrical会有不同的可选字段
                                        models.BB.objects.filter(...)
                                        # 可选字段有:code, id, m1
                                            class BB(models.Model):
    
                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)
                                        # 可选字段有: bb, code, id, m1
                                            class BB(models.Model):
    
                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)
    
        through=None,               # 自定义第三张表时,使用字段用于指定关系表
        through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                        from django.db import models
    
                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)
    
                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )
    
                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在数据库中创建外键约束
        db_table=None,              # 默认创建第三张表时,数据库中表的名称

    4. ORM单表操作

    4.1 增:添加表记录

    1)方式一

    在Views的index函数中操作:

    def index(request):
        # 实例化一个对象就是一条记录
        student_obj = models.Student(
            name='hgzero',
            age=18
        )
        
        student_obj.save()   # 将此记录增加到数据表中
    return render(request,'index.html')

    2)方式二

    通过objects控制器对象来调用数据表相应的增删改查方法。

    它可以创建一个新对象保存到对应的数据表中,并返回这个新创建的对象。

    这个models类的对象就称之为model对象

    new_obj = models.Student.objects.create(
            name='hgzero',
            age=19
        )
    print(new_obj)       # Student object
    print(new_obj.name)  # hgzero

    3)方式三:批量创建

    批量插入很多数据:

    obj_list = [models.Student(name=f'stu{i}', age=20) for i in range(1, 21)]
    models.Student.objects.bulk_create(obj_list)   # 调用bulk_create来批量插入数据

    4.2 查:获取行记录

    1)all() 全部取出

    • 通过 objects 控制器调用,返回QuerySet类型,里面有很多个Student类的对象也就是model对象
    • QuerySet类似于列表,可以循环遍历取值
    all_objs = models.Student.objects.all()
    '''
        < QuerySet[ < Student: Student object >, 
                           < Student: Student object >, 
                           < Student: Student object >,
                            ...'...(remaining elements truncated)...'
                  ]>
    '''

    2)filter(条件) 条件查询

    • 通过object控制器调用,返回QuerySet类型
    • 如果查询不到内容不会报错,返回一个空的QuerySet集合
    objs = models.Student.objects.filter(id=2)
    objs = models.Student.objects.filter(name='xxx', age=18)  # 多条件查询
    print(objs)         # <QuerySet [<Student: hgzero>]>
    print(objs[0].id)   # 可以通过索引取值

    3)get(条件) 条件查询

    • 通过object控制器调用,返回model对象
    • 通过get条件查询,查询的结果有且只有1个
    • object.getlist(id=1)   这样可以取多个结果
    obj = models.Student.objects.get(id=1)  # 返回的是model对象,且查询结果只能有一个

    4)exclude 排除

    • 通过object对象或者QuerySet集合调用,返回QuserySet集合
    # 排除name为hgzero的行记录,将剩下所有的返回
    objs = models.Student.objects.filter(age=20).exclude(name='hgzero')

    5)order_by 排序

    • 通过object对象或者QuerySet集合调用,返回QuserySet集合
    # object对象调用
    objs = models.Student.objects.order_by('age')        # 通过姓名升序排列
    
    # queryset集合调用
    objs = models.Student.objects.all().order_by('age')  # 通过姓名升序排列

    6)reverse 反转

    • 通过order_by返回的QuerySet集合调用,返回一个QuerySet集合
    # 只能通过order_by返回的QuerySet集合调用
    objs = models.Student.objects.order_by('id').reverse()  

    7)count 计数

    • 通过QuerySet集合调用,返回一个元素个数
    num = models.Student.objects.all().count()     # 返回的是记录的条数
    
    num = models.Student.objects.filter(age=20).count()

    8)first 返回第一个model对象

    • 通过QuerySet集合调用,返回第一个model对象
    obj = models.Student.objects.filter(age=20).first()

    9)last 返回最后一个model对象

    • 通过QuerySet集合调用,返回最后一个model对象
    obj = models.Student.objects.filter(age=20).last()

    10)exists 判断是否存在

    • 通过QuerySet集合调用,返回bool值
    flag = models.Student.objects.filter(age=25).exists()

    11)values_list

    • 通过QuerySet集合调用,返回一个QuerySet集合
    • 这个QuerySet里面的元素是元组的形式,而不是model对象
    query_tuple = models.Student.objects.filter(age=20).values_list()
    # <QuerySet [(4, 'stu1', 20), (24, 'stu2', 20), (27, 'stu3', 20)]>
    
    query_tuple = models.Student.objects.filter(age=20).values_list('name','age')
    # 指定想要获取的字段

    12)values

    • 通过QuerySet集合调用,返回一个QuerySet集合
    • 这个QuerySet集合里面是字典的形式,而不是model对象
    query_dict = models.Student.objects.filter(age=19).values()
    # <QuerySet [{'id':3, 'name':'stu1', 'age':19}, {'id':25, 'name':'stu2', 'age':19}]>
    
    query_dict = models.Student.objects.filter(age=19).values('name', 'age')
    # <QuerySet [{'name':'stu1', 'age':19}, {'name':'stu2', 'age':19}]>

    13)distinct 去重

    • 通过QuerySet集合调用,返回一个QuerySet集合
    • 对整个对象去重是没有意义的,因为只要有一个字段不同,都不是重复的
    query_objs = models.Student.objects.filter(age=20).distinct()
    
    query_objs = models.Student.objects.filter(age=20).values('age').distinct()
    # 去重一般都用于values或者values_list

    14)group by 分组

    from django.db.models import Count, Min, Max, Sum
    
    models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))
    
    # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"

    15)双下划线模糊查询

    query_objs = models.Student.objects.filter(age__gt=19)   # 大于
    query_objs = models.Student.objects.filter(age__gte=19)  # 大于等于
    query_objs = models.Student.objects.filter(age__lt=20)   # 小于
    query_objs = models.Student.objects.filter(age__lte=20)  # 小于等于
    
    query_objs = models.Student.objects.filter(age__range=[18, 20])    # 范围 左右都包含
    query_objs = models.Student.objects.filter(name__contains='xiao')  # 针对字符串类型,内容含有
    query_objs = models.Student.objects.filter(name__icontains='xiao') # 针对字符串类型,内容含有 不区分大小写
    query_objs = models.Student.objects.filter(name__startswith='x')   # 匹配以x开头
    query_objs = models.Student.objects.filter(name__istartswith='x')  # 匹配以x开头,不区分大小写
    query_objs = models.Student.objects.filter(name__endswith='o')     # 匹配以x结尾
    query_objs = models.Student.objects.filter(name__iendswith='o')    # 匹配以x结尾,不区分大小写
    models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
    models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in

    16)日期

    创建日期字段:

    class Birthday(models.Model):
        id = models.AutoField(primary_key=True)
        name = models.CharField(max_length=16)
        date = models.DateField()

    插入日期数据:

     models.Birthday.objects.create(name='stu1', date='2020-06-28')
     models.Birthday.objects.create(name='stu2', date='2020-07-02')

    查询2020年6月出生的人:

    query_objs = models.Birthday.objects.filter(date__year='2020',date__month='06')
    print(query_objs)  # <QuerySet [<Birthday: stu1>]>

    5.5  删:删除行记录

    1)调用model对象删除

    models.Student.objects.get(id=20).delete()

    2)调用QuerySet集合删除

    models.Student.objects.filter(age=20).delete()

    5.6 改:更新行记录

    count = models.Student.objects.filter(name='stu1').update(age=20)

    6. ORM多表操作

    6.1 多对多表的创建方式

    1)自行创建第三张表

    • 注意:自行创建第三张表就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了
    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
    
    # 自己创建第三张表,分别通过外键关联书和作者
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")

    2)通过ManyToManyField自动创建

    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    # 通过ORM自带的ManyToManyField自动创建第三张表
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book", related_name="authors")  
    #自动生成的第三张表我们是没有办法添加其他字段的

    3)设置ManyToManyField并指定自行创建的第三张表(中介模型)

    • 当需要在第三种关系表中存储额外的字段时,就要使用第三种方式
    • 第三种方式还是可以使用多对多关联操作的接口(all、add、clear等等)
    class Book(models.Model):
        title = models.CharField(max_length=32, verbose_name="书名")
    
    # 自己创建第三张表,并通过ManyToManyField指定关联
    class Author(models.Model):
        name = models.CharField(max_length=32, verbose_name="作者姓名")
        books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
        # through_fields接受一个2元组('field1','field2'):
        # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名
    
    class Author2Book(models.Model):
        author = models.ForeignKey(to="Author")
        book = models.ForeignKey(to="Book")
        #可以扩展其他的字段了
        class Meta:
            unique_together = ("author", "book")

    6.2 添加表记录(一对多)

    • 注意:对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名

    1)方式一

    # 拿到nid为1的出版社对象
    publish_obj=Publish.objects.get(nid=1) 
    
    # publish_obj作为值传给publish,其实就是自动将publish字段变成publish_id,然后将publish_obj的id给取出来赋值给publish_id字段
    # 注意:如果不是publish类的对象肯定会报错的
    book_obj=Book.objects.create(title="动物农场",publishDate="2020-06-04",price=100,publish=publish_obj)

    2)方式二

    # 直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx
    book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1)  

    6.3 添加表记录(多对多)

    1)方式一

    • 多对多一般在前端页面上使用的时候是多选下拉框的样子,来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者
    # 当前生成的书籍对象
    book_obj=Book.objects.create(title="跟某孩和某哥学Linux",price=200,publishDate="2012-11-12",publish_id=1)
        
    # 为书籍绑定作者对象 mage=Author.objects.filter(name="mage").first() # 在Author表中主键为2的纪录,注意取的是author的model对象 oldboy=Author.objects.filter(name="oldboy").first() # 在Author表中主键为1的纪录   
    # 这个自动生成的第三张表通过models是获取不到的,用不了的
    # 但是如果知道这个表的名字,通过原生sql语句可以进行书的添加,
    # 所以要通过orm间接的给第三张表添加数据,如果是手动添加的第三张表则是可以直接给第三张表添加数据的
    # 绑定多对多关系,即向关系表book_authors中添加纪录,给书添加两个作者,以下语法就是告诉orm给第三张表添加两条数据 book_obj.authors.add(mage,oldboy) # 将某些特定的 model 对象添加到被关联对象集合中 == book_obj.authors.add(*[]) # book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称 # 其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去

    2)方式二

    book_obj.authors.add(1,2)
    book_obj.authors.add(*[1,2]) # 这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值

    6.4 多表的其他操作

    1)多对多关系其他常用API

    book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除 == book_obj.authors.remove(*[1,2]),将多对多的关系数据删除
    book_obj.authors.clear()  # 清空被关联对象集合
    book_obj.authors.set()    # 先清空再设置

    2)删除示例

    book_obj = models.Book.objects.filter(nid=4)[0]
    
    book_obj.authors.remove(2) # 将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除
    book_obj.authors.clear()
    
    # 先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录  
    # 3-->2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以就可以先清空,然后再重新绑定关系数据
    # 注意这里写的是字符串,数字类型不可以
    book_obj.authors.set('2')
    # 这么写也可以,但是列表中的元素是字符串,列表前面没有* book_obj.authors.set(['1',])

    3)更新&删除

    # 更新
    book_obj = models.Book.objects.get(id=1)        # 获取一个书籍对象
    data = {'title':'xxx','price':100}              # 这个书籍对象更新后的数据
    models.Book.objects.filter(id=n).update(**data) # 将新数据更新到原来的记录中
    book_obj.authors.set(author_list)               # 将数据和作者的多对多关系加上
    
    # 删除
    models.Book.objects.filter(id=1).delete()

    7. 基于对象的跨表查询

    7.1 一对多查询

    基于Publish和Book表进行查询

    • 正向查询按字段:book.publish
    • 反向查询表名小写_set.all():pub_obj.book_set.all()

    1)正向查询

    • 按字段:publish
    • 关联属性字段所在的表查询被关联表的记录就是正向查询,反之就是反向查询
    book_obj=Book.objects.filter(pk=1).first()
    # book_obj.publish 是主键为1的书籍对象关联的出版社对象,book对象.外键字段名称
    print(book_obj.publish.city)  

    2)反向查询

    • 按表名:book_set
    • 因为加上_set是因为反向查询的时候,查询出来的可能是多条记录的集合
    publish=Publish.objects.get(name="庆丰出版社")
    # publish.book_set.all() : 与庆丰出版社关联的所有书籍对象集合,写法:小写的表名_set.all(),得到queryset类型数据
    book_list=publish.book_set.all()    
    for book_obj in book_list:
           print(book_obj.title)

    7.2 一对一查询

    基于Author与AuthorDetail

    • 正向查询按字段:mege.authorDetail
    • 反向查询按表名小写:authorDetial.author

    1)正向查询

    • 按字段:authorDetail
    mage=Author.objects.filter(name="mage").first()
    print(mage.authorDetail.telephone)
    # mage.authorDeail就拿到了这个对象,因为一对一找到的就是一条记录
    # 写法:作者对象.字段名,就拿到了那个关联对象

    2)反向查询

    • 按表名:author
    • 不需要_set,因为一对一正向反向都是找到一条记录
    # 查询所有住址在北京的作者的姓名
    authorDet=AuthorDetail.objects.filter(addr="beijing")[0]
    authorDet.author.name

    7.3 多对多查询

    • 正向查询按字段:book.authors.all()
    • 反向查询按表名小写_set.all():mage.book_set.all()

    1)正向查询

    • 按字段:author
    # 动物农场所有作者的名字以及手机号
    book_obj=Book.objects.filter(title="动物农场").first()
    authors=book_obj.authors.all()
    for author_obj in authors:
         print(author_obj.name,author_obj.authorDetail.telephone)

    2)反向查询

    • 按表名:book_set
    author_obj=Author.objects.get(name="mage")
    book_list=author_obj.book_set.all()        #与作者mage相关的所有书籍
    for book_obj in book_list:
        print(book_obj.title)

    3)注意

    • 可以通过在ForeignKey()和ManyToManyField的定义中设置related_name的值来修改xxxx_set的名称
    • 反向查询时,如果定义了related_name ,则用related_name定义的值来替换 表名
        
    publish = ForeignKey(Blog, related_name='bookList')

    8. 基于双下划线的跨表查询

    • 基于双下划线的查询能自动确认JOIN关系,其实就是不断的join
    • 正向查询按字段,反向查询按表名小写用来告诉ORM引擎 join 哪张表

    8.1 一对多查询

    # 查询苹果出版社出版过的所有书籍的名字与价格(一对多) 
    
    # 正向查询 按字段:publish
    queryResult=Book.objects
                        # 通过__告诉orm将book表和publish表进行join,然后找到所有记录中publish.name='苹果出版社'的记录(注意publish是属性名称)
    # 然后select book.title,book.price的字段值
             .filter(publish__name="苹果出版社")          .values_list("title","price") # values或者values_list # 反向查询 按表名:book queryResult=Publish.objects          .filter(name="苹果出版社")          .values_list("book__title","book__price")

    8.2 多对多查询

    # 查询mage出过的所有书籍的名字(多对多)
    
    # 正向查询 按字段:authors:
    queryResult=Book.objects
             .filter(authors__name="mage")
             .values_list("title")
    
    # 反向查询 按表名:book
    queryResult=Author.objects
             .filter(name="mage")
             .values_list("book__title","book__price")

    8.3 一对一查询

    # 查询mage的手机号
        
    # 正向查询
    ret=Author.objects.filter(name="mage").values("authordetail__telephone")
    
    # 反向查询
    ret=AuthorDetail.objects.filter(author__name="mage").values("telephone")

    8.4 连续跨表

    # 查询人民出版社出版过的所有书籍的名字以及作者的姓名
    # 正向查询
    queryResult=Book.objects
             .filter(publish__name="人民出版社")
             .values_list("title","authors__name")
    # 反向查询
    queryResult=Publish.objects
             .filter(name="人民出版社")
             .values_list("book__title","book__authors__age","book__authors__name")
    
    
    # 手机号以151开头的作者出版过的所有书籍名称以及出版社名称
    # 方式1:
    queryResult=Book.objects
             .filter(authors__authorDetail__telephone__regex="151")
             .values_list("title","publish__name")
    # 方式2:    
    ret=Author.objects
                   .filter(authordetail__telephone__startswith="151")
                   .values("book__title","book__publish__name")

    8.5 性能相关

    1)select_related

    def select_related(self, *fields)
         # 性能相关:表之间进行join连表操作,一次性获取关联的数据
         model.tb.objects.all().select_related()
         model.tb.objects.all().select_related('外键字段')
         model.tb.objects.all().select_related('外键字段__外键字段')

    2)prefetch_related

    def prefetch_related(self, *lookups)
        # 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
        # 获取所有用户表
        # 获取用户类型表where id in (用户表中的查到的所有用户ID)
        models.UserInfo.objects.prefetch_related('外键字段')

    9. 聚合&分组

    9.1 聚合

    语法:aggregate(*args, **kwargs)

    aggregate()是QuerySet的一个终止子句,它返回一个包含一些键值对的字典。

    键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的,如果要想为聚合值指定一个名称,可以向聚合子句提供它。

    # 计算所有图书的平均价格
    from django.db.models import Avg
    Book.objects.all().aggregate(Avg('price')) 
    # 或者给它起名字:aggretate(a=Avg('price'))
    # >>> {'price__avg': 34.35}
    
    Book.objects.aggregate(average_price=Avg('price'))
    # >>> {'average_price': 34.35}
    
    # 查询所有图书价格的最大值和最小值
    from django.db.models import Avg, Max, Min
    Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))  
    #count('id'),count(1)也可以统计个数,Book.objects.all().aggregete和Book.objects.aggregate(),都可以 # >>> {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

    9.2 分组

    • annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)

    1)单表分组查询

    # 查询每一个部门名称以及对应的员工数
    
    emp:
    id  name age   salary    dep
    1   alex  12   2000     销售部
    2   egon  22   3000     人事部
    3   wen   22   5000     人事部
    
    # sql语句的写法:
    select dep,Count(*) from emp group by dep;
    
    # ORM的写法:
    emp.objects.values("dep").annotate(c=Count("id") 
    # 注意:annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有
    # 用哪个字段分组,values里面就写哪个字段, annotate其实就是对分组结果的统计,统计你需要什么
    '''   select dep,count('id') as c from emp grouby dep; # 原生sql语句中的as c,不是必须有的 '''

    2)多表分组查询

    # 查询每一个部门名称以及对应的员工数
    
    emp:
    id  name age   salary   dep_id
    1   alex  12   2000       1
    2   egon  22   3000       2
    3   wen   22   5000       2
    
    dep:
    id   name 
    1    销售部
    2    人事部
    
    emp-dep:
    id  name age   salary   dep_id   id   name 
    1   alex  12   2000       1      1    销售部
    2   egon  22   3000       2      2    人事部
    3   wen   22   5000       2      2    人事部
    
    # sql语句的写法:
    select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id
    
    # ORM的写法:
    dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")
    ret = models.Emp.objects.values('dep_id','name').annotate(a=Count(1)) 
    
    '''
      SELECT `app01_emp`.`dep_id`, `app01_emp`.`name`, COUNT(1) AS `a` FROM `app01_emp` GROUP BY `app01_emp`.`dep_id`, `app01_emp`.`name`
    '''
    # <QuerySet [{'dep_id': 1, 'name': 'alex', 'a': 1}, {'dep_id': 2, 'name': 'egon', 'a': 1}, {'dep_id': 2, 'name': 'wen', 'a': 1}]>,
    # 注意,这里如果写了其他字段,那么只有这两个字段重复,才算一组,合并到一起来统计个数

    总结:跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询,既然是join连接,就可以使用双下划线进行连表了。

    #单表:
        # 查询每一个部门的id以及对应员工的平均薪水
        ret = models.Emp.objects.values('dep_id').annotate(s=Avg('salary'))
        # 查询每个部门的id以及对对应的员工的最大年龄
        ret = models.Emp.objects.values('dep_id').annotate(a=Max('age'))
        # Emp表示表,values中的字段表示按照哪个字段group by,annotate里面是显示分组统计的是什么
    
    #连表:
        # 查询每个部门的名称以及对应的员工个数和员工最大年龄
        ret = models.Emp.objects.values('dep__name').annotate(a=Count('id'),b=Max('age')) 
    # 注意,正向与反向的结果可能不同,如果反向查的时候,有的部门还没有员工,那么他的数据也会被统计出来,只不过值为0,
    # 但是正向查的话只能统计出来有员工的部门的相关数据,因为通过你是员工找部门,而不是通过部门找员工,结果集里面的数据个数不同,但是你想要的统计结果是一样的
    #<QuerySet [{'a': 1, 'dep__name': '销售部', 'b': 12}, {'a': 3, 'dep__name': '人事部', 'b': 22}]> #使用双下划线进行连表,然后按照部门名称进行分组,然后统计员工个数和最大年龄,最后结果里面显示的是部门名称、个数、最大年龄
    # 注意:如果values里面有多个字段的情况:ret = models.Emp.objects.values('dep__name','age').annotate(a=Count('id'),b=Max('age'))
    # 是按照values里面的两个字段进行分组,两个字段同时相同才算是一组,看下面的sql语句
    ''' SELECT `app01_dep`.`name`, `app01_emp`.`age`, COUNT(`app01_emp`.`id`) AS `a`, MAX(`app01_emp`.`age`) AS `b`
    FROM `app01_emp` INNER JOIN `app01_dep` ON (`app01_emp`.`dep_id` = `app01_dep`.`id`)
    GROUP BY `app01_dep`.`name`, `app01_emp`.`age`;
    '''

    10. F&Q查询

    10.1 F查询

    如果要对两个字段的值做比较,就需要用到F查询。F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。

    # 查询评论数大于收藏数的书籍
    from django.db.models import F
    Book.objects.filter(commentNum__lt=F('keepNum'))
    
    # Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
    # 查询评论数大于收藏数2倍的书籍
    Book.objects.filter(commentNum__lt=F('keepNum')*2)
    
    # 修改操作也可以使用F函数,比如将每一本书的价格提高30元
    Book.objects.all().update(price=F("price")+30) 

    10.2 Q查询

    filter() 等方法中的关键字参数查询都是一起“AND”的,如果需要自行更复杂的查询(例如OR语句),就可以使用Q对象。

    from django.db.models import Q
    Q(title__startswith='Py')

    11. ORM执行原生SQL语句

    11.1 执行原生查询

    11.2 直接执行自定义SQL

  • 相关阅读:
    1120. Maximum Average Subtree 子树平均值的最大值
    490. The Maze 迷宫踢足球
    323. Number of Connected Components in an Undirected Graph 连通图的数量
    done infosys_不告你答案的面试
    MyBatis(三)全局配置文件 之 typeHandlers 类型处理器
    MyBatis(三)全局配置文件 之 typeAliases 类型命名
    MyBatis(三)全局配置文件 之 properties 属性
    MyBatis(三)全局配置文件 之 settings 设置
    MyBatis(三)全局配置文件
    MyBatis(二)HelloWorld 案例
  • 原文地址:https://www.cnblogs.com/hgzero/p/13387517.html
Copyright © 2020-2023  润新知