Django QuerySet 方法梳理 。model外键 多对多的保存(转)

引用:https://feifeiyum.github.io/2017/03/28/python-django-queryset/

说明

Models 层是 Django 框架中最强大的部分之一, 大大方便了 Web 层与数据层的交互。由于对 Model 层缺少系统理解,在使用 model Api 时经常需要查找文档, 在此做一次系统地整理。
本文主要是对 Django Model 文档的翻译, 文档地址

说明

Models 层是 Django 框架中最强大的部分之一, 大大方便了 Web 层与数据层的交互。由于对 Model 层缺少系统理解,在使用 model Api 时经常需要查找文档, 在此做一次系统地整理。
本文主要是对 Django Model 文档的翻译, 文档地址

Model 层对象

一个model类对应数据库中的一张表, 类中的属性代表数据库中的各个字段。 类实例化后的对象, 代表数据库中的一条记录。
本文将基于下面的 models 对象展开, 由 Blog, Author, Entry 三个 models 组成。 Blog, Author 是两个独立的 model(表), 没有任何外键字段。 Entry 和 Blog 是多对一的关系, 通过外键关联; Entry 和 Author 是多对多关系。
假设 Blog 类所在目录为 mysite/blog/models.py, Author 类所在目录为 mysite/author/models.py, Entry 类所在目录为 mysite/entry/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db import models
 
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
 
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
 
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255) //外键
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author) // 多对多
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()

创建 model 对象

Model 对象实例化之后,可以调用 save() 方法其写入到数据库。

1
2
3
4
5
6
from blog.models import Blog
 
# 实例化对象
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') 
# 将对象写入数据库
b.save()

 

当执行 save() 函数时, ORM 会执行 INSERT SQL 语句, 将数据写入数据库。在调用 save() 函数之前, 实例不会执行 INSERT 操作。

保存对象更新

假设: 上例中的 b 实例已经存入数据库, 现在要更新 b 中的 tagline 字段。先更新 tagline 对应的值,然后执行 save() 操作。如下:

1
2
3
4
#更新 tagline 字段
b.tagline = 'All the latest Beatles new 03/28'
# 将更新写入数据库
b.save()

 

当执行 save() 操作时, ORM 会执行 UPDATE SQL 语句。

保存外键字段

保存外键(ForeignKey)字段同正常的字段类似, 也是通过 save() 函数来实现。
假设, 数据库中已经存在对应的 Blog 和 Entry 对象, 现在要将这个两个对象关联起来, 可以进行如下操作:

1
2
3
4
5
6
7
8
9
10
from entry.models import Entry
 
# 获取 id 为 1 的 entry 对象, 执行 SELECT 操作
entry = Entry.objects.get(pk=1)
# 获取 name 为 'Cheddar Talk' 的 blog 对象 
cheese_blog = Blog.objects.get(name='Cheddar Talk')
# 更新 entry 的 blog 属性 
entry.blog = cheese_blog
# 写入更新
entry.save()

 

保存多对多字段

更新一个多对多字段, 同外键字段不同, 其使用一个专门的 add() 函数添加对应的关联, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from author.models import Author
 
# 创建 author 对象
# 采用 create() 方法创建对象,会将 create 和 save () 同时进行,
# 不需要单独调用 save() 方法
joe = Author.objects.create(name='Joe')
# entry 为上例中实例的对象 
entry.authors.add(joe)
 
# add() 方法可以添加对个参数
john = Author.objects.create(name='john')
paul = Author.objects.create(name='paul')
ringo = Author.objects.create(name='ringo')
entry.authors.add(john, paul, ringo)

 

对象检索

对数据库中对象的检索, 是通过 model Manage 来构造一个 QuerySet 对象来实现。每个 model 类都有一个 Manage方法, Model 类通过 objects 来调用 Manage 方法。 model 对象中没有 objects 属性。QuerySet 对象是一个model 类对应的实例集合, 即数据库对应表的子集。
QuerySet 可以是 空(zero), 单个对象(one), 多个对象(many).
QuerySet 通过 Filters 方法来实现查询结果的过滤。 对于 SQL 来说, QuerySet 等同于 SELECT 声明, Filter 等同于 LIMIT, WHERE 声明。

检索所有对象

查找 model 类对应表中的所有对象 (数据), 是通过 all() 方法来实现, 其返回一个 QuerySet 对象。

1
2
# Entry model, 
all_entries = Entry.objects.all()

 

通过 filters 检索特定对象

通常查询数据库是,只是检索对应表中的一条或几条数据, 其主要通过一下两种方法来实现:

1、filter(**kwargs)

通过 filter 中的条件(kwargs) 进数据库查询特定的数据, 返回一个 QuerySet 对象。
2、exclude(**kwargs)
通过 exclude 中的条件, 排除特定的数据, 返回表中的剩余数据, 返回结果为 QuerySet 对象。

例: 查询姓名为 paul 的作者:

1
2
# 返回的 QuerySet 对象中只包含 name='paul' 的 model 对象
Author.objects.filter(name='paul')

 

例: 查询姓名不为 paul 的所有作者:

1
2
# 返回的 QuerySet 对象中,不包含 name='pual' 的 model 对象
Author.objects.exclude(name='paul')

 

3、filter 级联

filter, exclude 等方法不仅仅可以单独使用, 也可以级联进行使用

例: 查找 entry 中 headline 由 What 开头, 不是有今天发布的, 发布日期大于 2017/03/10 的数据, 共三个条件。

1
2
3
4
5
6
7
8
9
10
Entry.objects.filter(
#条件1, 由 What 开头
headline__startswith='What'
).exclude(
#条件2, 不由今天(2017-03-28)发布
pub_data__gte=datetime.date.today() 
).filter(
# 条件3, 发布日期大于 2017/03/10
pub_data__gte=datetime(2017, 3, 10)
)

 

上面所列的三个条件最终会整合成一条 SQL 语句去执行:
SELECT * FROM entry(表名) WHERE headline LIKE ‘What%’ AND NOT pub_date = ‘2017-3-28’ AND pub_date > ‘2017-3-10’

4、每个 filter 返回的对象都是不相关的

每次查询生成的 QuerySet 对象都是相互独立的, 可以保存或重复使用

1
2
3
4
q1 = Entry.objects.filter(headline__startwith='What')
# QuerySet 对象中有 filter, exclude 方法
q2 = q1.exclude(pub_data__gte=datetime.date.today())
q3 = q1.filter(pub_data__gte=datetime.date.today())

 

上面的三个 QuerySet 对象 q1, q2, q3 中, 是相互独立的,拥有各自独立的内存空间。

5、QuerySet 懒加载 (lazy)

QuerySet are lazy - 暂且套用前端的懒加载名词称之为懒加载。其意思为,在 QuerySet 对象创建的时候,是不会进行数据库查询操作的。只有在使用这个对象的时候才会进行数据库查询操作。

