Django-ORM模型详解

ORM模型

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

ORM 在业务逻辑层和数据库层之间充当了桥梁的作用,解决的是对象和关系的映射

图片描述

ORM的缺点是会在一定程度上牺牲程序的执行效率,以及不适用于复杂的查询场景。

Django中内置的ORM模型

想要在Djaogo中使用ORM模型,首先要在seethings配置文件中指定后端的数据库引擎,默认是SQLite3,

我们可以修改其为MySQL。

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 指定mysql引擎
'NAME': 'django_db',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123456'
}
}
1
2
3
4
5
# 想要和使得django和Mysql联接,还需要安装MySQL驱动,mysqlclient 和 pymysql 是在 python 开发中最常使用的 MySQL 驱动模块
# pip安装pymsql后,在项目根目录__init__.py中写入

import pymysql
pymysql.install_as_MySQLdb()

在Django中每创建一个应用,其应用下都会有一个models.py文件,我们可以将应用的模型类写入到这个文件里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# models.py
from django.db import models

# 书
class Book(models.Model):
title = models.CharField(max_length=32)
publish_date = models.DateField(auto_now_add=True)
price = models.DecimalField(max_digits=5, decimal_places=2)
memo = models.TextField(null=True)
# 创建外键,关联publish
publisher = models.ForeignKey(to="Publisher")
# 创建多对多关联author
author = models.ManyToManyField(to="Author")

def __str__(self):
return "<Book object: {} {}>".format(self.id, self.title)

class Meta:
# 通过db_table自定义数据表名
db_table = 'book'


# 出版社
class Publisher(models.Model):
name = models.CharField(max_length=32)
city = models.CharField(max_length=32)

def __str__(self):
return "<Publisher object: {} {}>".format(self.id, self.name)


# 作者
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
phone = models.CharField(max_length=11)

def __str__(self):
return "<Author object: {} {}>".format(self.id, self.name)

最后执行迁移命令,将模型类做迁移映射至MySQL数据库中

1
2
python manage.py makemigrations   # 创建迁移脚本文件
python manage.py migrate # 执行迁移映射

在django的源码中可以看到,模型中定义的像CharField,IntegerField都会与MySQL数据库的字段类型一一映射

1614596452361

Django 中常用的 Field types

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

  • BooleanField:布尔类型 (True/False),这个Field不接受null参数,要想使用可以为 null 的布尔类型的字段,就要使用 NullBooleanField;

  • CharField:最常用的字段类,映射到数据库中会转换成 varchar 类型,使用时必须传入 max_length 属性以定义该字符串的最大长度,如果超过254个字符,就不建议使用 CharField 了,此时建议使用 TextField;

  • DateField 和 DateTimeField:都是日期时间的字段类,注意前者只到天,后者可以精确到毫秒。使用这两个 Field 可以传递以下几个参数:

    • auto_now=True:在每次这个数据保存的时候,都使用当前的时间;
    • auto_now_add=True:在每条数据第一次被添加进去的时候,都使用当前的时间;

    此外要注意的是 auto_add_now,auto_now 与 default 是互斥的

  • DecimalField:处理浮点类型的 Field。从上面的源码可以看到,它有两个必须填入的参数:

    • max_digits:数字允许的最大位数;
    • decimal_places:小数的最大位数;
  • FloatField:也是处理浮点类型的 Field。它和 DecimalField 的区别就是 Python 中 float 和 decimal 的区别;

  • IntegerField /BigIntegerField/SmallIntegerField:都是处理整数类型的 Field;

  • TextField:长文本类型 Field,对应 MySQL 中的 longtext 类型。

Django 中 Field 选项

每种 Field 类会有一些特定的 Field 选项,比如 CharField 必须要传入 max_length 属性值。但是下面这些属性对于所有 Field 类都是有效的:

  • null:默认为 False。如果为 True 则表明在数据库中该字段可以为 null;
  • blank:默认为 False。如果为 True 则表明在数据库中该字段可以为不填;
  • choice:设置可选项,表明该字段的值只能从 choice 中选择
  • default:设置字段的默认值;
  • help_text:设置说明信息;
  • primary_key:如果为 True,表明设置该字段为主键。此时 Django 便不会再为我们添加默认的 id 主键了;
  • unique:设置该字段的值在表中唯一。

Django使用ORM进行增查改删

单表操作

1.增加

