参考文献: Django 模型查询 navysummer博客 友情赞助:有道词典 (并没有)
目录
保存 ForeignKey 和 ManyToManyFiled 字段
Lookups that span relationships(跨越关系的查找)
Filters can reference fields on the model (在 filter 中引用模型的字段)
The pk lookup shortcut(主键查询简洁方式)
Escaping percent signs and underscores in LIKE statements(在 LIKE 语句中转义 '%' 和 '_')
Complex lookups with Q objects(使用Q对象进行复杂查询)
Copying model instances(复制模型实例)
Updating multiple objects at once(一次更新多个对象 )
One-to-many relationships(一对多关系)
Many-to-many relationships(多对多关系)
One-to-one relationships(一对一关系)
进行查询
一旦创建了数据模型,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 语句,Django 在显式调用 save() 之前不会访问数据库,save() 方法没有返回值。
注意:save() 接收这里没有描述的一些高级选项。有关 save() 的完整细节,请参阅文档。要在单个步骤中创建和保存对象,请使用 create() 方法。
保存修改对象
要保存对数据库中已有对象的更改,请使用save()。
给定一个已经保存到数据库中的 Blog 实例b5,这个例子更改了它的名称并更新了它在数据库中的记录:
>>> b5.name = 'New name'
>>> b5.save()
这将在幕后执行 UPDATE SQL 语句。Django 在显式调用 save() 之前不会访问数据库。
保存 ForeignKey 和 ManyToManyFiled 字段
更新一个 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个或多个过滤器。过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,QuerySet 等同于 SELECT 语句,过滤器是一个 LIMIT 子句,例如 WHERE 或 LIMIT。
通过使用模型的 Manager,您可以得到一个QuerySet。每个模型至少有一个 Manager,默认情况下称为 objects 。通过 model 类直接访问,如下:
>>> 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."
注意:只有通过模型类(而不是从模型实例)才能访问管理器,以强制分离 “table-level” 操作和 “record-level” 操作。
Manager 是模型查询集的主要来源。例如,Blog.objects.all() 返回一个 QuerySet,其中包含数据库中的所有 Blog 对象。
检索所有对象
从表中检索对象的最简单方法是获取所有对象。要做到这一点,对一个 Manager 使用 all() 方法:
>>> all_entries = Entry.objects.all()
all() 方法返回数据库中所有对象的 QuerySet 。
使用过滤器检索特定对象
all() 返回的 QuerySet 描述数据库表中的所有对象。不过,通常只需要选择完整对象集的子集。
要创建这样一个子集,您需要细化初始 QuerySet,添加筛选条件。优化 QuerySet 最常见的两种方法是:
filter(**kwargs)
返回一个新的 QuerySet,其中包含匹配给定查找参数的对象。
exclude(**kwargs)
返回一个新的QuerySet,其中包含与给定查找参数不匹配的对象。
查找参数(上面函数定义中的**kwargs)应该采用下面字段查找中描述的格式。
例如,要获取2006年的博客文章查询集,可以使用filter(),如下所示:
Entry.objects.filter(pub_date__year=2006)
对于默认的 Manager 类,与下面相同:
Entry.objects.all().filter(pub_date__year=2006)
Chaining filters(链接 filter)
精炼 QuerySet 的结果本身就是一个 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,其中包含从2005年1月30日到现在发布的所有条目,标题以 “What” 开头。
Filtered QuerySets are unique(过滤后查询集唯一)
每次优化 QuerySet 时,都将得到一个全新的 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)不受细化过程的影响。
QuerySets are lazy(不知道咋翻译QAQ)
QuerySet 是惰性的,创建 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 的结果不会从数据库中获取。当您这样做时,QuerySet 将通过访问数据库进行评估。有关计算确切发生时间的详细信息,请参见 When QuerySets are evaluated。
使用 get() 检索单个对象
filter() 总是会给您一个 QuerySet,即使只有一个对象与查询匹配,在本例中,它将是一个包含单个元素的 QuerySet。
如果你知道只有一个对象匹配你的查询,你可以对一个直接返回对象的 Manager 使用 get() 方法:
>>> one_entry = Entry.objects.get(pk=1)
您可以使用任何带有 get() 的查询表达式,就像使用 filter() 一样,同样,请参见下面的 Field lookups。
注意,使用 get() 和使用 filter() 处理 slice of [0] 是有区别的。如果没有匹配查询的结果,get() 将引发 DoesNotExis texception。这个异常是正在执行查询的模型类的一个属性,因此在上面的代码中,如果没有主键为1的Entry对象,Django 将引发 Entry.DoesNotExist。
类似地,如果有多个项目匹配 get() 查询,Django 也会发出申诉。在本例中,它将引发 MultipleObjectsReturned,这也是模型类本身的一个属性。
其他 QuerySet 方法
大多数时候,当您需要从数据库中查找对象时,您将使用 all()、get()、filter() 和 exclude()。然而,这还远远不够,有关所有各种 QuerySet 方法的完整列表,请参阅 QuerySet API Reference。
限制 QuerySets
使用 Python 数组切片语法的一个子集来将查询集限制为一定数量的结果。这相当于 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切片语法的“step”参数。例如,这将实际执行查询,以便返回一个列表包含前10个对象的每个的第二个对象:
>>> Entry.objects.all()[:10:2]
由于切片 queryset 工作方式的模糊性,禁止对其进行进一步过滤或排序。
要检索单个对象而不是列表(例如 SELECT foo FROM bar LIMIT 1),请使用简单的索引而不是切片。例如,在按标题的字母顺序排列文章之后将返回数据库中的第一篇文章:
>>> 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 。
查找中指定的字段必须是模型字段的名称。但是有一个例外,对于外键,您可以指定以_id为后缀的字段名。在这种情况下,value 参数应该包含外部模型的主键的原始值,例如:
>>> Entry.objects.filter(blog_id=4)
如果传递无效的关键字参数,则查找函数将引发 TypeError。
database API 支持大约24种查找类型,可以在 field lookup reference 中找到完整的引用。为了让你对可用的东西有所了解,下面是一些你可能会用到的更常见的查找:
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 title为 "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。
同样,这只触是一些常用的,更多可以在 field lookup reference 中找到完整的引用。
Lookups that span relationships(跨越关系的查找)
Django 提供了一种强大而直观的方法来“跟踪”查询中的关系,在幕后自动地为您处理 SQL JOINs,要跨越关系,只需跨模型使用相关字段的字段名,用双下划线分隔,直到您到达您想要的字段。
这个例子检索了所有博客名字为 'Beatles Blog' 的文章对象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
这个跨度只要你能达到都可以达到的(原谅我的渣翻)
它也可以逆向进行。要引用“反向”关系,只需使用模型的小写名称。
这个例子检索了所有的博客对象,其中至少有一个文章其标题包含 Lennon:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果您正在跨多个关系进行筛选,而其中一个中间模型没有满足筛选条件的值,Django 将把它当作一个空的,但是有效的对象(所有值都为NULL)。所有这些意味着不会产生错误,例如,在这个过滤器中:
Blog.objects.filter(entry__authors__name='Lennon')
(如果有相关的 Author 模型),如果没有与文章关联的 Author,则会将其视为没有附加名称,而不是由于缺少 Author 而引发错误。通常这正是你想要发生的。唯一可能让人混淆的情况是当你使用 isnull。因此:
Blog.objects.filter(entry__authors__name__isnull=True)
将返回 author 为空的 Blog objects,以及文章名为空的 Blog objects。如果你不想要后面这些对象,你可以这样写:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
Spanning multi-valued relationships(跨越多值关系)
当您基于 ManyToManyField 或 reverse ForeginKey 过滤对象时,您可能会对两种不同类型的过滤器感兴趣。考虑 Blog/Entry 关系是一对多关系。它关联一个大标题包含 "Lennon",且在2008年出版的 entry ;或者要查找这样的 blogs:它关联一个大标题包含 "Lennon" 的 entry ,同时它又关联另外一个在2008年出版的 entry 。因为有多个文章与一个博客相关联,所以这两个查询都是可能的,并且在某些情况下是有意义的。
同样的情况出现在 ManyToManyField。例如,如果一篇文章有一个名为 tags 的 ManyToManyField,我们可能想要找到链接到名为 music 和 bands 的标记的文章,或者我们可能想要一个包含名为 music 的标记和 public 状态的文章。
为了处理这两种情况,Django 使用了一种一致的方法来处理 filter() 调用。同时应用单个 filter() 调用中的所有内容来过滤出匹配所有这些需求的项。连续的 filter() 调用进一步限制了对象集,但是对于多值关系,它们适用于与主模型链接的任何对象,不一定是前面 filter() 调用选择的对象。
这听起来可能有点令人困惑,所以希望有一个例子可以阐明这一点。要检索这样的 blog:它要关联一个标题中含有 "Lennon" 并且在2008年出版的 entry ( 这个 entry 同时满足这两个条件 ),可以这样写,将检索到Blog1:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
要检索另外一种 blog:它关联一个大标题含有 "Lennon" 的 entry ,又关联一个在 2008 年出版的 entry (一个 entry 的大标题含有 Lennon,同一个或另一个 entry 是在 2008 年出版的)。可以这样写,将检索Blog1和Blog2:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假设只有一个博客同时包含 Lennon 和 2008 年的文章,但是 2008 年的文章中没有一个包含 Lennon。第一个查询不会返回任何blog,但是第二个查询将返回该blog。
在第二个示例中,第一个 filter 将 queryset 限制为所有链接到标题中有 Lennon 的文章的博客。第二个 filter 将博客集进一步限制为那些也链接到 2008 年发布的文章的博客集。第二个筛选器选择的条目可能与第一个筛选器中的条目相同,也可能不相同。我们使用每个 filter 语句过滤博客条目,而不是文章条目。
注释:
如上所述,对于跨越多值关系的查询,filter() 的行为对于 exclude() 没有等效地实现。相反,单个 exclude() 调用中的条件不一定引用相同的项。(为什么啊???)
例如,下面的查询将同时排除标题中包含 Lennon 和 2008 年发表的文章的博客(这个 entry 同时满足这两个条件),将得到Blog2:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
但是,与使用 filter() 时的行为不同,这不会根据满足这两种条件的文章来限制 blog。为了做到这一点,也就是说,要选择所有不包含 Lennon 且在 2008 年发表的所有的作品的博客,你需要做两个查询,将不会得到任何Blog:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
Filters can reference fields on the model (在 filter 中引用模型的字段)
在目前给出的示例中,我们已经构建了将模型字段的值与常量进行比较的过滤器。但是,如果您想将一个模型字段的值与同一模型上的另一个字段的值进行比较,该怎么办呢?
Django 提供了 F 表达式来允许这种比较。F() 的实例充当查询中模型字段的引用。然后可以在查询过滤器中使用这些引用来比较同一模型实例上两个不同字段的值。
例如,要查找所有评论多于 pingbacks 的博客文章列表,我们构造一个 F() 对象来引用 pingback count,并在查询中使用该 F() 对象:(ps: pingback 通知网志系统文章被引用情况的一种手段)
>>> 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 count 和 comment count 之和的所有文章,我们将进行以下查询:
>>> 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 对象(datetime.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)
The pk lookup shortcut(主键查询简洁方式)
Django提供了一个主键查询简洁方式,它代表 primary key 。在示例博客模型中,主键是 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
Escaping percent signs and underscores in LIKE statements(在 LIKE 语句中转义 '%' 和 '_')
等同于 LIKE SQL 语句( iexact、contains、icon、startswith、istartswith、endswith 和 iendswith )的字段查找将自动转义 LIKE 语句中使用的两个特殊字符 % 和 _ 。( 在 LIKE 语句中,% 表示多字符通配符,_ 表示单字符通配符。)
这就意味着我们可以直接使用这两个字符,而不用考虑他们的 SQL 语义。例如,要检索包含 % 的所有 Entry,只需将 % 当作任何其他字符一样:
>>> Entry.objects.filter(headline__contains='%')
Django 会处理转义,最终的 SQL 看起来会是这样:
SELECT ... WHERE headline LIKE '%\%%';
下划线 _ 和百分号 % 的处理方式相同,Django 都会自动转义。
Caching and QuerySets(缓存和查询)
每个 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.
When QuerySets are not cached(当查询集没有缓存时)
查询集并不总是缓存它们的结果。当只计算 queryset 的一部分时,会检查缓存,但是如果没有填充缓存,则不会缓存后续查询返回的项。具体来说,这意味着当使用 array slice 或 index 限制时 queryset 不会填充缓存。
例如,在 queryset 对象中反复获取某个 index,每次都会查询数据库:
>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again
但是,如果整个 queryset 已经被计算过,那么缓存将被检查:
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache
下面是一些其他操作的例子,这些操作将导致整个 queryset 被计算,从而填充缓存:
>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)
注解:简单地打印 queryset 将不会填充缓存。这是因为调用 __repr__() 只返回整个 queryset 的一部分。
Complex lookups with Q objects(使用Q对象进行复杂查询)
在 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 ,这是用 OR 关系连接的两个 question__startswith 查询:
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查找示例 展示了 Q 的一些可能用法。
Comparing objects(对象比较)
要比较两个对象,就和 Python 一样,使用双等号运算符:==。实际上比较的是两个 model 的主键值。
以上面的 Entry 为例,下面两个语句是等价的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
如果 model 的主键名称不是 id,也没关系。Django 会自动比较主键的值,而不管他们的名称是什么。例如,如果一个 model 的主键字段名称是 name,那么下面两个语句是等价的:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
Deleting Objects(删除对象)
删除方法就是 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})
要牢记这一点:无论在什么情况下,QuerySet 中的 delete() 方法都只使用一条 SQL 语句一次性删除所有对象,而并不是分别删除每个对象。如果你想使用在 model 中自定义的 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()
这个级联行为可以通过 ForeginKey 的 on_delete 参数定制。
要注意的是: delete() 方法是 QuerySet 上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用 Entry.objects.delete() 方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用:
Entry.objects.all().delete()
Copying model instances(复制模型实例)
虽然没有用于复制模型实例的内置方法,但是可以使用复制所有字段值轻松创建新实例。在最简单的情况下,可以将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
由于继承的工作原理,您必须将 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()
Updating multiple objects at once(一次更新多个对象 )
有时你想对 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()
对 update 的调用还可以使用 F 表达式根据模型中另一个字段的值更新一个字段。这对于基于当前值递增的计数器特别有用。例如,要增加博客中每个条目的 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'))
Related objects(对象关联)
当你定义在 model 定义关系时 ( 例如, ForeignKey, OneToOneField, 或 ManyToManyField ),model 的实例自带一套很方便的 API 以获取关联的对象。
以最上面的 models 为例,一个 Entry 对象 e 能通过 blog 属性获得相关联的 Blog 对象: e.blog。
( 在场景背后,这个功能是由 Python 的 descriptors 实现的。如果你对此感兴趣,可以了解一下。)
Django 也提供反向获取关联对象的 API,就是由从被关联的对象得到其定义关系的主对象。例如,一个 Blog 类的实例 b 对象通过 entry_set 属性得到所有相关联的 Entry 对象列表: b.entry_set.all()。
这一节所有的例子都使用本页顶部所列出的 Blog, Author 和 Entry model。
One-to-many relationships(一对多关系)
正向(Forward)
如果一个 model 有一个 ForeignKey 字段,我们只要通过使用关联 model 的名称就可以得到相关联的外键对象。
举例:
>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.
你可以设置和获得外键属性。正如你所期望的,改变外键的行为并不引发数据库操作,直到你调用 save() 方法时,才会保存到数据库。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()
如果外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。例如:
>>> e = Entry.objects.get(id=2)
>>> e.blog = None
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
在一对多关系中,第一次正向获取关联对象时,关联对象会被缓存。其后根据外键访问时这个实例,就会从缓存中获得它。例如:
>>> e = Entry.objects.get(id=2)
>>> print(e.blog) # Hits the database to retrieve the associated Blog.
>>> print(e.blog) # Doesn't hit the database; uses cached version.
要注意的是,QuerySet 的 select_related() 方法提前将所有的一对多关系放入缓存中。例如:
>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog) # Doesn't hit the database; uses cached version.
>>> print(e.blog) # Doesn't hit the database; uses cached version.
Following relationships "backward"(逆向关系)
如果 model 有一个 ForeignKey外键字段,那么外联 model 的实例可以通过访问 Manager 来得到所有相关联的源 model 的实例。默认情况下,这个 Manager 被命名为 FOO_set, 这里面的 FOO 就是源 model 的小写名称。这个 Manager 返回 QuerySets,它是可过滤和可操作的,在上面 "对象获取( Retrieving objects )" 有提及。
举例:
>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.
# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()
你可以通过在 ForeignKey() 的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Entry model 中做一下更改: blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'),那么接下来就会如我们看到这般:
>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.
# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()
Using a custom reverse manager(使用自定义反向 manager)
默认情况下,用于反向关系的 RelatedManager 是该模型的默认管理器的子类。如果你想为一个给定的查询指定一个不同的 manager,你可以使用以下语法:
from django.db import models
class Entry(models.Model):
#...
objects = models.Manager() # Default Manager
entries = EntryManager() # Custom Manager
b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()
如果 EntryManager 在其 get_queryset() 方法中执行默认过滤,那么该过滤将应用于all()调用。
当然,指定自定义反向 manager 还允许您调用它的自定义方法:
b.entry_set(manager='entries').is_published()
Additional methods to handle related objects(处理关联对象的其他方法)
除了上面“检索对象”中定义的 QuerySet 方法之外,ForeginKey Manager 还有用于处理相关对象集的其他方法。下面是它们的概要,完整的细节可以在相关对象引用中找到。
add(obj1, obj2, ...)
将特定的模型对象加入关联对象集合。
create(**kwargs)
创建一个新对象,保存它并将其放入相关的对象集中。返回新创建的对象。
remove(obj1, obj2, ...)
从相关对象集中移除指定的模型对象。
clear()
从相关对象集中移除所有对象。
set(objs)
替换相关对象集。
要分配相关集合的成员,请使用具有对象实例迭代的 set() 方法。例如,如果 e1 和 e2 是 Entry 实例:
b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])
如果 clear() 方法可用,在所有可迭代对象(这个例子中是 list)被添加进该集合之前任何已经存在的对象将被从 entry_set 中移除。如果 clear() 方法不可用,iterable 中的所有对象将不会删除任何现有的元素。
本节中描述的每个“反向”操作都会对数据库产生直接影响。每一次添加、创建和删除都会立即自动保存到数据库中。
Many-to-many relationships(多对多关系)
在多对多关系的任何一方都可以使用 API 访问相关联的另一方。多对多的 API 用起来和上面提到的 "逆向" 一对多关系关系非常相像。
一个不同之处在于属性命名:定义 ManyToManyField 的模型使用该字段本身的属性名,而“反向”模型使用原始模型的小写模型名,加上 “_set” ( 就像反向一对多关系一样 )。
例如:(此处是在 Entry 中定义的 ManyToManyField 关系 )
e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')
a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.
与 ForeignKey 一样, ManyToManyField 也可以指定 related_name。在上面的例子中,如果 Entry 中的 ManyToManyField 指定 related_name='entries',那么接下来每个 Author 实例的 entry_set 属性都被 entries 所代替。
与一对多关系的另一个区别是,除了模型实例之外,多对多关系上的 add()、set() 和 remove() 方法还接受主键值。例如,如果 e1 和 e2 是 Entry 实例,那么这些 set() 调用的工作原理是相同的:
a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])
One-to-one relationships(一对一关系)
一对一关系很像多对一关系。如果您在模型上定义了一个 OneToOneField,那么该模型的实例将可以通过模型的一个简单属性访问相关对象。
举个栗子:
class EntryDetail(models.Model):
entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
details = models.TextField()
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.
不同之处在于“反向”查询。一对一关系中的相关模型也可以访问 Manager 对象,但该 Manager 表示单个对象,而不是对象的集合:
e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object
如果没有为这个关系分配对象,Django 将引发一个 DoesNotExist exception。
实例可以像分配正向关系一样分配给反向关系:
e.entrydetail = ed
How are the backward relationships possible? (关系中的反向连接是如何做到的?)
其他对象关系的映射 ( ORM ) 需要你在关联双方都定义关系。而 Django 的开发者则认为这违背了 DRY 原则 ( Don't Repeat Yourself ),所以 Django 只需要你在一方定义关系即可。
但仅由一个 model 类并不能知道其他 model 类是如何与它关联的,除非是其他 model 也被载入,那么这是如何办到的?
答案就在 app registry 中。Django 启动时,它导入 INSTALLED_APPS 中列出的每个 application,然后导入每个 application 中的 models 模块。每当创建一个新的模型类时,Django 都会向任何相关的模型添加反向连接。如果还没有导入相关模型,Django 将跟踪这些关系,并在最终导入相关模型时添加它们。
因此,在 INSTALLED_APPS 中列出的 applications 中定义所使用的所有模型尤为重要。否则,反向关系可能无法正常工作。
Queries over related objects(在关联对象上查询)
包含关联对象的查询与包含普通字段值的查询都遵循相同的规则。为某个查询指定某个值的时候,你可以使用一个类实例,也可以使用对象的主键值。
例如,如果你有一个 Blog 对象 b ,它的 id=5, 下面三个查询是一样的:
Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly
直接使用 SQL
如果您发现自己需要编写一个对于 Django 的数据库映射程序来说太复杂而无法处理的 SQL 查询,那么您可以重新使用手工编写 SQL。Django 有两个用于编写原始 SQL 查询的选项;参见 执行原始SQL查询。
最后,要注意的是,Django 的数据操作层仅仅是访问数据库的一个接口。你可以用其他的工具,编程语言,数据库框架来访问数据库。对你的数据库而言,没什么是非用 Django 不可的。