1
2
3
4
q = Entry.objects.filter(headline__startwith='What')
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains='food')
print(q)

 

上例中, 虽然在 print(q) 有三次 QuerySet 对象的 filter 操作。但是,他们都不会实际进行数据库查询操作, 知道在使用这个 QuerySet 对象的时候。即,在 print(q) 的时候执行真正的数据库查询操作。

检索单个 model 对象

上面介绍的 filter() 等方法返回的结果是 QuerySet 对象。如果明确知道有且只有一个对象可以查询到时,可以使用 get() 方法进行查询, 其返回结果为一个 model 对象。

1
one_entry = Entry.objects.get(pk=1)

 

返回的 one_entry 为 model 对象, 而不是 QuerySet 对象。
采用这种方法查询是,如果查询结果为空,将抛出 DoesNotExist 异常。在一般情况下,不建议使用可以使用如下方式代替:

1
2
3
one_entry = Entry.objects.filter(pk=1)
if one_entry.exists():
one_entry = one_entry[0]

 

QuerySet 的其他方法

当进行数据库查询时,常用的也就是 all(), get(), filter() 和 exclude() 等方法。但是这些方法,无法完成一些复杂的查询方法。下面将介绍一些 QuerySet 的复杂的查询方法。

Limiting 查询

利用 Python Array 中的分片, 可以限制返回查询结果的数量。 其对应的 SQL 语法为 LIMIT 和 OFFSET。
返回结果为 QuerySet 对象

例如:
限制返回查询结果集中的前5组数据 (LIMIT 5)

1
Entry.objects.all()[:5]

 

返回查询结果集中5~9这5组数据 ( OFFSET 5 LIMIT 5)

1
Entry.objects.all()[5:10]

 

在查询结果集中前10组数据中取5组数据, 取数据的 step 等于 2

1
Entry.objects.all()[:10:2]

 

返回查询结果集中的第一个数据, 即 SELECT * FROM table1 LIMIT 1

1
2
3
4
5
6
7
# 返回长度为 1 的 QuerySet 对象
Entry.objects.all()[0:1]
# 注意其返回的不是 QuerySet 对象, 而是 Entry 的 model 对象
# 如果查询结果不存在, 将会抛出 DoesNotExist 异常
Entry.objects.all()[0]
# Entry.objects.all()[0] 等同于如下:
Entry.objects.all()[0:1].get()

 

按字段查询

按字段查询, 在 SQL 中对应的 WHERE 条件。这些查询条件是以参数形式出入 QuerySet 方法 (filter, exclude, get) 中, 这个在前面例子中有涉及。
基本查询条件配置方式: fieldlookuptype=value, field 为字段名称, lookuptype 为查询类型, value为类型值。 field 和 lookuptype 通过 双下划线连接。
例如: 查询在今天之前发表的 Entry

1
2
3
Entry.objects.filter(pub_date__lte=datetime.date.today())
# 其对应如下 SQL 语句, 假设今天为 2017-03-30
# SELECT * FROM entry WHERE pub_date <= '2017-03-30';

 

如果根据外键的查询时, 可以根据对应的字段名称加上 _id 后缀进行查询, 其实在 model migration 之后, 数据库中外键所在字段名称就是 model 中的属性名加上后缀 _id。
例如查询 id 为 4 的 blog 对象所关联的 Entry:

1
Entry.objects.filter(blog_id=4)

 

如果传入的查询条件不对时, 其将会抛出 TypeError 异常。

常见查询类型

1、exact 精确匹配
根据对应字段值查询,大小写敏感

1
2
Entry.objects.filter(headline__excat='cat bites dog')
# SQL: SELECT * from entry WHERE headline = 'cat bites dog'

 

如果不添加 __ 对应的查询类型, 则默认是 exact 匹配。

1
2
3
# 以下两条查找语句等价
Entry.objects.filter(id__excat=13)
Entry.objects.filter(id=13)

 

2、 iexact 匹配
根据对应字段值查询,但大小写不敏感

1
2
3
4
Entry.objects.filter(headline__iexact='cat Bites Dog')
# 估计类型下列查询语句
# SQL: SELECT * FROM entry WHERE lower(headline) = lower('cat Bites Dog')
# 或者SQL: SELECT * FROM entry WHERE UPPER(headline) = UPPER('cat Bites Dog')

 

即: 表中 headline 字段值为 Cat Bites dog 或 Cat BITES dog 等都会命中返回

3、contains 查询
查询对应字段值包含某些字符的数据, 大小写敏感

1
2
Entry.objects.filter(headline__contains='Lennon')
# SQL: SELECT * FROM entry WHERE headline LIKE '%Lennon%'

 

4、icontains 查询
同 3、 contains 查询, 但大小写不敏感

1
Entry.objects.filter(headline__icontains='Lennon')

 

5、startswith 查询
查询对应字段值中, 以特定某些字符开头的数据, 大小写敏感

1
2
Entry.objects.filter(headline__startswith='cat')
# SQL: SELECT * FROM entry WHERE headline LIKE 'cat%'

 

6、istartswith 查询
同 5、startswith 查询, 但大小写不敏感

1
Entry.objects.filter(headline__istartswith='Cat')

 

7、endswith 查询
查询对应字段值中, 以特定某些字符结尾的数据, 大小写敏感

1
2
Entry.objects.filter(headline__startswith='dog')
# SQL: SELECT * FROM entry WHERE headline LIKE '%dog'

 

8、iendswith 查询
同 7、endswith 查询, 但大小写不敏感

1
Entry.objects.filter(headline__istartswith='Cat')

 

跨表查询

Django 提供了强大的跨表查询方式(lookup span relationship), 完成 SQL 中的 JOINs 查询。
使用与外表关联的字段名(field name) 和 对应表中对应的字段名通过双下划线联接起来,即可完成两张表的 JOIN 查询
例如: 查询名为 beatles blog 的 blog 对应的 entry

1
2
3
4
5
# blog 为 Entry mode 外键字段, name 为 Blog model 中的字段, __ 联接
# 分解成两步理解: 1、根据 blog 表中 name='beatles blog' 得到对应的数据,
# 再 1、中获得的结果,到 entry 表中查询出最终的结果
Entry.objects.filter(blog__name='beatles blog')
# SQL 估计为: SELECT a.id, a.blog, a.headline ... FROM entry a INNER JOIN blog b ON a.blog = b.id WHERE b.name = 'beatles blog'

 

上例中是根据 blog 查询对应的 entry, 从 entry 反推 blog 也是可以实现的,
例如: 查询 headline 中包含 Lennon 的 entry 对应的 blog
查询方式, 使用小写的 Model 名(即: entry) 关联对应的字段名(headline) 再添加相应的查询类型和其值: 如下:

1
Blog.objects.filter(entry__headline__contains='Lennon')

 

