执行查询

一旦你建立好数据模型,Django会自动为你生成一套数据库抽象的API,可以让你创建、检索、更新和删除对象。 这篇文档阐述如何使用这些API。 关于模型查询所有选项的完整细节,请见数据模型参考

在整个文档(以及参考)中,我们将引用下面的模型,它构成一个博客应用:

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

创建对象

Django 使用一种直观的方式把数据库表中的数据表示成Python 对象:一个模型类代表数据库中的一个表,一个模型类的实例代表这个数据库表中的一条特定的记录。

使用关键字参数实例化模型实例来创建一个对象,然后调用save() 把它保存到数据库中。

假设模型存放于文件mysite/blog/models.py中,下面是一个例子:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

上面的代码在背后执行了SQL 的INSERT 语句。 在你显式调用save()之前,Django 不会访问数据库。

save() 方法没有返回值。

请参见

save()方法带有一些高级选项,它们没有在这里给出。 完整的细节请见save() 文档。

如果你想只用一条语句创建并保存一个对象,使用create()方法。

将更改保存到对象

要保存对数据库中已存在的对象的改动,请使用save()

假设Blog 的一个实例b5已经被保存在数据库中,下面这个例子将更改它的name并且更新数据库中的记录:

>>> b5.name = 'New name'
>>> b5.save()

上面的代码在背后执行SQL 的UPDATE语句。 在你显式调用save()之前,Django 不会访问数据库。

保存ForeignKeyManyToManyField字段

更新ForeignKey 字段的方式和保存普通字段相同 — 只要把一个正确类型的对象赋值给该字段。 下面的例子更新一个Entry实例entryblog属性,假设EntryBlog已经有正确的实例保存在数据库中(所以我们可以像下面这样获取它们):

>>> 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()方法来增加关联关系的一条记录。 下面这个例子向entry对象添加Author类的实例joe

>>> 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表示从数据库中取出来的对象的集合。 它可以含有零个、一个或者多个过滤器 过滤器基于所给的参数限制查询的结果。 从SQL 的角度来看,QuerySetSELECT 语句等价,过滤器是像WHERELIMIT 一样的限制子句。

你可以从模型的Manager那里取得QuerySet 每个模型都至少有一个Manager,它默认命名为objects 通过模型类来直接访问它,像这样:

>>> Blog.objects

>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
    ...
AttributeError:“无法通过博客实例访问管理器”。

Managers只可以通过模型的类访问,而不可以通过模型的实例访问,目的是为了强制区分“表级别”的操作和“记录级别”的操作。

对于一个模型来说,ManagerQuerySets的主要来源。 例如, Blog.objects.all() 返回一个QuerySet,这个QuerySet包含数据库中所有Blog对象,即Blog表中所有的记录.

检索所有对象

获取一个表中所有对象的最简单的方式是全部获取。 可以使用Managerall() 方法:

>>> all_entries = Entry.objects.all()

all()方法返回包含数据库中所有对象的一个QuerySet

使用过滤器检索特定对象

all() 方法返回了一个包含数据库表中所有记录QuerySet 但在通常情况下,你往往想要获取的是完整数据集的一个子集。

要创建这样一个子集,你需要在原始的的QuerySet上增加一些过滤条件。 QuerySet两个最普遍的途径是:

filter(**kwargs)
返回一个新的QuerySet,它包含满足查询参数的对象。
exclude(**kwargs)
返回一个新的QuerySet,它包含满足查询参数的对象。

查询参数(上面函数定义中的**kwargs)需要满足特定的格式,下面字段查询一节中会提到。

举个例子,要获取年份为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不会带来任何数据库的访问。 你可以将过滤器保持一整天,直到QuerySet 需要求值时,Django 才会真正运行这个查询。 看下这个例子:

>>> 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 通过访问数据库来求值 关于求值发生的准确时间,参见QuerySets何时求值