1
2
3
4
5
# 方式一
book_obj = models.Book(title='老人与海', publish_date='2020-02-02', price=60, publisher_id=1)
book_obj.save()
# 方式二
models.Book.objects.create(title='百年孤独', publish_date='2020-02-02', price=50, publisher_id=1)

2.查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 1、all():查询所有结果,返回的类型为 QuerySet 实例
book_obj = models.Book.objects.all()
print(book_obj)

# 2、filter():根据条件过滤查询结果,返回的类型为 QuerySet 实例,查不到的时候不会报错
print(models.Book.objects.filter(title="老人与海")) #查找书名是老人与海的
print(models.Book.objects.filter(title="老人与海",price=60)) #查找书名是老人与海的并且价格是60的

# 3、get():返回与所给筛选条件相匹配的记录,只返回一个结果。如果符合筛选条件的记录超过一个或者没有都会抛出错误,返回的类型为模型对象实例
print(models.Book.objects.get(title="老人与海")) #拿到的是model对象

# 4、exclude():和 filter() 方法正好相反,筛选出不匹配的结果,返回的类型为 QuerySet 实例
print( models.Book.objects.exclude(title="老人与海")) #查找除了书名是老人与海的书籍

# 5、values():返回一个ValueQuerySet,一个特殊的QuerySet,(把对象转换成字典的形式)
print(models.Book.objects.filter(title="老人与海").values("id","price")) #查找书名是老人与海的id与价格; 结果:<QuerySet [{'id': 5, 'price': Decimal('60.00')}]>

# 6、values_list():与 values() 类似,(把对象转成元组的形式)
print(models.Book.objects.filter(title="老人与海").values_list("id","price"))
#打印结果:<QuerySet [(5, Decimal('60.00'))]>

# 7、order_by():排序,对结果按照传入的字段进行排序,返回的类型为 QuerySet 实例
print(models.Book.objects.all().order_by("price")) #按价格正序排序
print(models.Book.objects.all().order_by("-price")) #按价格倒序排序

# 8、reverse():倒序,对查询结果反向排序,返回的类型为 QuerySet 实例
print(models.Book.objects.all().reverse())

# 9、distinct():去重,去掉查询结果中重复的部分,返回的类型为 QuerySet 实例
print(models.Book.objects.filter(title="老人与海").values("price").distinct())

# 10、count():返回数据库中匹配查询的记录数,返回类型为 int
print(models.Book.objects.filter(title="老人与海").count())

# 11、first():返回第一条记录,结果为模型对象实例
# 12、last():返回最后一条记录,结果为模型对象实例
# 13、exists():如果 QuerySet 包含数据,就返回 True,否则返回 False

如果上述这些方法的返回结果是一个 QuerySet 实例,那么它也同样具有上面这些方法,因此可以继续调用,形成链式调用

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
方法名	                解释
annotate() 使用聚合函数
dates() 根据日期获取查询集
datetimes() 根据时间获取查询集
none() 创建空的查询集
union() 并集
intersection() 交集
difference() 差集
select_related() 附带查询关联对象
prefetch_related() 预先查询
extra() 附加SQL查询
defer() 不加载指定字段
only() 只加载指定的字段
using() 选择数据库
select_for_update() 锁住选择的对象,直到事务结束。
raw() 接收一个原始的SQL查询
bulk_create([]) 批量添加

在 filter() 方法中还有一些比较神奇的双下划线辅助我们进一步过滤结果

1
2
3
4
5
6
7
8
9
10
11
12
13
字段名__gt            # 过滤字段值大于某个值的结果
字段名__gte # 过滤字段值大于等于某个值的结果
字段名__lt # 过滤字段值小于某个值的结果
字段名__lte # 过滤字段值小于等于某个值的结果
字段名__range=[1,3] # 过滤字段值在某个范围内的结果
字段名__in=[1,3] # 和 SQL 语句中的 in 类似,过滤字段的值在某个列表内的结果
字段名__contains='' # 过滤字段的值包含某个字符串的结果(区分大小写)
字段名__icontains='' # 过滤字段的值包含某个字符串的结果(不区分大小写)
字段名__startswith='' # 匹配字段的值以某个字符串开始(区分大小写)
字段名__istartswith='' # 匹配字段的值以某个字符串开始(不区分大小写)
字段名__endswith='' # 匹配字段的值以某个字符串结束(区分大小写)
字段名__iendswith='' # 匹配字段的值以某个字符串结束(不区分大小写)
字段名__isnull=True # 匹配字段值为null的