更进一步, 跨三张表的查询, 也很容易实现:
例如: 根据 auther 名查询对应的 blog, 这个过程中关系到三张表, entry, author, blog。简单分析:根据 author 表中的查询结果, 找出 entry 表中的符合条件的数据, 再根据从 entry 表中获得结果, 获取 blog 表中对应的数据。
查询方式, 小写的 Model 名(entry) 关联对应的字段名 authors(不是表 author) 关联对应 author 表中的字段名(name) 再添加相应的查询类型和其值:

1
2
# 
Blog.objects.filter(entry__authors__name='Lennon')

 

在跨表查询时, 在执行 filter 操作是,可以添加多个过滤参数,以达到更复杂的查询逻辑
例如:

1
2
3
4
# 查询 entry 中 headline 包含 Lennon 的 Blog 和 发表日期在 2013 年的 blog, 两个查询条件结果取并集
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2013)
# 查询 entry 中 headline 包含 Lennon 的 Blog 并且 发表日期是 2013 年的, 两个查询条件取交集
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2013)

 

根据同一表(model)内字段查询

Django 提供了 F 表达式(expressions) 来实现, 同一个表内不同的两个字段的对比。F() 表现一次查询实例的引用。
例: 查询 entry 表中 n_comments 值大于 n_pingbacks 的数据:

1
2
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

 

F() 支持逻辑运算: 加,减,乘,除, 取模, 幂等。

1
2
Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
Entry.objects.filter(rating__gt=F('n_pingbacks') + F('n_comments'))

 

F() 也支持双下划线的表关联操作,

1
2
3
4
5
#查询: entry 表中 author name 等于 blog name 的数据
Entry.objects.filter(authors__name=F('blog__name'))
#查询, entry 表中发表3天后还有修改的数据
from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(day=3))

 

F() 可以通过方法 .bitand() 和 .bitor() 实现按位与运算和或运算。

pk 缩写

在 Django 中采用 pk 缩写来表示 ‘primary key’, 表中的 id (primary key) 字段,可以使用 pk 来表示
例如: 下面的查询语句等价

1
2
3
Blog.objects.get(id__exact=13)
Blog.objects.get(id=13)
Blog.objects.get(pk=13)

 

pk 除 exact 查询类型外,也可以使用其他的查询类型

1
2
3
4
# 查找 id 为 1,2,3 的 blog 数据
Blog.objects.filter(pk__in=[1,2,3])
# 查找 id 大于 13 的数据
Blog.objects.filter(pk_gt=13)

 

pk 可以使用在关联表的查询中

1
2
3
#查找 entry 表中 blog id 为 13 的数据
Entry.objects.filter(blog__id=13)
Entry.objects.filter(blog__pk=13)

 

QuerySet 缓存

由于 QuerySet 懒加载的原因,在一个新生成的 QuerySet 对象中是没有缓存的。 当其第一次使用的时候才会去请求数据库拉取数据。 此时 Django 会将查询到的结果缓存起来,当第二次使用该 QuerySet 对象时,就不需要重新请求数据库。
当然要获得缓存带来的便利,要保持正确的使用姿势才行, 例如:

1
2
print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])

 

上面两句代码中, QuerySet 对象将会查询两次数据库。因为,每使用一次 Entry.objects.all() 都会生成一个新的 QuerySet 对象。 其本质上, 第一 print 语句执行时使用的 是一个 QuerySet 对象, 第二个 print 语句执行的是另一个 QuerySet 对象, 虽然说前后两个 QuerySet 对象是拥有相同的值, 但他们不是共享一个内存空间,是相互独立的。
正确的使用姿势如下:

1
2
3
4
5
queryset = Entry.objects.all()
# 请求数据库
print([e.headline for e in queryset])
# 使用对象 queryset 对象的缓存
print([e.pub_date for e in queryset])

 

无法使用缓存的情况
QuerySet 并不是所有情况都会设置缓存的, 当查询 QuerySet 对象中部分值时, 会先去检查下缓存, 如果缓存中没有, 将会请求数据拉取数据, 但是这部分数据将不会被缓存。因此,在第一次使用 QuerySet 对象时, 如果是通过 python slice 或 数组下标(index) 去限制所取请求结果的子集时, 这部分结果将不会加入缓存。
其原因大致是使用了 slice 或 index 时数据查询时的 SQL 语句会额外添加 LIMIT, OFFSET 条件, 而 queryset 不做部分数据的缓存。
例如:

1
2
3
4
5
6
queryset = Entry.objects.all()
# 请求数据库拉取第5条数据, 
# 通过数组下标(index)取数据, 其结果不缓存
print(queryset[5])
# 因为没有换成, 再一次拉取第5条数据时, 会重新请求数据库
print(queryset[5])

 

假如在使用 slice 或 index 取数据之前,已经对 QuerySet 对象求值(不是部分求值), 即可使用 Queryset 对象的缓存

1
2
3
4
5
6
7
queryset = Entry.objects.all()
# 因为使用了整个 queryset 对象
[entry for entry in Entry.objects.all()]
# 从缓存中取数据
print(queryset[5])
# 从缓存中取数据
print(queryset[5])

 

使用 Q 对象进行复杂查找

在 QuerySet 对象的 filter() 方法中, 要用 AND 或者 OR 组合查询条件, 来执行更复杂的查询时, 可以使用 Q 对象。
Q 对象使用 &, | 和 ~ 来分别表示 SQL 中的 AND, OR 和 NOT
例如: 查询 entry 表中 headline 以 ‘what’ 或 ‘who’ 开头的数据

1
2
3
from django.db.models import Q
entry = Entry.objects.filter(Q(headline__startswith='what') | Q(headline__startswith='who')) 
# SQL: SELECT * FROM entry WHERE headline LIKE 'what%' OR headline LIKE 'who%'

 

如果在 filer() 方法中多个 Q 对象作为参数时, 则不同参数之间将会以 AND 形式组合, 例如:

1
2
Entry.objects.filter(Q(headline__startswith='what'), Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))
# SQL SELECT * FROM entry WHERE headline LIKE 'what%' AND (pub_date='2017-3-29' OR pub_date='2017-3-31')

 

Q 对象和普通的查询条件可以混合使用, 但是在 filter 中普通的查询条件,要放在 Q 对象之后传入, 否则将会是无效查询

1
2
3
4
# 有效查询
Entry.objects.filter(Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)), headline__startswith='what')
# 无效查询, 因为普通查询条件在 Q 对象之前
Entry.objects.filter(headline__startswith='what', Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))

 

对象删除

1、通过 model 对象删除
一个 model 对象拥有 delete() 方法, 当要删除某一条数据时, 可以直接执行 delete 方法

1
2
entry = Entry.objects.get(pk=1)
entry.delete()

 

2、通过 QuerySet 对象删除
QuerySet 对象也有 delete() 方法, 其将会删除所有包含的成员。
例如: 删除 entry 表中所有在 2016 年发布的数据

1
2
3
Entry.objects.filter(pub_date__year=2016).delete()
#删除表中所有数据
Entry.bojects.all().delete()

 

