- 0x00 -- 关联对象
- 0x01 -- 一对多关联
- 0x01 -- 1 -- 正向访问
- 0x01 -- 2 -- 反相关联
- 0x01 -- 3 -- 使用自定义反相管理器
- 0x01 -- 4 -- 管理关联对象的额外方法
- 0x02 -- 多对多关联
- 0x03 -- 一对一关联
- 0x04 -- 反向关联如何实现
- 0x05 -- 查询关联对象
- 0x06 -- 回归原生SQL
0x00 – 关联对象
当你在模型中定义了关联关系(如 ForeignKey, OneToOneField, 或 ManyToManyField),该模型的实例将会自动获取一套 API,能快捷地访问关联对象。
拿本文开始的模型做例子,一个 Entry 对象 e 通过 blog 属性获取其关联的 Blog 对象: e.blog。
(在幕后,这个函数是由 Python descriptors 实现的。这玩意一般不会麻烦你,但是我们为你指出了注意点。)
Django 也提供了从关联关系 另一边 访问的 API —— 从被关联模型到定义关联关系的模型的连接。例如,一个 Blog 对象 b 能通过 entry_set 属性 b.entry_set.all() 访问包含所有关联 Entry 对象的列表。
本章节中的所有例子都是用了本页开头定义的 Blog, Author 和 Entry 模型。
0x01 – 一对多关联 0x01 – 1 – 正向访问
若模型有个 ForeignKey,该模型的实例能通过其属性访问关联(外部的)对象。
举例:
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object.
你可以通过 foreign-key 属性获取和设置值。如你所想,对外键的修改直到你调用 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.
注意 select_related() QuerySet 方法会预先用所有一对多关联对象填充缓存。例子:
>>> 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.
0x01 – 2 – 反相关联
若模型有 ForeignKey,外键关联的模型实例将能访问 Manager,后者会返回第一个模型的所有实例。默认情况下,该 Manager 名为 FOO_set, FOO 即源模型名的小写形式。 Manager 返回 QuerySets,后者能以 “检索对象” 章节介绍的方式进行筛选和操作。
举例:
>>> 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 模型为 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()
0x01 – 3 – 使用自定义反相管理器
RelatedManager 反向关联的默认实现是该模型 默认管理器 一个实例。若你想为某个查询指定一个不同的管理器,可以使用如下语法:
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() 调用中。
指定一个自定义反向管理也允许你调用模型自定义方法:
b.entry_set(manager='entries').is_published()
0x01 – 4 – 管理关联对象的额外方法
ForeignKey Manager 还有方法能处理关联对象集合。除了上面的 “检索对象” 中定义的 QuerySet 方法以外,以下是每项的简要介绍,而完整的细节能在 关联对象参考 中找到。
-
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() 方法, entry_set 中所有旧对象会在将可迭代集合(本例中是个列表)中的对象加入其中之前被删除。若 不能 使用 clear() 方法,添加新对象时不会删除旧对象。
本节介绍的所有 “反向” 操作对数据库都是立刻生效的。每次的增加,创建和删除都是及时自动地保存至数据库。
0x02 – 多对多关联
多对多关联的两端均自动获取访问另一端的 API。该 API 的工作方式类似上面的 “反向” 一对多关联。
不同点在为属性命名上:定义了 ManyToManyField 的模型使用字段名作为属性名,而 “反向” 模型使用源模型名的小写形式,加上 '_set' (就像反向一对多关联一样)。
一个更易理解的例子:
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 实例会拥有一个 entries 属性,而不是 entry_set。
另一个与一对多关联不同的地方是,除了模型实例以外,多对多关联中的 add(), set() 和 remove() 方法能接收主键值。例如,若 e 和 e2 是 Entry 的实例,以下两种 set() 调用结果一致:
a = Author.objects.get(id=5) a.entry_set.set([e1, e2]) a.entry_set.set([e1.pk, e2.pk])
0x03 – 一对一关联
一对一关联与多对一关联非常类似。若在模型中定义了 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 异常。
实例能通过为正向关联指定关联对象一样的方式指定给反向关联:
e.entrydetail = ed
0x04 – 反向关联如何实现
其它对象关联映射实现要求你在两边都定义关联关系。而 Django 开发者坚信这违反了 DRY 原则(不要自我重复),故 Django 仅要求你在一端定义关联关系。
但这是如何实现的呢,给你一个模型类,模型类并不知道是否有其它模型类关联它,直到其它模型类被加载?
答案位于 应用注册。 Django 启动时,它会导入 INSTALLED_APPS 列出的每个应用,和每个应用中的 model 模块。无论何时创建了一个新模型类,Django 为每个关联模型添加反向关联。若被关联的模型未被导入,Django 会持续追踪这些关联,并在关联模型被导入时添加关联关系。
出于这个原因,包含你所使用的所有模型的应用必须列在 INSTALLED_APPS 中。否则,反向关联可能不会正常工作。
0x05 – 查询关联对象
涉及关联对象的查询与涉及普通字段的查询遵守同样的规则。未查询条件指定值时,你可以使用对象实例,或该实例的主键。
例如,若有个博客对象 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
0x06 – 回归原生SQL
若你发现需要编写的 SQL 查询语句太过复杂,以至于 Django 的数据库映射无法处理,你可以回归手动编写 SQL。Django 针对编写原生 SQL 有几个选项;参考 执行原生 SQL 查询。
最后,Django 数据库层只是一种访问数据库的接口,理解这点非常重要。你也可以通过其它工具,编程语言或数据库框架访问数据库;Django 并没有对数据库数据库做啥独有的操作。
2021年10月11日



