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

原文地址:https://www.cnblogs.com/hgzero/p/13387517.html