使用get()检索单个对象

filter() 始终给你一个QuerySet,即使只有一个对象满足查询条件 —— 这种情况下,QuerySet将只包含一个元素。

如果你知道只有一个对象满足你的查询,你可以使用Managerget() 方法,它直接返回该对象:

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

可以对get() 使用任何查询表达式,和filter() 一样 —— 同样请查看下文的字段查询

注意,使用get() 和使用filter() 的切片[0] 有一点区别。 如果没有结果满足查询,get() 将引发一个DoesNotExist 异常。 这个异常是正在查询的模型类的一个属性 —— 所以在上面的代码中,如果没有主键(pk) 为1 的Entry对象,Django 将引发一个Entry.DoesNotExist

类似地,如果有多条记录满足get() 的查询条件,Django 也将报错。 这种情况将引发MultipleObjectsReturned,它同样是模型类自身的一个属性。

其他QuerySet方法

大多数情况下,需要从数据库中查找对象时,你会使用all()get()filter()exclude() 然而,这远不及一切;有关所有各种QuerySet方法的完整列表,请参阅QuerySet API Reference

限制QuerySet

可以使用Python 的切片语法来限制QuerySet记录的数目 。 它等同于SQL 的OFFSETLIMIT 子句。

例如,下面的语句返回前面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 切片语法中"step"参数。 例如,下面的语句将返回前10 个对象中每隔2个对象,它将真实执行查询:

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

检索单个对象而不是列表(例如, SELECT foo FROM bar LIMIT 1),使用简单的索引而不是切片。 例如,下面的语句返回数据库中根据标题排序后的第一条Entry

>>> Entry.objects.order_by('headline')[0]

它大体等同于:

>>> Entry.objects.order_by('headline')[0:1].get()

然而请注意,如果没有对象满足给定的条件,第一条语句将引发DoesNotExist而第二条语句将引发IndexError 更多细节参见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 后缀。 在这种情况下,该参数的值应该是外键的原始值。 像这样:

>>> 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")

将匹配标题为"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

startswithendswith
分别表示以XXX开头和以XXX结尾。 当然还有大小写不敏感的版本,叫做istartswithiendswith

同样,这里只是表面。 完整的参考可以在字段查找参考中找到。

跨关联关系的查询

Django 提供一种强大而又直观的方式来“处理”查询中的关联关系,它在后台自动帮你处理JOIN 若要跨越关联关系,只需使用关联的模型字段的名称,并使用双下划线分隔,直至你想要的字段:

下面这个例子获取所有Blogname'Beatles Blog'Entry 对象:

>>> Entry.objects.filter(blog__name='Beatles Blog')

这种跨越可以是任意的深度。

它还可以反向工作。 若要引用一个“反向”的关系,只需要使用该模型的小写的名称。

下面的示例获取所有的Blog 对象,它们至少有一个Entryheadline包含'Lennon'

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

如果你在多个关联关系过滤而且其中某个中介模型没有满足过滤条件的值,Django 将把它当做一个空的(所有的值都为NULL)但是合法的对象。 这意味着不会有错误引发。 例如,在下面的过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

(如果有一个相关联的Author 模型),如果没有author与entry关联,那么它将当作其没有name,而不会因为没有author 引发一个错误。 通常,这就是你想要的。 唯一可能让你困惑的是当你使用isnull 的时候。 因此:

Blog.objects.filter(entry__authors__name__isnull=True)

返回的Blog对象包括authorname为空的对象,以及entry上的author为空的对象。 如果你不需要后者,你可以这样写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

跨越多值的关联关系

当你基于ManyToManyField 或反向的ForeignKey 来过滤一个对象时,有两种不同的过滤器你可能会感兴趣。 考虑Entry/Blog 关联关系(BlogEntry 是一对多的关系)。 我们可能想找出blogs,它们有一个entry的headline为“Lennon” 并且pub_date为'2008'年的Entry。 或者我们可能想查询blogs,它们有一个entry的headline为“Lennon” 的并且也有一个entry的pub_date为'2008'。 因为实际上有和单个Blog 相关联的多个Entry,所以这两个查询在某些场景下都是有可能并且有意义的。

