一旦创建 数据模型 后,Django 自动给予你一套数据库抽象 API,允许你创建,检索,更新和删除对象。本页介绍如何使用这些 API。参考 数据模型参考 获取所有查询选项的完整细节。
在本指南中(以及相关参考资料中),我们将引用以下模型,这些模型包含了一个 Webblog 应用:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
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()
def __str__(self):
return self.headline
为了用 Python 对象展示数据表对象,Django 使用了一套直观的系统:一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录。
要创建一个对象,用关键字参数初始化它,然后调用 save()
将其存入数据库。
假设模型都位于文件 mysite/blog/models.py
中,这是一个例子:
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
这在幕后执行了 INSERT
SQL 语句。Django 在你显式调用 save()
才操作数据库。
save()
方法没有返回值。
要将修改保存至数据库中已有的某个对象,使用 save()
。
有一个已被存入数据库中的 Blog
实例 b5
,本例将其改名,并在数据库中更新其记录:
>>> b5.name = 'New name'
>>> b5.save()
这在幕后执行了 UPDATE
SQL 语句。Django 在你显示调用 save()
后才操作数据库。
ForeignKey
和 ManyToManyField
字段P更新 ForeignKey
字段的方式与保存普通字段的方式相同——只需将正确类型的实例分配给相关字段。本例为 Entry
类的实例 entry
更新了 blog
属性,假设 Entry
和 Blog
的实例均已保存在数据库中(因此能在下面检索它们):
>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
更新 ManyToManyField
字段有点不同——在字段上使用 add()
方法为关联关系添加一条记录。本例将 Author
实例 joe
添加至 entry
对象:
>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
要一次添加多行记录至 ManyToManyField
字段,在一次调用 add()
时传入多个参数,像这样:
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
Django 会在添加或指定错误类型的对象时报错。
要从数据库检索对象,要通过模型类的 Manager
构建一个 QuerySet
。
一个 QuerySet
代表来自数据库中对象的一个集合。它可以有 0 个,1 个或者多个 filters. Filters,可以根据给定参数缩小查询结果量。在 SQL 的层面上, QuerySet
对应 SELECT
语句,而过滤器对应类似 WHERE
或 LIMIT
的限制子句。
你能通过模型的 Manager
获取 QuerySet
。每个模型至少有一个 Manager
,默认名称是 objects
。像这样直接通过模型类使用它:
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances."
注解
Managers
只能通过模型类访问,而不是通过模型实例,目的是强制分离 “表级” 操作和 “行级” 操作。
Manager
是模型的 QuerySets
主要来源。例如 Blog.objects.all()
返回了一个 QuerySet
,后者包含了数据库中所有的 Blog
对象。
all()
返回的 QuerySet
包含了数据表中所有的对象。虽然,大多数情况下,你只需要完整对象集合的一个子集。
要创建一个这样的子集,你需要通过添加过滤条件精炼原始 QuerySet
。两种最常见的精炼 QuerySet
的方式是:
查询参数(**kwargs
)应该符合下面的 Field lookups 的要求。
例如,要包含获取 2006 年的博客条目(entries blog)的 QuerySet
,像这样使用 filter()
:
Entry.objects.filter(pub_date__year=2006)
通过默认管理器类也一样:
Entry.objects.all().filter(pub_date__year=2006)
精炼 QuerySet
的结果本身还是一个 QuerySet
,所以能串联精炼过程。例子:
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)
... )
这个先获取包含数据库所有条目(entry)的 QuerySet
,然后排除一些,再进入另一个过滤器。最终的 QuerySet
包含标题以 "What" 开头的,发布日期介于 2005 年 1 月 30 日与今天之间的所有条目。
QuerySet
都是唯一的P每次精炼一个 QuerySet
,你就会获得一个全新的 QuerySet
,后者与前者毫无关联。每次精炼都会创建一个单独的、不同的 QuerySet
,能被存储,使用和复用。
举例:
>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
这三个 QuerySets
是独立的。第一个是基础 QuerySet
,包含了所有标题以 "What" 开头的条目。第二个是第一个的子集,带有额外条件,排除了 pub_date
是今天和今天之后的所有记录。第三个是第一个的子集,带有额外条件,只筛选 pub_date
是今天或未来的所有记录。最初的 QuerySet
(q1
) 不受筛选操作影响。
QuerySet
是惰性的PQuerySet
是惰性的 —— 创建 QuerySet
并不会引发任何数据库活动。你可以将一整天的过滤器都堆积在一起,Django 只会在 QuerySet
被 计算 时执行查询操作。来瞄一眼这个例子:
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)
) 做了一次。一般来说, QuerySet
的结果直到你 “要使用” 时才会从数据库中拿出。当你要用时,才通过数据库 计算 出 QuerySet
。关于何时才真的执行计算的更多细节,参考 When QuerySets are evaluated。
get()
检索单个对象Pfilter()
总是返回一个 QuerySet
,即便只有一个对象满足查询条件 —— 这种情况下, QuerySet
只包含了一个元素。
若你知道只会有一个对象满足查询条件,你可以在 Manager
上使用 get()
方法,它会直接返回这个对象:
>>> one_entry = Entry.objects.get(pk=1)
你可以对 get()
使用与 filter()
类似的所有查询表达式 —— 同样的,参考下面的 Field lookups。
注意, 使用切片 [0]
时的 get()
和 filter()
有点不同。如果没有满足查询条件的结果, get()
会抛出一个 DoesNotExist
异常。该异常是执行查询的模型类的一个属性 —— 所有,上述代码中,若没有哪个 Entry
对象的主键是 1,Django 会抛出 Entry.DoesNotExist
。
类似了,Django 会在有不止一个记录满足 get()
查询条件时发出警告。这时,Django 会抛出 MultipleObjectsReturned
,这同样也是模型类的一个属性。
QuerySet
方法P大多数情况下,你会在需要从数据库中检索对象时使用 all()
, get()
, filter()
和 exclude()
。然而,这样远远不够;完整的各种 QuerySet
方法请参阅 QuerySet API 参考。
QuerySet
条目数P利用 Python 的数组切片语法将 QuerySet
切成指定长度。这等价于 SQL 的 LIMIT
和 OFFSET
子句。
例如,这将返回前 5 个对象 (LIMIT 5
):
>>> Entry.objects.all()[:5]
这会返回第 6 至第 10 个对象 (OFFSET 5 LIMIT 5
):
>>> Entry.objects.all()[5:10]
不支持负索引 (例如 Entry.objects.all()[-1]
)
一般情况下, QuerySet
的切片返回一个新的 QuerySet
—— 其并未执行查询。一个特殊情况是使用了的 Python 切片语法的 “步长”。例如,这将会实际的执行查询命令,为了获取从前 10 个对象中,每隔一个抽取的对象组成的列表:
>>> Entry.objects.all()[:10:2]
由于对 queryset 切片工作方式的模糊性,禁止对其进行进一步的排序或过滤。
要检索 单个 对象而不是一个列表时(例如 SELECT foo FROM bar LIMIT 1
),简单的使用索引,而不是切片。例如,这会返回按标题字母排序后的第一个 Entry
:
>>> Entry.objects.order_by('headline')[0]
这大致等价于:
>>> Entry.objects.order_by('headline')[0:1].get()
然而,注意一下,若没有对象满足给定条件,前者会抛出 IndexError
,而后者会抛出 DoesNotExist
。参考 get()
获取更多细节。
字段查询即你如何制定 SQL WHERE
子句。它们以关键字参数的形式传递给 QuerySet
方法 filter()
, exclude()
和 get()
。
基本的查询关键字参数遵照 field__lookuptype=value
。(有个双下划线)。例如:
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
转换为 SQL 语句大致如下:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
这是怎么做到的
Python 能定义可接受任意数量 name-value 参数的函数,参数名和值均在运行时计算。更多信息,请参考官方 Python 教程中的 Keyword Arguments。
查询子句中指定的字段必须是模型的一个字段名。不过也有个例外,在 ForeignKey
中,你可以指定以 _id
为后缀的字段名。这种情况下,value 参数需要包含 foreign 模型的主键的原始值。例子:
>>> Entry.objects.filter(blog_id=4)
若你传入了无效的关键字参数,查询函数会抛出 TypeError
。
数据库 API 支持两套查询类型;完整参考文档位于 字段查询参考。为了让你了解能干啥,以下是一些常见的查询:
exact
一个 "exact" 匹配的例子:
>>> Entry.objects.get(headline__exact="Cat bites dog")
会生成这些 SQL:
SELECT ... WHERE headline = 'Cat bites dog';
若你为提供查询类型 —— 也就说,若关键字参数未包含双下划线 —— 查询类型会被指定为 exact
。
例如,以下两条语句是等价的:
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
这是为了方便,因为 exact
查询是最常见的。
iexact
不分大小写的匹配,查询语句:
>>> Blog.objects.get(name__iexact="beatles blog")
会匹配标题为 "Beatles Blog"
, "beatles blog"
, 甚至 "BeAtlES blOG"
的 Blog
。
contains
大小写敏感的包含测试。例子:
Entry.objects.get(headline__contains='Lennon')
粗略地转为 SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
注意这将匹配标题 'Today Lennon honored'
,而不是 'today lennon honored'
。
这也有个大小写不敏感的版本, icontains
。
startswith
, endswith
istartswith
和 iendswith
。同样,这只介绍了皮毛。完整的参考能在 field 查询参考 找到。
Django 提供了一种强大而直观的方式来“追踪”查询中的关联关系,在幕后自动为你处理 SQL JOIN
关系。要跨越关系,只需跨模型使用关联字段名,字段名由双下划线分割,直到拿到想要的字段。
本例检索出所有的 Entry
对象,带有 name
为 'Beatles Blog'
的 Blog
:
>>> Entry.objects.filter(blog__name='Beatles Blog')
跨域的深度随你所想。
反向操作也能行。要指向一个“反向的”关联关系,只需使用模型名的小写。
本例检索的所有 Blog
对象均拥有少一个 标题
含有 'Lennon'
的条目:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果你在跨多个关系进行筛选,而某个中间模型的没有满足筛选条件的值,Django 会将它当做一个空的(所有值都是 NULL
)但是有效的对象。这样就意味着不会抛出错误。例如,在这个过滤器中:
Blog.objects.filter(entry__authors__name='Lennon')
(假设有个关联的 Author
模型),若某项条目没有任何关联的 author
,它会被视作没有关联的 name
,而不是因为缺失 author
而抛出错误。大多数情况下,这就是你期望的。唯一可能使你迷惑的场景是在使用 isnull
时。因此:
Blog.objects.filter(entry__authors__name__isnull=True)
将会返回 Blog
对象,包含 author
的 name
为空的对象,以及那些 entry
的 author
为空的对象。若你不想要后面的对象,你可以这样写:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
当基于 ManyToManyField
或反向 ForeignKey
筛选某对象时,你可能对两种不同类型的过滤器感兴趣。假设 Blog
/Entry
关联关系(Blog
对 Entry
是一种一对多关联关系)。我们可能对那些条目标题同时含有 "Lennon" 且发布于 2008 年的博客感兴趣。或者,我们可能想找到那些条目标题包含 "Lennon" 或发布于 2008 的博客。由于多个条目能同时关联至一个 Blog
,两种查询都是可行的,且在某些场景下非常有用。
同样的场景也发生在 ManyToManyField
。例如,若有个 Entry
拥有一个叫做 tags
的 ManyToManyField
,要从关联至 tags 中的条目中找到名为 "music" 和 "bands" 的条目,或要找到某个标签名为 "music" 且状态为 "public" 的条目。
要处理这两种情况,Django 有一套统一的方式处理 filter()
调用。配置给某次 filter()
的所有条件会在调用时同时生效,筛选出满足条件的项目。连续的 filter()
调用进一步限制了对象结果集,但对于多值关联来说,限制条件作用于链接至主模型的对象,而不一定是那些被前置 filter()
调用筛选的对象。
这听起来可能有点迷糊,所以需要一个例子来解释一下。要筛选出所有关联条目同时满足标题含有 "Lennon" 且发布于 2008 (同一个条目,同时满足两个条件)年的博客,我们会这样写:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
要筛选所有条目标题包含 "Lennon" 或发布于 2008 年的博客,我们会这样写:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假设只有一个博客,拥有的条目同时满足标题含有 "Lennon" 且发布于 2008 年,但是发布于 2008 年的条目的标题均不含有 "Lennon"。第一项查询不会返回任何博客,而第二项查询会返回此博客。
在第二个例子中,第一个过滤器限制结果集为那些关联了标题包含 "Lennon" 的条目的博客。第二个过滤器进一步要求结果集中的博客要发布于 2008 年。第二个过滤器筛选的条目与第一个过滤器筛选的可能不尽相同。我们是用过滤器语句筛选 Blog
,而不是 Entry
。
注解
filter()
的查询行为会跨越多值关联,就像前文说的那样,并不与 exclude()
相同。相反,一次 exclude()
调用的条件并不需要指向同一项目。
例如,以下查询会排除那些关联条目标题包含 "Lennon" 且发布于 2008 年的博客:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
但是,与 filter()
的行为不同,其并不会限制博客同时满足这两种条件。要这么做的话,也就是筛选出所有条目标题不带 "Lennon" 且发布年不是 2008 的博客,你需要做两次查询:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
在之前的例子中,我们已经构建过的筛选器都是将模型字段值与常量做比较。但是,要怎么做才能将模型字段值与同一模型中的另一字段做比较呢?
Django 提供了 F 表达式
实现这种比较。 F()
的实例充当查询字段的引用。这些引用可在查询过滤器中用于在同一模型实例中比较两个不同的字段。
例如,要查出所有评论数大于 pingbacks 的博客条目,我们构建了一个 F()
对象,指代 pingback 的数量,然后在查询中使用该 F()
对象:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持对 F()
对象进行加、减、乘、除、求余和次方,另一操作数既可以是常量,也可以是其它 F()
对象。要找到那些评论数两倍于 pingbacks 的博客条目,我们这样修改查询条件:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
要找出所有评分低于 pingback 和评论总数之和的条目,修改查询条件:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
你也能用双下划线在 F()
对象中通过关联关系查询。带有双下划线的 F()
对象将引入访问关联对象所需的任何连接。例如,要检索出所有作者名与博客名相同的博客,这样修改查询条件:
>>> Entry.objects.filter(authors__name=F('blog__name'))
对于 date 和 date/time 字段,你可以加上或减去一个 timedelta
对象。以下会返回所有发布 3 天后被修改的条目:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()
对象通过 .bitand()
, .bitor()
, .bitrightshift()
和 .bitleftshift()
支持位操作。例子:
>>> F('somefield').bitand(16)
pk
) 查询快捷方式P出于方便的目的,Django 提供了一种 pk
查询快捷方式, pk
表示主键 "primary key"。
示例 Blog
模型中,主键是 id
字段,所以这 3 个语句是等效的:
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact
pk
的使用并不仅限于 __exact
查询——任何的查询项都能接在 pk
后面,执行对模型主键的查询:
# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7])
# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)
pk
查找也支持跨连接。例如,以下 3 个语句是等效的:
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3) # __exact is implied
>>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
LIKE
语句中转义百分号和下划线P等效于 LIKE
SQL 语句的字段查询子句 (iexact
, contains
, icontains
, startswith
, istartswith
, endswith
和 iendswith
) 会将 LIKE
语句中有特殊用途的两个符号,即百分号和下划线自动转义。(在 LIKE
语句中,百分号匹配多个任意字符,而下划线匹配一个任意字符。)
这意味着事情都是表里如一的,不会出现抽象事故。例如,要检索所有包含百分号的条目,就像对待其它字符一样使用百分号:
>>> Entry.objects.filter(headline__contains='%')
Django 为你小心处理了引号;生成的 SQL 语句看起来像这样:
SELECT ... WHERE headline LIKE '%\%%';
同样的处理也包括下划线。百分号和下划线都为你自动处理,你无需担心。
QuerySet
P每个 QuerySet
都带有缓存,尽量减少数据库访问。理解它是如何工作的能让你编写更高效的代码。
新创建的 QuerySet
缓存是空的。一旦要计算 QuerySet
的值,就会执行数据查询,随后,Django 就会将查询结果保存在 QuerySet
的缓存中,并返回这些显式请求的缓存(例如,下一个元素,若 QuerySet
正在被迭代)。后续针对 QuerySet
的计算会复用缓存结果。
牢记这种缓存行为,在你错误使用 QuerySet
时可能会被它咬一下。例如,以下会创建两个 QuerySet
,计算它们,丢掉它们:
>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])
这意味着同样的数据库查询会被执行两次,实际加倍了数据库负载。同时,有可能这两个列表不包含同样的记录,因为在两次请求间,可能有 Entry
被添加或删除了。
要避免此问题,简单地保存 QuerySet
并复用它:
>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
QuerySet
未被缓存时P查询结果集并不总是缓存结果。当仅计算查询结果集的 部分 时,会校验缓存,若没有填充缓存,则后续查询返回的项目不会被缓存。特别地说,这意味着使用数组切片或索引的 限制查询结果集 不会填充缓存。
例如,重复的从某个查询结果集对象中取指定索引的对象会每次都查询数据库:
>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again
不过,若全部查询结果集已被检出,就会去检查缓存:
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache
以下展示一些例子,这些动作会触发计算全部的查询结果集,并填充缓存的过程:
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)
注解
只是打印查询结果集不会填充缓存。因为调用 __repr__()
仅返回了完整结果集的一个切片。
Q
对象完成复杂查询P在类似 filter()
中,查询使用的关键字参数是通过 "AND" 连接起来的。如果你要执行更复杂的查询(例如,由 OR
语句连接的查询),你可以使用 Q 对象
。
一个 Q 对象
(django.db.models.Q
) 用于压缩关键字参数集合。这些关键字参数由前文 "Field lookups" 指定。
例如,该 Q
对象压缩了一个 LIKE
查询:
from django.db.models import Q
Q(question__startswith='What')
Q
对象能通过 &
和 |
操作符连接起来。当操作符被用于两个 Q
对象之间时会生成一个新的 Q
对象。
例如,该语句生成一个 Q
对象,表示两个 "question_startswith"
查询语句之间的 "OR" 关系:
Q(question__startswith='Who') | Q(question__startswith='What')
这等价于以下 SQL WHERE
字句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
你能通过 &
和 |
操作符和括号分组,组合任意复杂度的语句。当然, Q
对象也可通过 ~
操作符反转,允许在组合查询中组合普通查询或反向 (NOT
) 查询:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个接受关键字参数的查询函数 (例如 filter()
, exclude()
, get()
) 也同时接受一个或多个 Q
对象作为位置(未命名的)参数。若你为查询函数提供了多个 Q
对象参数,这些参数会通过 "AND" 连接。例子:
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
……粗略的翻译成 SQL:
SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
查询函数能混合使用 Q
对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q
对象)均通过 "AND" 连接。然而,若提供了 Q
对象,那么它必须位于所有关键字参数之前。例子:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)
……会是一个有效的查询,等效于前文的例子;但是:
# INVALID QUERY
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
……却是无效的。
参见
Django 单元测试中的 OR 查询实例 展示了 Q
的用法。
要比较两个模型实例,只需使用标准的 Python 比较操作符,两个等号: ==
。实际上,这比较了两个模型实例的主键值。
使用前文的 Entry
,以下的两个语句是等效的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
若模型主键名不是 id
,没问题。比较时总会使用主键,不管它叫啥。例如,若模型的主键字段名为 name
,以下两个语句是等效的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
通常,删除方法被命名为 delete()
。该方法立刻删除对象,并返回被删除的对象数量和一个包含了每个被删除对象类型的数量的字典。例子:
>>> e.delete()
(1, {'weblog.Entry': 1})
你也能批量删除对象。所有 QuerySet
都有个 delete()
方法,它会删除 QuerySet
中的所有成员。
例如,这会删除 2005 发布的所有 Entry
对象:
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
请记住,只要有机会的话,这会通过纯 SQL 语句执行,所以就无需在过程中调用每个对象的删除方法了。若你为模型类提供了自定义的 delete()
方法,且希望确保调用了该方法,你需要 “手动” 删除该模型的实例(例如,如,遍历 QuerySet
,在每个对象上分别调用 delete()
方法),而不是使用 QuerySet
的批量删除方法 delete()
。
当 Django 删除某个对象时,默认会模仿 SQL 约束 ON DELETE CASCADE
的行为——换而言之,某个对象被删除时,关联对象也会被删除。例子:
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
这种约束行为由 ForeignKey
的 on_delete
参数指定。
注意 delete()
是唯一未在 Manager
上暴漏的 QuerySet
方法。这是一种安全机制,避免你不小心调用了 Entry.objects.delete()
,删除了 所有的 条目。若你 确实 想要删除所有对象,你必须显示请求完整结果集合:
Entry.objects.all().delete()
虽然没有用于拷贝模型实例的内置方法,但仍能很简单的拷贝所有字段值创建新实例。最简单的例子,你只需将 pk
设为 None
。使用博客示例:
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
若你使用了集成,事情会更复杂。考虑下 Blog
的一个子类:
class ThemeBlog(Blog):
theme = models.CharField(max_length=200)
django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3
根据继承的原理,你必须将 pk
和 id
都设置为 None:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
该方法不会拷贝不是模型数据表中的关联关系。例如, Entry
有一个对 Author
的 ManyToManyField
关联关系。在复制条目后,你必须为新条目设置多对多关联关系。
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)
对于 OneToOneField
关联,你必须拷贝关联对象,并将其指定给新对象的关联字段,避免违反一对一唯一性约束。例如,指定前文复制的 entry
:
detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()
有时候,你想统一设置 QuerySet
中的所有对象的某个字段。你可以通过 update()
达到目的。例子:
# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
你仅能用此方法设置非关联字段和 ForeignKey
字段。要修改非关联字段,需要用常量提供新值。要修改 ForeignKey
字段,将新值置为目标模型的新实例。例子:
>>> b = Blog.objects.get(pk=1)
# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)
方法 update()
立刻被运行,并返回匹配查询调节的行数(若某些行早已是新值,则可能不等于实际匹配的行数)。更新 QuerySet
的唯一限制即它只能操作一个数据表:该模型的主表。你可以基于关联字段进行筛选,但你只能更新模型主表中的列。例子:
>>> b = Blog.objects.get(pk=1)
# Update all the headlines belonging to this Blog.
>>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')
要认识到 update()
方法是直接转为 SQL 语句的。这是一种用于直接更新的批量操作。它并不会调用模型的 save()
方法,或发射 pre_save
或 post_save
信号(调用 save()
会触发信号),或使用 auto_now
字段选项。若想保存 QuerySet
中的每项,并确保调用了每个实例的 save()
方法,你并不需要任何特殊的函数来处理此问题。只需迭代它们,并调用它们的 save()
方法:
for item in my_queryset:
item.save()
调用更新方法时也能使用 F 表达式
基于同一模型另一个字段的值更新某个字段。这在基于计数器的当前值增加其值时特别有用。例如,要增加针对博客中每项条目的 pingback 技术:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
然而,与过滤器中的 F()
对象和排除字句不同,你不能在更新方法中使用 F()
对象的同时使用 join——你只能引用被更新模型的内部字段。若你试着在使用 join 字句时使用 F()
对象,会抛出一个 FieldError()
:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
若你发现需要编写的 SQL 查询语句太过复杂,以至于 Django 的数据库映射无法处理,你可以回归手动编写 SQL。Django 针对编写原生 SQL 有几个选项;参考 执行原生 SQL 查询。
最后,Django 数据库层只是一种访问数据库的接口,理解这点非常重要。你也可以通过其它工具,编程语言或数据库框架访问数据库;Django 并没有对数据库数据库做啥独有的操作。
8月 23, 2019