F查询和Q查询

上面构造的过滤器都只是将字段值与某个常量做比较,如果要对两个字段的值做比较,就需要使用 F查询。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值:

1
2
3
4
5
 from django.db.models import F,Q
# 找出id值大于price/10的记录
print(models.Book.objects.all().filter(id__gt=F('price')/10))
# 将所有书籍的价格降价5元
print(models.Book.objects.all().update(price=F('price')-5))

上面的多个 filter() 方法实现的是过滤条件的 “AND” 操作,如果想实现过滤条件 “OR” 操作,就需要使用Q查询:

1
2
3
4
# 查询年份等于2020年或者价格大于50的书
print(models.Book.objects.filter(Q(publish_date__year=2020)|Q(price__gt=50)))
# 查询年份不是2020年且价格大于5的书
print(models.Book.objects.filter(~Q(publish_date__year=2020)&Q(price__gt=5)))

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将”AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。

3.修改

1
2
3
4
5
6
7
# 方式一
book = models.Book.objects.get(id=1)
book.title = '三体'
book.save()
# 方式二,返回的是受影响条数的整型数值
print(models.Book.objects.all().update(price=F('price')-5))
# 方式二修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法

4.删除

1
2
3
4
models.Book.objects.filter(publish_date__year=2020).delete()
# 或者
book = models.Book.objects.get(id=1)
book.delete()

跨表操作

1.外键

外键 (Foreign Key)是用于建立和加强两个表数据之间的链接的一列或多列,使两张表形成关联,就是当你对一个表的数据进行操作,和他有关联的一个或更多表的数据能够同时发生改变。

在 MySQL 种想使用外键需要具备一定条件

  • MySQL 重需要关联的表必须都使用 InnoDB 引擎创建,MyISAM 表暂时不支持外键;
  • 外键列必须建立了索引,MySQL 4.1.2 以后的版本在建立外键时会自动创建索引,但如果在较早的版本则需要显式建立;
  • 外键关系的两个表的列必须是数据类型相似,也就是可以相互转换类型的列,比如 int 和 tinyint 可以,而 int和char 则不可以。

前面在定义外键时,我们添加了一个 on_delete 属性,这个属性控制着在删除子表外键连接的记录时,对应字表的记录会如何处理,它有如下属性值:

CASCADE:级联操作。如果外键对应的那条记录被删除了,那么子表中所有外键为那个记录的数据都会被删除。

PROTECT:受保护。即只要子表中有记录引用了外键的那条记录,那么就不能删除外键的那条记录。如果我们强行删除,Django 就会报 ProtectedError 异常

SET_NULL:设置为空。如果外键的那条数据被删除了,那么子表中所有外键为该条记录的对应字段值会被设置为 NULL,前提是要指定这个字段可以为空,否则也会报错

SET_DEFAULT:设置默认值。和上面类似,前提是字表的这个字段有默认值

DO_NOTHING:什么也不做,一切全看数据库级别的约束。在 MySQL 中,这种情况下无法执行删除动作

1
2
# 创建外键,关联publish,写在多的一方
publisher = models.ForeignKey(to="Publisher")

2.表关系

  • 一对一的关系:OneToOne(“要绑定关系的表名”)

  • 一对多的关系:ForeignKey(“要绑定关系的表名”)

  • 多对多的关系:ManyToMany(“要绑定关系的表名”) 会自动创建第三张表

一对多添加记录

1
2
3
4
5
6
7
8
9
10
11
12
# 方式一
models.Book.objects.create(title="追风筝的人",publish_date="2015-5-8",price="111",publisher_id=1)
# 方式二:推荐
pub_obj = models.Publisher.objects.filter(name="人民出版社")[0]
print(pub_obj)
models.Book.objects.create(title = "钢铁是怎样炼成的",publish_date="1996-6-6",price="66",publisher=pub_obj)

# 方式三:save
pub_obj= models.Publisher.objects.get(name="人民出版社")
#只有一个的时候用get,拿到的直接就是一个对象
book_obj = models.Book(title = "看见",publish_date="2014-11-24",price="50",publisher=pub_obj)
book_obj.save()