当 Django 删除一个数据时, 其默认或使用 SQL 的 CASCADE 删除模式, 即: 在父表上 delete 删除一条数据是,与之通过 ForeignKey 关联的子表中的数据也对应删除。

复制数据

在 Django 中复制一条数据只需要将对应的 model 对象的 pk(id) 清除即可

1
2
3
4
5
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk = 1
# 复制一条
blog.pk = None
blog.save() # blog.pk = 2

 

但是这种拷贝不会 many-to-many 的对应关系,因此在复制数据后,需要更新其多对多关系

1
2
3
4
5
entry = Entry.objects.all()[0]
auther_old = entry.authors.all() #提取 authors
entry.pk =None
entry.save()
entry.authors.set(auther_old) # 更新对应关系

 

对于 one-to-one 对应关系的数据, 在拷贝后要先更新一个新的对应关系后,开可以执行 save() 操作。

通过 QuerySet 执行更新操作

QuerySet 对象使用 update() 方法更新数据, 与 model 对象使用 save 不同。
update() 方法调用后会立即执行数据库 Update 操作
1、更新不是外键的字段
例: entry 表中在 2017 年发布数据的 headline 字段都更新为 ‘Everything is the same’

1
Entry.objects.filter(pub_date__year=2017).update(headline='Everything is the same')

 

2、更新外建字段
例: 更新 entry 表中 blog 字段关联的外键

1
2
b = Blog.objects.get(pk=1)
Entry.objects.all().update(blog=b)

 

如果想要更新某一行数据, 可以使用 filter 来进行过滤

1
2
b = Blog.objects.get(pk=1)
Entry.objects.filter(blog=b).update(headline='Everything is the same')

 

注意: 执行 update() 方法时, 会执行生成相应的 SQL 语句。不会去执行 model 对象里面的 save() 方法, 也不会出发 pre_save 和 post_save 这两个钩子函数。添加了 auto_now 属性的字段也无法执行。如果需要执行这些方法的操作, 可以遍历 QuerySet 对象中的每一个 model 实例去执行 save() 方法。

1
2
for item in my_queryset:
item.save()

 

3、使用 F 表达式执行更新操作
在根据字段现有值去执行更新操作的场景中, F 表达比较实用
例如: 对 entry 中 n_pingbacks 字段执行递增操作

1
2
from django.db.models import F
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

 

但是通过 F 表达式无法执行, 有 join 操作的更新, 即通过双下划线 __ 关联的操作
例如: 将 entry 表中所有数据的 headline 字段更新为其对应 blog 表中的 name, 无法通过 F 表达式实现

1
2
# 下面语句执行后会抛出 FieldError 异常
Entry.objects.update(headline=F('blog__name'))

 

对象的对应关系

当在 model 定义是添加了 ForeignKey, OneToOneFiedl, ManyToMangField 的字段时, model 会自动生成相关 API 来获取相关数据。

One-To-Many 关系

1、正向获取 父表 to 子表
如果一个 model 包含有 ForeignKey 字段, 这个 model 的对象可以方便的获取与之关联的另一个 model 的对象。
例如: 获取 Entry 对象对应的 Blog 对象

1
2
3
4
5
6
e = Entry.objects.get(id=2)
e.blog # 返回通过外键关联的 blog 对象
#如果要更新 e 对象的 blog 属性
b = Blog.objects.get(id=3)
e.blog = b
e.save() # 执行根系操作,

 

one-to-many 关系在第一次使用后将会被缓存

1
2
3
e = Entry.objects.get(id=2)
print(e.blog) # 查询数据, 并将数据缓存
print(e.blog) # 不查询数据库, 之间中缓存中读取

 

使用 QuerySet 的 select_related() 方法时, 会将相应的 one-to-many 关系的对象都预先取出来并缓存, 在真正使用时就不会访问数据库

1
2
3
e = Entry.objects.select_related().get(id=2)
print(e.blog) # 不查询数据库
print(e.bong) # 不查询数据库

 

2、反向获取 子表 to 父表
如果 model A 通过 ForeignKey字段 field 与 model B 想关联。 B 对象可以通过 model Manager 去访问与之对应的所有的 A 对象。 默认的, 这个 model Manage 名为 foo_set, 其中 foo 是拥有外键那个 model 名的小写, 即 a_set()
例: 通过 Blog 对象查询 Entry 对象:

1
2
3
4
5
6
# 查询与 Blog 对象 b 关联的所有 entry 对象
b = Blog.objects.get(pk=2)
b.entry_set.all()
 
# 查询与 Blog 对象 b 关联的 entry 对象中 headline 包含 'Lennon' 的
b.entry_set.filter(headline__contains='Lennon')

 

如果在定义 ForeignKey 字段时 通过 related_name 可以更改这个默认的 foo_set() Manage 方法。
例如: 将最顶部的 Entry Model 中的 blog 字段修改成如下: blog = ForeignKey(Blog, related_name=’entries’), 上面的代码中的 entry_set 就可以都改成 entries。

1
2
3
4
5
6
# 查询与 Blog 对象 b 关联的所有 entry 对象
b = Blog.objects.get(pk=2)
b.entries.all()
 
# 查询与 Blog 对象 b 关联的 entry 对象中 headline 包含 'Lennon' 的
b.entries.filter(headline__contains='Lennon')

 

Many-To-Many 关系

对于 many-to-many 关系的 API 使用方法与上面的 one-to-many 关系的一致。区别在于, 在命名 ManyToMany 字段时, 字段名不要与其对应的 model 名的小写一致。例如 Entry 中, authors 字段名称不与其对应 Model(Author) 的小写名 author 相同。 这点与 blog 字段不同。

1
2
3
4
5
6
7
8
e = Entry.objects.get(id=3)
e.authors.all() # 返回 e 对象对应的所有 authors
e.authors.count() # authors 的数量
e.authors.filter(name__contains='John') # 返回名字中包含 John 的作者
 
a = Author.objects.get(id=5)
# 返回所有与 a 对象对应的 Entry 对象
a.entry_set.all()

 

One-To-One 关系

One-to-one 关系同 many-to-one 非常相似, API 用法与 many-to-one 的基本也基本一致

1
2
3
4
5
6
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
 
ed = EntryDetail.objects.get(pk=3)
en.entry # 返回与之对应的 Entry 对象

 

与 many-to-one 不同的是其反向查找, 如下:

1
2
3
4
5
6
7
8
e = Entry.objects.get(pk=3)
# 取得与 Entry 对象对应的 EntryDetail 对象,
# 只需调用 EntryDetail 的小写 entrydetail 即可
e.entrydetail
 
#更新
e.entrydetail = ed2
e.save()

 

通过关联对象查询

当查询过滤条件传入 filter 函数是,既可以使用整个对象,可以使用相应的对象值。

1
2
3
4
# Blog 对象 b 的 id = 5
Entry.objects.filter(blog=b) # 通过整个对象进行查询
Entry.objects.filter(blog=b.id) # 通过 b 的 id 属性查询
Entry.objects.filter(blog=5) # 直接用 id 值查询 hard code

 