ManyToManyField 有类似的情况。 例如,如果Entry有一个ManyToManyField叫做tags,我们可能想找到tag叫做“music”“bands”的Entry,或者我们想找一个tag名为“music”且状态为“public”的Entry。

对于这两种情况,Django 有种一致的方法来处理filter() 调用。 一个filter() 调用中的所有参数会同时应用以过滤出满足所有要求的记录。 接下来的filter() 调用进一步限制对象集,但是对于多值关系,它们应用到与主模型关联的对象,而不是应用到前一个filter() 调用选择出来的对象。

这些听起来可能有点混乱,所以希望展示一个例子使它变得更清晰。 选择所有包含同时满足两个条件的entry的blog,这两个条件是headline 包含Lennon 和发表时间是2008 (同一个entry 满足两个条件),我们的代码是:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

要选择所有这样的blog,有一个entry的headline包含“Lennon”有一个entry发表时间是2008,我们将这样编写:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

假设这里有一个blog拥有一条包含'Lennon'的entries条目和一条来自2008的entries条目,但是没有一条来自2008并且包含"Lennon"的entries条目。 第一个查询不会返回任何blog,第二个查询将会返回一个blog。

在第二个例子中, 第一个filter限定查询集为所有与headline包含“Lennon”的entry关联的blog。 第二个filter进一步限定查询集中的blog,这些blog关联的entry 的发表时间是2008。 第二个filter 过滤出来的entry 与第一个filter 过滤出来的entry 可能相同也可能不同。 我们用每个filter语句过滤的是Blog,而不是Entry

跨越多值关系的filter() 查询的行为,与exclude() 实现的不同。 单个exclude() 调用中的条件不必引用同一个记录。

例如,下面的查询将排除两种 entry的blog,headline中包含“Lennon”的entry在2008年发布的entry:

Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)

然而,这与使用filter() 的行为不同,它不是排除同时满足两个条件的Entry。 为了实现这点,即选择的Blog中不包含在2008年发布且healine 中带有“Lennon” 的Entry,你需要编写两个查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)

过滤器可以引用模型的字段

到目前为止给出的示例中,我们构造过将模型字段与常量进行比较的filter。 但是,如果你想将模型的一个字段与同一个模型的另外一个字段进行比较该怎么办?

Django 提供F表达式 来允许这样的比较。 F() 返回的实例用作查询内部对模型字段的引用。 这些引用可以用于查询的filter 中来比较相同模型实例上不同字段之间值的比较。

例如,为了查找comments 数目多于pingbacks 的Entry,我们将构造一个F() 对象来引用pingback 数目,并在查询中使用该F() 对象:

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

Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作,两个操作数可以都是常数和其它F() 对象。 为了查找comments 数目比pingbacks 两倍还要多的Entry,我们将查询修改为:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

为了查询rating 比pingback 和comment 数目总和要小的Entry,我们将这样查询:

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

你还可以在F() 对象中使用双下划线标记来跨越关联关系。 带有双下划线的F() 对象将引入任何需要的join 操作以访问关联的对象。 例如,如要获取author 的名字与blog 名字相同的Entry,我们可以这样查询:

>>> Entry.objects.filter(authors__name=F('blog__name'))

对于date 和date/time 字段,你可以给它们加上或减去一个timedelta 对象。 下面的例子将返回发布超过3天后被修改的所有Entry:

>>> 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)
在Django 1.11中的更改:

添加了对.bitrightshift().bitleftshift()的支持。

pk查找快捷方式

为了方便,Django 提供一个查询快捷方式pk ,它表示“primary key” 的意思。