多对多添加记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方式一:
# 先创建一本书:
pub_obj=models.Publisher.objects.filter(name="人民出版社").first()
book_obj = models.Book.objects.create(title="原则",publish_date="2020-02-02",price="80", publisher=pub_obj)
#通过作者的名字,django默认找到id
zhangsan_obj = models.Author.objects.filter(name="zhangsan")[0]
lisi_obj = models.Author.objects.filter(name="lisi")[0]
zhan_obj = models.Author.objects.filter(name="zhan")[0]
# 绑定多对多的关系
book_obj.author.add(zhangsan_obj, lisi_obj, zhan_obj)

# 方式二:
# 查出所有的作者
pub_obj = models.Publisher.objects.filter(name="人民出版社").first()
book_obj = models.Book.objects.create(title="原则", publish_date="2020-02-02",price="80", publisher=pub_obj)
authers = models.Author.objects.all()
#绑定多对多关系
book_obj.author.add(*authers)
# 解除绑定,remove
book_obj.author.remove(*authers)
# 清除绑定:clear
book_obj.author.clear(*authers)

3.跨表查询

一对多查询记录:

正向查询(按字段:publisher):

反向查询(按表名:book_set):

1
2
3
4
5
6
7
8
9
# 正向查询:查询老人与海这本书的出版社的地址
book_obj = models.Book.objects.filter(title="老人与海")[0] # 找对象
print("出版社对象", book_obj.publisher) # 拿到的是关联出版社的对象
print(book_obj.publisher.city)
# 反向查询:查询人民出版社出版过的所有的书的价格和名字
pub_obj = models.Publish.objects.filter(name="人民出版社")[0]
book_dic = pub_obj.book_set.all().values("price", "title")[0]
print(book_dic)
print(book_dic["price"]) # 查询 人民出版社出版过的所有书籍

多对多查询记录:

正向查询(按字段author)

反向查询(按表名book_set)

1
2
3
4
5
6
# 正向查询:查询老人与海这本书的所有的作者的姓名和年龄
book_obj = models.Book.objects.filter(title="老人与海")[0]
print(book_obj.author.all().values("name", "age")) # 这本书关联的所有作者对象的集合
# 反向查询:查询作者是zhangsan的这个人出了哪几本书的信息
zhangsan_obj = models.Author.objects.filter(name="zhangsan")[0]
print("books:", zhangsan_obj.book_set.all()) # 与该作者关联的所有书对象的集合

可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 xxx_set 的名称。

:多对多的查询用.all,查单个的时候用.values或者values_list,不要用obj.Author.name,,这样查到的会是None,反向查询也是如此。不管是一对多,还是多对多,要是查询多得一方就得用all()

基于双下划线的跨表查询

Django 还提供了一种直观而高效的方式在查询中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的 model 为止。相当于用sql语句用join连接的方式

双下划线一对多查询

1
2
3
4
# 方式一
ret = models.Publisher.objects.filter(name="人民出版社").values("book__price","book__title")
# 方式二
ret2 = models.Book.objects.filter(publisher__name="人民出版社").values("price","title")

双下划线多对多查询

1
2
3
4
#方式一    
ret = models.Author.objects.filter(name="zhan").values("book__title")
#方式二
ret2 = models.Book.objects.filter(author__name="zhan").values("title")

聚合查询与分组查询

1614671640132

聚合查询:aggregate(*args, **kwargs),只对一个组进行聚合

它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。

1
2
3
from django.db.models import Avg,Sum,Count,Max,Min
# 查询所有图书的平均价格
print(models.Book.objects.all().aggregate(平均价格=Avg("price")))

分组查询 :annotate(*args, **kwargs):先分组然后再进行某些聚合操作或排序

它返回结果的不仅仅是含有统计结果的一个字典,而是包含有新增统计字段的查询集 (QuerySet)

1
2
3
from django.db.models import Avg,Sum,Count,Max,Min
# 根据一本图书作者数量的多少对查询集QuerySet进行排序,查询一个作者出了几本书
print(models.Book.objects.values("author__name").annotate(authorsNum=Count("author__name")).order_by("authorsNum"))
  • annotate 方法前面的 values 中出现的字段正是需要 GROUP BY 的字段。values 方法中出现多个值,即对多个字段进行 GROUP BY;
  • annotate 方法的结果是一个查询集 (QuerySet),这样我们可以继续在后面调用 filter()、order_by() 等方法进行进一步过滤结果;
  • order_by 方法是对前面的 QuerySet 按某些字段排序,类似于 SQL 中的 ORDER BY 操作。排序字段前面加上 “-” 表示按倒序顺序,类似于 DESC 操作
都看到这里了,不赏点银子吗^v^