做个标记: 下期整理 QuerySet 比较常用的 API

Model 层对象

一个model类对应数据库中的一张表, 类中的属性代表数据库中的各个字段。 类实例化后的对象, 代表数据库中的一条记录。
本文将基于下面的 models 对象展开, 由 Blog, Author, Entry 三个 models 组成。 Blog, Author 是两个独立的 model(表), 没有任何外键字段。 Entry 和 Blog 是多对一的关系, 通过外键关联; Entry 和 Author 是多对多关系。
假设 Blog 类所在目录为 mysite/blog/models.py, Author 类所在目录为 mysite/author/models.py, Entry 类所在目录为 mysite/entry/models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.db import models
 
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
 
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
 
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255) //外键
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author) // 多对多
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()

创建 model 对象

Model 对象实例化之后,可以调用 save() 方法其写入到数据库。

1
2
3
4
5
6
from blog.models import Blog
 
# 实例化对象
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') 
# 将对象写入数据库
b.save()

 

当执行 save() 函数时, ORM 会执行 INSERT SQL 语句, 将数据写入数据库。在调用 save() 函数之前, 实例不会执行 INSERT 操作。

保存对象更新

假设: 上例中的 b 实例已经存入数据库, 现在要更新 b 中的 tagline 字段。先更新 tagline 对应的值,然后执行 save() 操作。如下:

1
2
3
4
#更新 tagline 字段
b.tagline = 'All the latest Beatles new 03/28'
# 将更新写入数据库
b.save()

 

当执行 save() 操作时, ORM 会执行 UPDATE SQL 语句。

保存外键字段

保存外键(ForeignKey)字段同正常的字段类似, 也是通过 save() 函数来实现。
假设, 数据库中已经存在对应的 Blog 和 Entry 对象, 现在要将这个两个对象关联起来, 可以进行如下操作:

1
2
3
4
5
6
7
8
9
10
from entry.models import Entry
 
# 获取 id 为 1 的 entry 对象, 执行 SELECT 操作
entry = Entry.objects.get(pk=1)
# 获取 name 为 'Cheddar Talk' 的 blog 对象 
cheese_blog = Blog.objects.get(name='Cheddar Talk')
# 更新 entry 的 blog 属性 
entry.blog = cheese_blog
# 写入更新
entry.save()

 

保存多对多字段

更新一个多对多字段, 同外键字段不同, 其使用一个专门的 add() 函数添加对应的关联, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from author.models import Author
 
# 创建 author 对象
# 采用 create() 方法创建对象,会将 create 和 save () 同时进行,
# 不需要单独调用 save() 方法
joe = Author.objects.create(name='Joe')
# entry 为上例中实例的对象 
entry.authors.add(joe)
 
# add() 方法可以添加对个参数
john = Author.objects.create(name='john')
paul = Author.objects.create(name='paul')
ringo = Author.objects.create(name='ringo')
entry.authors.add(john, paul, ringo)

 

对象检索

对数据库中对象的检索, 是通过 model Manage 来构造一个 QuerySet 对象来实现。每个 model 类都有一个 Manage方法, Model 类通过 objects 来调用 Manage 方法。 model 对象中没有 objects 属性。QuerySet 对象是一个model 类对应的实例集合, 即数据库对应表的子集。
QuerySet 可以是 空(zero), 单个对象(one), 多个对象(many).
QuerySet 通过 Filters 方法来实现查询结果的过滤。 对于 SQL 来说, QuerySet 等同于 SELECT 声明, Filter 等同于 LIMIT, WHERE 声明。

检索所有对象

查找 model 类对应表中的所有对象 (数据), 是通过 all() 方法来实现, 其返回一个 QuerySet 对象。

1
2
# Entry model, 
all_entries = Entry.objects.all()

 

通过 filters 检索特定对象

通常查询数据库是,只是检索对应表中的一条或几条数据, 其主要通过一下两种方法来实现:

1、filter(**kwargs)

通过 filter 中的条件(kwargs) 进数据库查询特定的数据, 返回一个 QuerySet 对象。
2、exclude(**kwargs)
通过 exclude 中的条件, 排除特定的数据, 返回表中的剩余数据, 返回结果为 QuerySet 对象。

例: 查询姓名为 paul 的作者:

1
2
# 返回的 QuerySet 对象中只包含 name='paul' 的 model 对象
Author.objects.filter(name='paul')

 

例: 查询姓名不为 paul 的所有作者:

1
2
# 返回的 QuerySet 对象中,不包含 name='pual' 的 model 对象
Author.objects.exclude(name='paul')

 

3、filter 级联

filter, exclude 等方法不仅仅可以单独使用, 也可以级联进行使用

例: 查找 entry 中 headline 由 What 开头, 不是有今天发布的, 发布日期大于 2017/03/10 的数据, 共三个条件。

1
2
3
4
5
6
7
8
9
10
Entry.objects.filter(
#条件1, 由 What 开头
headline__startswith='What'
).exclude(
#条件2, 不由今天(2017-03-28)发布
pub_data__gte=datetime.date.today() 
).filter(
# 条件3, 发布日期大于 2017/03/10
pub_data__gte=datetime(2017, 3, 10)
)

 

上面所列的三个条件最终会整合成一条 SQL 语句去执行:
SELECT * FROM entry(表名) WHERE headline LIKE ‘What%’ AND NOT pub_date = ‘2017-3-28’ AND pub_date > ‘2017-3-10’

4、每个 filter 返回的对象都是不相关的

每次查询生成的 QuerySet 对象都是相互独立的, 可以保存或重复使用

1
2
3
4
q1 = Entry.objects.filter(headline__startwith='What')
# QuerySet 对象中有 filter, exclude 方法
q2 = q1.exclude(pub_data__gte=datetime.date.today())
q3 = q1.filter(pub_data__gte=datetime.date.today())

 

上面的三个 QuerySet 对象 q1, q2, q3 中, 是相互独立的,拥有各自独立的内存空间。

5、QuerySet 懒加载 (lazy)

QuerySet are lazy - 暂且套用前端的懒加载名词称之为懒加载。其意思为,在 QuerySet 对象创建的时候,是不会进行数据库查询操作的。只有在使用这个对象的时候才会进行数据库查询操作。

1
2
3
4
q = Entry.objects.filter(headline__startwith='What')
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains='food')
print(q)

 

上例中, 虽然在 print(q) 有三次 QuerySet 对象的 filter 操作。但是,他们都不会实际进行数据库查询操作, 知道在使用这个 QuerySet 对象的时候。即,在 print(q) 的时候执行真正的数据库查询操作。

检索单个 model 对象

上面介绍的 filter() 等方法返回的结果是 QuerySet 对象。如果明确知道有且只有一个对象可以查询到时,可以使用 get() 方法进行查询, 其返回结果为一个 model 对象。