在示例Blog模型中,主键pk是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组合来执行查询一个模型的primary key:

# 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查询在join 中也可以工作。 例如,下面三个语句是等同的:

>>> 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语句中转义百分号和下划线

endswith SQL 语句等同的字段查询(LIKEistartswithiendswithiexactLIKEstartswithcontains)将自动转义在icontains 语句中使用的两个特殊的字符 —— 百分号和下划线。 (在LIKE 语句中,百分号通配符表示多个字符,下划线通配符表示单个字符)。

这意味着语句将很直观,不会显得太抽象。 例如,要获取包含一个百分号的所有的Entry,只需要像其它任何字符一样使用百分号:

>>> Entry.objects.filter(headline__contains='%')

Django照顾你的引用;生成的SQL将如下所示:

SELECT ... WHERE headline LIKE '%\%%';

对于下划线是同样的道理。 百分号和下划线都会透明地帮你处理。

缓存和QuerySet

每个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不缓存

查询集不会永远缓存它们的结果。 当只对查询集的部分进行求值时会检查缓存, 但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。 特别地,这意味着使用切片或索引来limiting the queryset将不会填充缓存。

例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:

>>> 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对象

Q 对象 (django.db.models.Q) 用于封装一组关键字参数。 这些关键字参数就是上文“字段查询” 中所提及的那些。

例如,下面的LIKE 对象封装一个Q 查询:

from django.db.models import Q
Q(question__startswith='What')

Q对象可以使用&|操作符组合起来。 当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

例如,下面的语句产生一个"question__startswith" 对象,表示两个Q 查询的“OR” :

Q(question__startswith='Who') | Q(question__startswith='What')

它等同于下面的SQL WHERE 子句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

你可以组合&| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。 同时,~ 对象可以使用NOT 操作符取反,这允许组合正常的查询和取反(Q) 查询:

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中的所有成员。

例如,下面的语句删除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()

这种级联的行为可以通过的ForeignKeyon_delete 参数自定义。

注意,delete()是唯一没有在Manager上暴露出来的QuerySet方法。 这是一个安全机制来防止你意外地请求Entry.objects.delete(),而删除所有 的条目。 如果你确实想删除所有的对象,你必须明确地请求一个完全的查询集:

Entry.objects.all().delete()

复制模型实例

虽然没有内建的方法用于拷贝模型实例,但还是很容易创建一个新的实例并让它的所有字段都拷贝过来。 最简单的方法是,只需要将pk 设置为None 利用我们的Blog 示例:

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

由于继承的工作方式,你必须设置idpk 都为None:

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

此过程不会复制不属于模型数据库表的一部分的关系。 例如,Entry有一个ManyToManyFieldAuthor 复制条目后,您必须为新条目设置多对多关系:

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_savepost_save信号(调用save()方法产生)或者查看auto_now 字段选项。 如果你想保存QuerySet中的每个条目并确保每个实例的save() 方法都被调用,你不需要使用任何特殊的函数来处理。 只需要迭代它们并调用save()

for item in my_queryset:
    item.save()

对update 的调用也可以使用F expressions 来根据模型中的一个字段更新另外一个字段。 这对于在当前值的基础上加上一个值特别有用。 例如,增加Blog 中每个Entry 的pingback 个数:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

然而,与filter 和exclude 子句中的F() 对象不同,在update 中你不可以使用F() 对象引入join —— 你只可以引用正在更新的模型的字段。 如果你尝试使用F()对象引入一个join,将引发一个FieldError

# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))

回到原始SQL

如果你发现需要编写的SQL 查询对于Django 的数据库映射机制太复杂,你可以回归到手工编写SQL。 Django有几个用于编写原始SQL查询的选项;请参阅执行原始的SQL查询

最后,值得注意的是Django 的数据库层只是数据库的一个接口。 您可以通过其他工具,编程语言或数据库框架访问数据库;没有Django特定的数据库。