进行查询¶
一旦你创建了你的 数据模型,Django自动给你一个数据库抽象API,让你创建,检索,更新和删除对象。本文档说明如何使用此API。有关所有各种模型查找选项的完整详细信息,请参阅 数据模型参考。
在本指南(以及参考文献)中,我们将参考以下模型,其中包括Weblog应用程序:
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return self.name
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()
def __str__(self): # __unicode__ on Python 2
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
字段¶
更新 ForeignKey
字段的方式与保存正常字段完全相同 - 只需将正确类型的对象分配给相关字段。此示例更新 Entry
实例 entry
的 blog
属性,假设 Entry
和 Blog
的适当实例已保存到数据库(因此我们可以在下面检索它们):
>>> from blog.models import 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
表示数据库中的对象集合。它可以有零个,一个或多个 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()
返回包含数据库中所有 Blog
对象的 QuerySet
。
使用过滤器检索特定对象¶
all()
返回的 QuerySet
描述数据库表中的所有对象。通常,你需要只选择一组完整的对象的子集。
要创建此类子集,请细化初始 QuerySet
,添加过滤条件。改进 QuerySet
的两种最常见的方法是:
filter(**kwargs)
返回包含与给定查找参数匹配的对象的新
QuerySet
。exclude(**kwargs)
返回一个新的
QuerySet
,它包含与给定查找参数匹配的 not 对象。
查找参数(上述函数定义中的 **kwargs
)应采用下面 Field lookups 中描述的格式。
例如,要从2006年获得 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(2005, 1, 30)
... )
这需要数据库中所有条目的初始 QuerySet
,添加一个过滤器,然后是排除,然后是另一个过滤器。最终结果是 QuerySet
,其中包含所有条目,标题以“What”开头,在2005年1月30日之前发布,并且当前日期。
过滤的 QuerySet
是唯一的¶
每次改进 QuerySet
时,您都会得到一个全新的 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
是懒惰的¶
QuerySets
是懒惰的 - 创建 QuerySet
的行为不涉及任何数据库活动。你可以将过滤器整个堆栈在一起,Django实际上不会运行查询,直到 QuerySet
是 evaluated。看看这个例子:
>>> 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
是 evaluated 通过访问数据库。有关确切何时进行评估的详细信息,请参阅 当评估 QuerySet 时。
使用 get()
检索单个对象¶
filter()
将总是给你一个 QuerySet
,即使只有一个对象匹配查询 - 在这种情况下,它将是一个 QuerySet
包含单个元素。
如果您知道只有一个对象与您的查询匹配,您可以在 Manager
上使用 get()
方法,该方法直接返回对象:
>>> one_entry = Entry.objects.get(pk=1)
您可以使用任何查询表达式与 get()
,就像 filter()
- 再次,请参阅下面的 Field lookups。
注意,在使用 get()
和使用具有 [0]
切片的 filter()
之间存在差异。如果没有匹配查询的结果,get()
将引发 DoesNotExist
异常。此异常是执行查询的模型类的属性 - 因此在上面的代码中,如果没有主键为1的 Entry
对象,Django将提升 Entry.DoesNotExist
。
类似地,如果多个项目与 get()
查询匹配,Django将投诉。在这种情况下,它将提高 MultipleObjectsReturned
,这也是模型类本身的一个属性。
其他 QuerySet
方法¶
大多数时候,当您需要从数据库中查找对象时,您将使用 all()
,get()
,filter()
和 exclude()
。然而,这远不是所有的;请参阅 QuerySet API参考 获取所有各种 QuerySet
方法的完整列表。
限制 QuerySet
¶
使用Python的数组切片语法的一个子集来限制您的 QuerySet
到一定数量的结果。这相当于SQL的 LIMIT
和 OFFSET
子句。
例如,这返回前5个对象(LIMIT 5
):
>>> Entry.objects.all()[:5]
这返回第六到第十个对象(OFFSET 5 LIMIT 5
):
>>> Entry.objects.all()[5:10]
不支持负索引(即 Entry.objects.all()[-1]
)。
通常,切片 QuerySet
返回一个新的 QuerySet
- 它不评估查询。一个例外是如果你使用Python的slice语法的“step”参数。例如,这将实际执行查询,以返回前10的每个 second 对象的列表:
>>> Entry.objects.all()[:10:2]
要检索 single 对象而不是列表(例如 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能够定义接受任意名称和值参数的函数,这些参数的名称和值在运行时被求值。有关更多信息,请参阅官方Python教程中的 Keyword Arguments。
在查找中指定的字段必须是模型字段的名称。有一个例外,但是在 ForeignKey
的情况下,您可以指定字段名称后缀为 _id
。在这种情况下,value参数应包含外部模型主键的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果传递一个无效的关键字参数,查找函数将引发 TypeError
。
数据库API支持大约二十种查找类型;一个完整的参考可以在 字段查找引用 中找到。为了让您了解可用的功能,以下是您可能会使用的一些常见查找:
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")
将匹配
Blog
标题为"Beatles Blog"
,"beatles blog"
,甚至"BeAtlES 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
的不区分大小写的版本。
同样,这只是划伤表面。完整的参考可以在 字段查找引用 中找到。
查找跨关系¶
Django提供了一种强大而直观的方式来“跟踪”查找中的关系,在后台自动处理SQL JOIN
s。要跨越关系,只需使用模型之间的相关字段的字段名称(用双下划线分隔),直到找到所需的字段。
此示例检索具有其 name
为 'Beatles Blog'
的 Blog
的所有 Entry
对象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
这个跨度可以像你想要的一样深。
它也向后工作。要引用“反向”关系,只需使用模型的小写名称。
此示例检索具有至少一个其 headline
包含 'Lennon'
的 Entry
的所有 Blog
对象:
>>> 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)
将返回在 author
上具有空 name
的 Blog
对象,以及在 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
,我们可能想要找到链接到标签 “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” 的条目的所有博客。第二过滤器将博客 further 集合限制为也被链接到在2008年发布的条目的博客。由第二过滤器选择的条目可以与第一过滤器中的条目相同或不同。我们正在用每个过滤器语句过滤 Blog
项目,而不是 Entry
项目。
注解
对于跨越多值关系的查询,如上所述,filter()
的行为不等效地实现为 exclude()
。相反,单个 exclude()
调用中的条件不一定引用相同的项目。
例如,以下查询将排除在2008年发布的标题 and 条目中包含具有 “Lennon” 的 both 条目的博客:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
但是,与使用 filter()
时的行为不同,这不会限制基于满足这两个条件的条目的博客。为了做到这一点,即选择不包含在2008年发布的 “Lennon” 发布的条目的所有博客,您需要进行两个查询:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
过滤器可以引用模型上的字段¶
在迄今为止给出的例子中,我们构造了将模型字段的值与常数进行比较的过滤器。但是,如果你想比较模型字段的值与同一模型上的另一个字段怎么办?
Django提供 F expressions
允许这样的比较。 F()
的实例充当对查询中的模型字段的引用。然后,可以在查询过滤器中使用这些引用来比较同一模型实例上的两个不同字段的值。
例如,要找到所有博客条目的列表,其中有比pingback更多的评论,我们构造一个 F()
对象来引用pingback计数,并在查询中使用该 F()
对象:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django支持对 F()
对象使用加法,减法,乘法,除法,模数和幂运算,这两个对象都具有常量和其他 F()
对象。为了找到所有的博客条目具有多于 twice 的许多评论作为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'))
对于日期和日期/时间字段,可以添加或减去 timedelta
对象。以下内容将返回在发布之后超过3天进行了修改的所有条目:
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()
对象支持例如 .bitand()
和 .bitor()
的按位操作:
>>> F('somefield').bitand(16)
pk
查找快捷方式¶
为了方便起见,Django提供了一个 pk
查找快捷方式,代表“主键”。
在示例 Blog
模型中,主键是 id
字段,因此这三个语句是等效的:
>>> 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
查找也在连接之间工作。例如,这三个语句是等效的:
>>> 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
声明中转义百分号和下划线¶
字段查找等同于 LIKE
SQL语句(iexact
,contains
,icontains
,startswith
,istartswith
,endswith
和 iendswith
)将自动转义 LIKE
语句中使用的两个特殊字符 - 百分号和下划线。 (在 LIKE
语句中,百分号表示多字符通配符,下划线表示单字符通配符。)
这意味着事情应该直观,所以抽象不泄漏。例如,要检索包含百分号的所有条目,只需使用百分号作为任何其他字符:
>>> Entry.objects.filter(headline__contains='%')
Django照顾你的报价;结果SQL将看起来像这样:
SELECT ... WHERE headline LIKE '%\%%';
下划线也一样。百分号和下划线都是透明地处理的。
缓存和 QuerySet
¶
每个 QuerySet
包含一个缓存以最小化数据库访问。了解其工作原理将允许您编写最有效的代码。
在新创建的 QuerySet
中,高速缓存为空。第一次评估 QuerySet
,因此,发生数据库查询 - Django将查询结果保存在 QuerySet
的缓存中,并返回已明确请求的结果(例如,如果正在重复 QuerySet
,则返回下一个元素过度)。随后对 QuerySet
的评估重用缓存的结果。
保持这种缓存行为,因为它可能会咬你,如果你不正确使用您的 QuerySet
。例如,以下将创建两个 QuerySet
s,评估它们,并将它们丢弃:
>>> 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
未缓存时¶
查询集不总是缓存其结果。当仅评估查询集的 part 时,将检查缓存,但如果未填充,则不缓存由后续查询返回的项。具体来说,这意味着使用数组切片或索引的 限制查询集 将不会填充缓存。
例如,在查询集对象中重复获取某个索引将每次查询数据库:
>>> 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
对象进行复杂查找¶
关键字参数查询 - 在 filter()
等中 - 被“AND”在一起。如果需要执行更复杂的查询(例如,使用 OR
语句的查询),则可以使用 Q objects
。
Q object
(django.db.models.Q
)是用于封装关键字参数集合的对象。这些关键字参数在上面的“字段查找”中指定。
例如,此 Q
对象封装单个 LIKE
查询:
from django.db.models import Q
Q(question__startswith='What')
Q
对象可以使用 &
和 |
运算符组合。当在两个 Q
对象上使用运算符时,它产生一个新的 Q
对象。
例如,此语句生成表示两个 "question__startswith"
查询的“OR”的单个 Q
对象:
Q(question__startswith='Who') | Q(question__startswith='What')
这等同于以下SQL WHERE
子句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
您可以通过将 Q
对象与 &
和 |
运算符组合并使用括号分组来构成任意复杂度的语句。此外,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 lookups examples 显示了 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
的所有成员。
例如,这将删除 pub_date
年份为2005的所有 Entry
对象:
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
请记住,这将尽可能纯粹在SQL中执行,因此单个对象实例的 delete()
方法不一定会在过程中被调用。如果您在模型类上提供了一个自定义 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()
,并删除 all 条目。如果 do 要删除所有对象,则必须显式请求一个完整的查询集:
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 expressions
根据模型中另一个字段的值更新一个字段。这对于根据计数器的当前值递增计数器特别有用。例如,为博客中的每个条目递增pingback计数:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
但是,与过滤器和排除子句中的 F()
对象不同,在更新中使用 F()
对象时,不能引入连接 - 只能引用正在更新的模型的本地字段。如果尝试引入具有 F()
对象的联接,则将引发 FieldError
:
# THIS WILL RAISE A FieldError
>>> Entry.objects.update(headline=F('blog__name'))