查询¶
一旦你创建了数据模型,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):
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 语句。在您明确调用save().
该save()方法没有返回值。
保存对对象的更改¶
要将更改保存到数据库中已存在的对象,请使用 save().
给定一个已保存到数据库的Blog
实例,此示例更改其名称并更新其在数据库中的记录:b5
>>> b5.name = 'New name'
>>> b5.save()
UPDATE
这将在幕后执行一条SQL 语句。在您明确调用save().
保存ForeignKey
和ManyToManyField
字段¶
更新ForeignKey字段的工作方式与保存普通字段的方式完全相同——只需将正确类型的对象分配给相关字段即可。这个例子更新了instance的blog
属性 ,假设和 的适当实例已经保存到数据库中(所以我们可以在下面检索它们):Entry
entry
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()
更新 a 的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 会抱怨。
检索对象¶
要从数据库中检索对象,请 在模型类上QuerySet通过 a 构造一个。Manager
AQuerySet代表数据库中的对象集合。它可以有零个、一个或多个过滤器。过滤器根据给定的参数缩小查询结果的范围。在 SQL 术语中,a QuerySet等同于SELECT
语句,过滤器是限制子句,例如WHERE
or LIMIT
。
您可以QuerySet通过使用模型的 Manager. 每个模型至少有一个 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
数据库中所有对象的 a。
使用过滤器检索特定对象¶
QuerySet返回的 描述数据库表中的all()所有对象。但是,通常您只需要选择完整对象集的一个子集。
要创建这样的子集,您需要优化初始 QuerySet,添加过滤条件。优化 a 的两种最常见的方法QuerySet是:
filter(**kwargs)
返回一个新QuerySet的包含与给定查找参数匹配的对象。
exclude(**kwargs)
返回一个新的包含与给定查找参数不QuerySet匹配的对象。
查找参数(**kwargs
在上述函数定义中)应采用下面字段查找中描述的格式。
例如,要获取QuerySet2006 年的博客条目,请filter()像这样使用:
Entry.objects.filter(pub_date__year=2006)
使用默认管理器类,它与以下内容相同:
Entry.objects.all().filter(pub_date__year=2006)
链接过滤器¶
细化 a 的结果QuerySet本身就是 a QuerySet,因此可以将细化链接在一起。例如:
>>> Entry.objects.filter(
... headline__startswith='What'
... ).exclude(
... pub_date__gte=datetime.date.today()
... ).filter(
... pub_date__gte=datetime.date(2005, 1, 30)
... )
这需要QuerySet数据库中所有条目的初始值,添加一个过滤器,然后是一个排除项,然后是另一个过滤器。最终结果是QuerySet包含标题以“What”开头的所有条目,这些条目在 2005 年 1 月 30 日和当天之间发布。
过滤QuerySet
的 s 是唯一的¶
每次你提炼一个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
s 很懒¶
QuerySets
是懒惰的——创建 a 的行为 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您“请求”它们之前,不会从数据库中获取 a 的结果。当您这样做时,将 通过访问数据库QuerySet来评估。有关何时进行评估的更多详细信息,请参阅 何时评估 QuerySet。
使用¶检索单个对象get()
filter()将始终为您提供 a QuerySet,即使只有一个对象与查询匹配 - 在这种情况下,它将是 a QuerySet包含单个元素。
如果您知道只有一个对象与您的查询匹配,您可以使用 get()a 上的方法 Manager直接返回该对象:
>>> one_entry = Entry.objects.get(pk=1)
您可以将任何查询表达式与 一起使用 get(),就像与 filter()- 一样,请参阅下面的字段查找 。
get()请注意,使用和使用 filter()切片之间存在差异 [0]
。如果没有与查询匹配的结果, get()将引发DoesNotExist
异常。这个异常是执行查询的模型类的一个属性——所以在上面的代码中,如果没有Entry
主键为 1 的对象,Django 将 raise Entry.DoesNotExist
。
同样,如果有多个项目与 get()查询匹配,Django 会报错。在这种情况下,它会 raise MultipleObjectsReturned,这也是模型类本身的一个属性。
其他QuerySet
方法¶
大多数情况下,当您需要从数据库中查找对象时,您会使用 ,all()和 。 然而,这远非如此。有关所有各种方法的完整列表,请参阅 QuerySet API 参考。get()filter()exclude()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]
不支持负索引 (ie )。
通常,切片 aQuerySet返回一个新的 QuerySet——它不评估查询。一个例外是如果您使用 Python 切片语法的“step”参数。例如,这实际上会执行查询,以便返回前 10个对象中每隔一个对象的列表:
>>> 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()
但是请注意,如果没有对象符合给定条件,则第一个将引发IndexError
,而第二个将引发。DoesNotExist
有关 get()更多详细信息,请参阅。
字段查找¶
字段查找是您指定 SQLWHERE
子句内容的方式。它们被指定为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 教程中的关键字参数。
查找中指定的字段必须是模型字段的名称。但是有一个例外,如果是 a,ForeignKey您可以指定以 . 为后缀的字段名称_id
。在这种情况下, value 参数应该包含外部模型主键的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果您传递无效的关键字参数,则查找函数将引发 TypeError
。
数据库 API 支持大约两打查找类型;完整的参考可以在字段查找参考中找到。为了让您了解可用的内容,以下是您可能会使用的一些更常见的查找:
“精确”匹配。例如:
>>> 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
查找是常见的情况。
不区分大小写的匹配。所以,查询:
>>> Blog.objects.get(name__iexact="beatles blog")
将匹配Blog
标题为,或什至."Beatles Blog"
"beatles blog"
"BeAtlES blOG"
区分大小写的遏制测试。例如:
Entry.objects.get(headline__contains='Lennon')
大致翻译成这个 SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
请注意,这将匹配标题,但不 匹配。'Today Lennon honored'
'today lennon honored'
还有一个不区分大小写的版本,icontains.
分别以搜索开始和以搜索结束。还有不区分大小写的版本,称为istartswith和 iendswith。
同样,这只是表面问题。完整的参考可以在 字段查找参考中找到。
跨越关系的查找¶
Django 提供了一种强大而直观的方式来“跟踪”查找中的关系JOIN
,在幕后自动为您处理 SQL。要跨越关系,只需跨模型使用相关字段的字段名称,用双下划线分隔,直到找到所需的字段。
此示例检索 具有以下属性的所有Entry
对象:Blog
name
'Beatles Blog'
>>> Entry.objects.filter(blog__name='Beatles Blog')
这个跨度可以像你想要的那样深。
它也可以向后工作。要引用“反向”关系,只需使用模型的小写名称。
此示例检索所有对象, 其中Blog
至少有一个包含:Entry
headline
'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
对象以及在上为空的对象。如果你不想要后面的对象,你可以写:name
author
author
entry
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
跨越多值关系¶
当您基于 a ManyToManyField或 reverse 过滤对象时ForeignKey,您可能会对两种不同类型的过滤器感兴趣。考虑Blog
/Entry
关系(Blog
toEntry
是一对多关系)。我们可能有兴趣查找标题中包含“Lennon”且于 2008 年发布的条目的博客。或者我们可能希望查找标题中包含 “Lennon”的条目以及标题中包含“Lennon”的条目的博客于 2008 年发布。由于有多个条目与单个 相关联Blog
,因此这两种查询都是可能的,并且在某些情况下是有意义的。
相同类型的情况出现在 ManyToManyField. 例如,如果 anEntry
有一个 ManyToManyFieldcalled 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(),这不会根据满足这两个条件的条目来限制博客。为了做到这一点,即选择所有不包含 2008 年发布的以“Lennon”发布 的条目的博客,您需要进行两个查询:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
过滤器可以引用模型上的字段¶
在到目前为止给出的示例中,我们构建了将模型字段的值与常量进行比较的过滤器。但是,如果您想将模型字段的值与同一模型上的另一个字段的值进行比较怎么办?
Django允许这样的比较。的实例充当对查询中模型字段的引用。然后可以在查询过滤器中使用这些引用来比较同一模型实例上两个不同字段的值。F expressionsF()
例如,要查找评论数多于 pingback 的所有博客条目的列表,我们构造一个F()
对象来引用 pingback 计数,并F()
在查询中使用该对象:
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持对对象使用加法、减法、乘法、除法、模和幂运算F()
,包括常量和其他F()
对象。要查找所有评论数量是 pingback 的两倍以上的所有博客条目 ,我们修改查询:
>>> 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()
、 .bitrightshift()
和的按位运算.bitleftshift()
。例如:
>>> F('somefield').bitand(16)
支持.bitrightshift()
并被.bitleftshift()
添加。
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
s ¶
每个都QuerySet包含一个缓存以最小化数据库访问。了解它的工作原理将使您能够编写最有效的代码。
在新创建QuerySet的 中,缓存是空的。第一次QuerySet评估 a 时——因此,发生数据库查询——Django 将查询结果保存在QuerySet的缓存中并返回已明确请求的结果(例如,下一个元素,如果 QuerySet正在迭代)。QuerySet重用缓存结果的后续评估。
记住这种缓存行为,因为如果你没有QuerySet正确使用你的 s,它可能会咬你。例如,以下将创建两个QuerySets,评估它们,然后将它们丢弃:
>>> 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
s 没有被缓存时¶
查询集并不总是缓存它们的结果。当只评估部分查询集时,会检查缓存,但如果未填充,则不会缓存后续查询返回的项目。具体来说,这意味着 使用数组切片或索引限制查询集不会填充缓存。
例如,在查询集对象中重复获取某个索引,每次都会查询数据库:
>>> 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
复杂的对象查找¶
关键字参数查询—— infilter()等——被“AND”在一起。如果您需要执行更复杂的查询(例如,带有OR
语句的查询),您可以使用.Q objects
A ( ) 是用于封装关键字参数集合的对象。这些关键字参数在上面的“字段查找”中指定。Q objectdjango.db.models.Q
例如,这个Q
对象封装了一个LIKE
查询:
from django.db.models import Q
Q(question__startswith='What')
Q
可以使用&
and|
运算符组合对象。当一个运算符用于两个Q
对象时,它会产生一个新Q
对象。
例如,此语句生成一个Q
表示两个"question__startswith"
查询的“或”的对象:
Q(question__startswith='Who') | Q(question__startswith='What')
这等效于以下 SQLWHERE
子句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
Q
您可以通过将对象与&
and|
运算符组合并使用括号分组来组成任意复杂的语句。此外,Q
可以使用~
运算符对对象求反,从而允许结合普通查询和否定 ( NOT
) 查询的组合查找:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个接受关键字参数(例如,,,)的查找函数filter()也 exclude()可以 get()传递一个或多个 Q
对象作为位置(未命名)参数。如果您向 Q
查找函数提供多个对象参数,则这些参数将被“与”在一起。例如:
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
对象)都被“与”在一起。但是,如果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()方法,该方法删除 that 的所有成员QuerySet。
例如,这将删除年份为 2005Entry
的所有对象:pub_date
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
请记住,只要有可能,这将纯粹在 SQL 中执行,因此delete()
在此过程中不一定会调用单个对象实例的方法。如果您在delete()
模型类上提供了自定义方法并希望确保调用它,则需要“手动”删除该模型的实例(例如,通过迭代 a QuerySet并单独调用delete()
每个对象)而不是使用a的批量 delete()方法 QuerySet。
当 Django 删除一个对象时,默认情况下它会模拟 SQL 约束的行为——换句话说,任何具有指向要删除的对象的外键的对象都将与它一起被删除。例如:ON DELETE CASCADE
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
on_delete这种级联 行为可通过 ForeignKey.
请注意,这delete()是唯一 QuerySet不暴露在 a Manager自身上的方法。这是一种安全机制,可防止您意外请求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
为无:
django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4
此过程不会复制不属于模型数据库表的关系。例如,Entry
有一个ManyToManyField
to Author
。复制条目后,您必须为新条目设置多对多关系:
entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)
对于 a 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
orpost_save
信号(这是调用的结果 save()),也不会尊重 auto_nowfield 选项。如果您想将每个项目保存在 a 中QuerySet 并确保save()在每个实例上调用该方法,则不需要任何特殊函数来处理它。只需遍历它们并调用save():
for item in my_queryset:
item.save()
对更新的调用也可以用来根据模型中另一个字段的值来更新一个字段。这对于根据当前值递增计数器特别有用。例如,要增加博客中每个条目的 pingback 计数:F expressions
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
但是,与F()
filter 和 exclude 子句中的对象不同,当您在更新中使用对象时不能引入连接F()
——您只能引用正在更新的模型的本地字段。如果您尝试引入与F()
对象的连接,FieldError
将引发 a:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
回退到原始 SQL ¶
如果您发现自己需要编写一个过于复杂的 SQL 查询,而 Django 的数据库映射器无法处理,您可以退回到手动编写 SQL。Django 有几个用于编写原始 SQL 查询的选项;请参阅 执行原始 SQL 查询。
最后,重要的是要注意 Django 数据库层只是数据库的一个接口。您可以通过其他工具、编程语言或数据库框架访问您的数据库;您的数据库没有任何特定于 Django 的内容。