1
one_entry = Entry.objects.get(pk=1)

 

返回的 one_entry 为 model 对象, 而不是 QuerySet 对象。
采用这种方法查询是,如果查询结果为空,将抛出 DoesNotExist 异常。在一般情况下,不建议使用可以使用如下方式代替:

1
2
3
one_entry = Entry.objects.filter(pk=1)
if one_entry.exists():
one_entry = one_entry[0]

 

QuerySet 的其他方法

当进行数据库查询时,常用的也就是 all(), get(), filter() 和 exclude() 等方法。但是这些方法,无法完成一些复杂的查询方法。下面将介绍一些 QuerySet 的复杂的查询方法。

Limiting 查询

利用 Python Array 中的分片, 可以限制返回查询结果的数量。 其对应的 SQL 语法为 LIMIT 和 OFFSET。
返回结果为 QuerySet 对象

例如:
限制返回查询结果集中的前5组数据 (LIMIT 5)

1
Entry.objects.all()[:5]

 

返回查询结果集中5~9这5组数据 ( OFFSET 5 LIMIT 5)

1
Entry.objects.all()[5:10]

 

在查询结果集中前10组数据中取5组数据, 取数据的 step 等于 2

1
Entry.objects.all()[:10:2]

 

返回查询结果集中的第一个数据, 即 SELECT * FROM table1 LIMIT 1

1
2
3
4
5
6
7
# 返回长度为 1 的 QuerySet 对象
Entry.objects.all()[0:1]
# 注意其返回的不是 QuerySet 对象, 而是 Entry 的 model 对象
# 如果查询结果不存在, 将会抛出 DoesNotExist 异常
Entry.objects.all()[0]
# Entry.objects.all()[0] 等同于如下:
Entry.objects.all()[0:1].get()

 

按字段查询

按字段查询, 在 SQL 中对应的 WHERE 条件。这些查询条件是以参数形式出入 QuerySet 方法 (filter, exclude, get) 中, 这个在前面例子中有涉及。
基本查询条件配置方式: fieldlookuptype=value, field 为字段名称, lookuptype 为查询类型, value为类型值。 field 和 lookuptype 通过 双下划线连接。
例如: 查询在今天之前发表的 Entry

1
2
3
Entry.objects.filter(pub_date__lte=datetime.date.today())
# 其对应如下 SQL 语句, 假设今天为 2017-03-30
# SELECT * FROM entry WHERE pub_date <= '2017-03-30';

 

如果根据外键的查询时, 可以根据对应的字段名称加上 _id 后缀进行查询, 其实在 model migration 之后, 数据库中外键所在字段名称就是 model 中的属性名加上后缀 _id。
例如查询 id 为 4 的 blog 对象所关联的 Entry:

1
Entry.objects.filter(blog_id=4)

 

如果传入的查询条件不对时, 其将会抛出 TypeError 异常。

常见查询类型

1、exact 精确匹配
根据对应字段值查询,大小写敏感

1
2
Entry.objects.filter(headline__excat='cat bites dog')
# SQL: SELECT * from entry WHERE headline = 'cat bites dog'

 

如果不添加 __ 对应的查询类型, 则默认是 exact 匹配。

1
2
3
# 以下两条查找语句等价
Entry.objects.filter(id__excat=13)
Entry.objects.filter(id=13)

 

2、 iexact 匹配
根据对应字段值查询,但大小写不敏感

1
2
3
4
Entry.objects.filter(headline__iexact='cat Bites Dog')
# 估计类型下列查询语句
# SQL: SELECT * FROM entry WHERE lower(headline) = lower('cat Bites Dog')
# 或者SQL: SELECT * FROM entry WHERE UPPER(headline) = UPPER('cat Bites Dog')

 

即: 表中 headline 字段值为 Cat Bites dog 或 Cat BITES dog 等都会命中返回

3、contains 查询
查询对应字段值包含某些字符的数据, 大小写敏感

1
2
Entry.objects.filter(headline__contains='Lennon')
# SQL: SELECT * FROM entry WHERE headline LIKE '%Lennon%'

 

4、icontains 查询
同 3、 contains 查询, 但大小写不敏感

1
Entry.objects.filter(headline__icontains='Lennon')

 

5、startswith 查询
查询对应字段值中, 以特定某些字符开头的数据, 大小写敏感

1
2
Entry.objects.filter(headline__startswith='cat')
# SQL: SELECT * FROM entry WHERE headline LIKE 'cat%'

 

6、istartswith 查询
同 5、startswith 查询, 但大小写不敏感

1
Entry.objects.filter(headline__istartswith='Cat')

 

7、endswith 查询
查询对应字段值中, 以特定某些字符结尾的数据, 大小写敏感

1
2
Entry.objects.filter(headline__startswith='dog')
# SQL: SELECT * FROM entry WHERE headline LIKE '%dog'

 

8、iendswith 查询
同 7、endswith 查询, 但大小写不敏感

1
Entry.objects.filter(headline__istartswith='Cat')

 

跨表查询

Django 提供了强大的跨表查询方式(lookup span relationship), 完成 SQL 中的 JOINs 查询。
使用与外表关联的字段名(field name) 和 对应表中对应的字段名通过双下划线联接起来,即可完成两张表的 JOIN 查询
例如: 查询名为 beatles blog 的 blog 对应的 entry

1
2
3
4
5
# blog 为 Entry mode 外键字段, name 为 Blog model 中的字段, __ 联接
# 分解成两步理解: 1、根据 blog 表中 name='beatles blog' 得到对应的数据,
# 再 1、中获得的结果,到 entry 表中查询出最终的结果
Entry.objects.filter(blog__name='beatles blog')
# SQL 估计为: SELECT a.id, a.blog, a.headline ... FROM entry a INNER JOIN blog b ON a.blog = b.id WHERE b.name = 'beatles blog'

 

上例中是根据 blog 查询对应的 entry, 从 entry 反推 blog 也是可以实现的,
例如: 查询 headline 中包含 Lennon 的 entry 对应的 blog
查询方式, 使用小写的 Model 名(即: entry) 关联对应的字段名(headline) 再添加相应的查询类型和其值: 如下:

1
Blog.objects.filter(entry__headline__contains='Lennon')

 

更进一步, 跨三张表的查询, 也很容易实现:
例如: 根据 auther 名查询对应的 blog, 这个过程中关系到三张表, entry, author, blog。简单分析:根据 author 表中的查询结果, 找出 entry 表中的符合条件的数据, 再根据从 entry 表中获得结果, 获取 blog 表中对应的数据。
查询方式, 小写的 Model 名(entry) 关联对应的字段名 authors(不是表 author) 关联对应 author 表中的字段名(name) 再添加相应的查询类型和其值:

1
2
# 
Blog.objects.filter(entry__authors__name='Lennon')

 

在跨表查询时, 在执行 filter 操作是,可以添加多个过滤参数,以达到更复杂的查询逻辑
例如:

1
2
3
4
# 查询 entry 中 headline 包含 Lennon 的 Blog 和 发表日期在 2013 年的 blog, 两个查询条件结果取并集
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2013)
# 查询 entry 中 headline 包含 Lennon 的 Blog 并且 发表日期是 2013 年的, 两个查询条件取交集
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2013)

 

根据同一表(model)内字段查询

Django 提供了 F 表达式(expressions) 来实现, 同一个表内不同的两个字段的对比。F() 表现一次查询实例的引用。
例: 查询 entry 表中 n_comments 值大于 n_pingbacks 的数据:

1
2
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

 

F() 支持逻辑运算: 加,减,乘,除, 取模, 幂等。

1
2
Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
Entry.objects.filter(rating__gt=F('n_pingbacks') + F('n_comments'))

 

F() 也支持双下划线的表关联操作,

1
2
3
4
5
#查询: entry 表中 author name 等于 blog name 的数据
Entry.objects.filter(authors__name=F('blog__name'))
#查询, entry 表中发表3天后还有修改的数据
from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(day=3))

 

F() 可以通过方法 .bitand() 和 .bitor() 实现按位与运算和或运算。

pk 缩写

在 Django 中采用 pk 缩写来表示 ‘primary key’, 表中的 id (primary key) 字段,可以使用 pk 来表示
例如: 下面的查询语句等价

1
2
3
Blog.objects.get(id__exact=13)
Blog.objects.get(id=13)
Blog.objects.get(pk=13)

 

pk 除 exact 查询类型外,也可以使用其他的查询类型

1
2
3
4
# 查找 id 为 1,2,3 的 blog 数据
Blog.objects.filter(pk__in=[1,2,3])
# 查找 id 大于 13 的数据
Blog.objects.filter(pk_gt=13)

 

pk 可以使用在关联表的查询中

1
2
3
#查找 entry 表中 blog id 为 13 的数据
Entry.objects.filter(blog__id=13)
Entry.objects.filter(blog__pk=13)

 

QuerySet 缓存

由于 QuerySet 懒加载的原因,在一个新生成的 QuerySet 对象中是没有缓存的。 当其第一次使用的时候才会去请求数据库拉取数据。 此时 Django 会将查询到的结果缓存起来,当第二次使用该 QuerySet 对象时,就不需要重新请求数据库。
当然要获得缓存带来的便利,要保持正确的使用姿势才行, 例如:

1
2
print([e.headline for e in Entry.objects.all()])
print([e.pub_date for e in Entry.objects.all()])

 

上面两句代码中, QuerySet 对象将会查询两次数据库。因为,每使用一次 Entry.objects.all() 都会生成一个新的 QuerySet 对象。 其本质上, 第一 print 语句执行时使用的 是一个 QuerySet 对象, 第二个 print 语句执行的是另一个 QuerySet 对象, 虽然说前后两个 QuerySet 对象是拥有相同的值, 但他们不是共享一个内存空间,是相互独立的。
正确的使用姿势如下:

1
2
3
4
5
queryset = Entry.objects.all()
# 请求数据库
print([e.headline for e in queryset])
# 使用对象 queryset 对象的缓存
print([e.pub_date for e in queryset])

 

无法使用缓存的情况
QuerySet 并不是所有情况都会设置缓存的, 当查询 QuerySet 对象中部分值时, 会先去检查下缓存, 如果缓存中没有, 将会请求数据拉取数据, 但是这部分数据将不会被缓存。因此,在第一次使用 QuerySet 对象时, 如果是通过 python slice 或 数组下标(index) 去限制所取请求结果的子集时, 这部分结果将不会加入缓存。
其原因大致是使用了 slice 或 index 时数据查询时的 SQL 语句会额外添加 LIMIT, OFFSET 条件, 而 queryset 不做部分数据的缓存。
例如:

1
2
3
4
5
6
queryset = Entry.objects.all()
# 请求数据库拉取第5条数据, 
# 通过数组下标(index)取数据, 其结果不缓存
print(queryset[5])
# 因为没有换成, 再一次拉取第5条数据时, 会重新请求数据库
print(queryset[5])

 

假如在使用 slice 或 index 取数据之前,已经对 QuerySet 对象求值(不是部分求值), 即可使用 Queryset 对象的缓存

1
2
3
4
5
6
7
queryset = Entry.objects.all()
# 因为使用了整个 queryset 对象
[entry for entry in Entry.objects.all()]
# 从缓存中取数据
print(queryset[5])
# 从缓存中取数据
print(queryset[5])

 

使用 Q 对象进行复杂查找

在 QuerySet 对象的 filter() 方法中, 要用 AND 或者 OR 组合查询条件, 来执行更复杂的查询时, 可以使用 Q 对象。
Q 对象使用 &, | 和 ~ 来分别表示 SQL 中的 AND, OR 和 NOT
例如: 查询 entry 表中 headline 以 ‘what’ 或 ‘who’ 开头的数据

1
2
3
from django.db.models import Q
entry = Entry.objects.filter(Q(headline__startswith='what') | Q(headline__startswith='who')) 
# SQL: SELECT * FROM entry WHERE headline LIKE 'what%' OR headline LIKE 'who%'

 

如果在 filer() 方法中多个 Q 对象作为参数时, 则不同参数之间将会以 AND 形式组合, 例如:

1
2
Entry.objects.filter(Q(headline__startswith='what'), Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))
# SQL SELECT * FROM entry WHERE headline LIKE 'what%' AND (pub_date='2017-3-29' OR pub_date='2017-3-31')

 

Q 对象和普通的查询条件可以混合使用, 但是在 filter 中普通的查询条件,要放在 Q 对象之后传入, 否则将会是无效查询

1
2
3
4
# 有效查询
Entry.objects.filter(Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)), headline__startswith='what')
# 无效查询, 因为普通查询条件在 Q 对象之前
Entry.objects.filter(headline__startswith='what', Q(pub_date=date(2017, 3, 29)) | Q(pub_date=date(2017, 3, 31)))

 

对象删除

1、通过 model 对象删除
一个 model 对象拥有 delete() 方法, 当要删除某一条数据时, 可以直接执行 delete 方法

1
2
entry = Entry.objects.get(pk=1)
entry.delete()

 

2、通过 QuerySet 对象删除
QuerySet 对象也有 delete() 方法, 其将会删除所有包含的成员。
例如: 删除 entry 表中所有在 2016 年发布的数据

1
2
3
Entry.objects.filter(pub_date__year=2016).delete()
#删除表中所有数据
Entry.bojects.all().delete()

 

当 Django 删除一个数据时, 其默认或使用 SQL 的 CASCADE 删除模式, 即: 在父表上 delete 删除一条数据是,与之通过 ForeignKey 关联的子表中的数据也对应删除。

复制数据

在 Django 中复制一条数据只需要将对应的 model 对象的 pk(id) 清除即可

1
2
3
4
5
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk = 1
# 复制一条
blog.pk = None
blog.save() # blog.pk = 2

 

但是这种拷贝不会 many-to-many 的对应关系,因此在复制数据后,需要更新其多对多关系

1
2
3
4
5
entry = Entry.objects.all()[0]
auther_old = entry.authors.all() #提取 authors
entry.pk =None
entry.save()
entry.authors.set(auther_old) # 更新对应关系

 

对于 one-to-one 对应关系的数据, 在拷贝后要先更新一个新的对应关系后,开可以执行 save() 操作。

通过 QuerySet 执行更新操作

QuerySet 对象使用 update() 方法更新数据, 与 model 对象使用 save 不同。
update() 方法调用后会立即执行数据库 Update 操作
1、更新不是外键的字段
例: entry 表中在 2017 年发布数据的 headline 字段都更新为 ‘Everything is the same’

1
Entry.objects.filter(pub_date__year=2017).update(headline='Everything is the same')

 

2、更新外建字段
例: 更新 entry 表中 blog 字段关联的外键

1
2
b = Blog.objects.get(pk=1)
Entry.objects.all().update(blog=b)

 

如果想要更新某一行数据, 可以使用 filter 来进行过滤

1
2
b = Blog.objects.get(pk=1)
Entry.objects.filter(blog=b).update(headline='Everything is the same')

 

注意: 执行 update() 方法时, 会执行生成相应的 SQL 语句。不会去执行 model 对象里面的 save() 方法, 也不会出发 pre_save 和 post_save 这两个钩子函数。添加了 auto_now 属性的字段也无法执行。如果需要执行这些方法的操作, 可以遍历 QuerySet 对象中的每一个 model 实例去执行 save() 方法。

1
2
for item in my_queryset:
item.save()

 

3、使用 F 表达式执行更新操作
在根据字段现有值去执行更新操作的场景中, F 表达比较实用
例如: 对 entry 中 n_pingbacks 字段执行递增操作

1
2
from django.db.models import F
Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

 

但是通过 F 表达式无法执行, 有 join 操作的更新, 即通过双下划线 __ 关联的操作
例如: 将 entry 表中所有数据的 headline 字段更新为其对应 blog 表中的 name, 无法通过 F 表达式实现

1
2
# 下面语句执行后会抛出 FieldError 异常
Entry.objects.update(headline=F('blog__name'))

 

对象的对应关系

当在 model 定义是添加了 ForeignKey, OneToOneFiedl, ManyToMangField 的字段时, model 会自动生成相关 API 来获取相关数据。

One-To-Many 关系

1、正向获取 父表 to 子表
如果一个 model 包含有 ForeignKey 字段, 这个 model 的对象可以方便的获取与之关联的另一个 model 的对象。
例如: 获取 Entry 对象对应的 Blog 对象

1
2
3
4
5
6
e = Entry.objects.get(id=2)
e.blog # 返回通过外键关联的 blog 对象
#如果要更新 e 对象的 blog 属性
b = Blog.objects.get(id=3)
e.blog = b
e.save() # 执行根系操作,

 

one-to-many 关系在第一次使用后将会被缓存

1
2
3
e = Entry.objects.get(id=2)
print(e.blog) # 查询数据, 并将数据缓存
print(e.blog) # 不查询数据库, 之间中缓存中读取

 

使用 QuerySet 的 select_related() 方法时, 会将相应的 one-to-many 关系的对象都预先取出来并缓存, 在真正使用时就不会访问数据库

1
2
3
e = Entry.objects.select_related().get(id=2)
print(e.blog) # 不查询数据库
print(e.bong) # 不查询数据库

 

2、反向获取 子表 to 父表
如果 model A 通过 ForeignKey字段 field 与 model B 想关联。 B 对象可以通过 model Manager 去访问与之对应的所有的 A 对象。 默认的, 这个 model Manage 名为 foo_set, 其中 foo 是拥有外键那个 model 名的小写, 即 a_set()
例: 通过 Blog 对象查询 Entry 对象:

1
2
3
4
5
6
# 查询与 Blog 对象 b 关联的所有 entry 对象
b = Blog.objects.get(pk=2)
b.entry_set.all()
 
# 查询与 Blog 对象 b 关联的 entry 对象中 headline 包含 'Lennon' 的
b.entry_set.filter(headline__contains='Lennon')

 

如果在定义 ForeignKey 字段时 通过 related_name 可以更改这个默认的 foo_set() Manage 方法。
例如: 将最顶部的 Entry Model 中的 blog 字段修改成如下: blog = ForeignKey(Blog, related_name=’entries’), 上面的代码中的 entry_set 就可以都改成 entries。

1
2
3
4
5
6
# 查询与 Blog 对象 b 关联的所有 entry 对象
b = Blog.objects.get(pk=2)
b.entries.all()
 
# 查询与 Blog 对象 b 关联的 entry 对象中 headline 包含 'Lennon' 的
b.entries.filter(headline__contains='Lennon')

 

Many-To-Many 关系

对于 many-to-many 关系的 API 使用方法与上面的 one-to-many 关系的一致。区别在于, 在命名 ManyToMany 字段时, 字段名不要与其对应的 model 名的小写一致。例如 Entry 中, authors 字段名称不与其对应 Model(Author) 的小写名 author 相同。 这点与 blog 字段不同。

1
2
3
4
5
6
7
8
e = Entry.objects.get(id=3)
e.authors.all() # 返回 e 对象对应的所有 authors
e.authors.count() # authors 的数量
e.authors.filter(name__contains='John') # 返回名字中包含 John 的作者
 
a = Author.objects.get(id=5)
# 返回所有与 a 对象对应的 Entry 对象
a.entry_set.all()

 

One-To-One 关系

One-to-one 关系同 many-to-one 非常相似, API 用法与 many-to-one 的基本也基本一致

1
2
3
4
5
6
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
 
ed = EntryDetail.objects.get(pk=3)
en.entry # 返回与之对应的 Entry 对象

 

与 many-to-one 不同的是其反向查找, 如下:

1
2
3
4
5
6
7
8
e = Entry.objects.get(pk=3)
# 取得与 Entry 对象对应的 EntryDetail 对象,
# 只需调用 EntryDetail 的小写 entrydetail 即可
e.entrydetail
 
#更新
e.entrydetail = ed2
e.save()

 

通过关联对象查询

当查询过滤条件传入 filter 函数是,既可以使用整个对象,可以使用相应的对象值。

1
2
3
4
# Blog 对象 b 的 id = 5
Entry.objects.filter(blog=b) # 通过整个对象进行查询
Entry.objects.filter(blog=b.id) # 通过 b 的 id 属性查询
Entry.objects.filter(blog=5) # 直接用 id 值查询 hard code

 

做个标记: 下期整理 QuerySet 比较常用的 API

posted @ 2018-05-25 19:30  yoyo008  阅读(1103)  评论(0编辑  收藏  举报