当你在模型中定义了关联关系(如 ForeignKey
, OneToOneField
或 ManyToManyField
),该模型的实例将会自动获取一套 API,能快捷地访问关联对象。
拿本文开始的模型做例子,一个 Entry
对象 e
通过 blog
属性获取其关联的 Blog
对象: e.blog
。
Django 也提供了从关联关系 另一边 访问的 API —— 从被关联模型到定义关联关系的模型的连接。例如,一个 Blog
对象 b
能通过 entry_set
属性 b.entry_set.all()
访问包含所有关联 Entry
对象的列表。
本章节中的所有例子都是用了本页开头定义的 Blog
, Author
和 Entry
模型。
若模型有个 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.
若模型有 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()
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()
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()
方法,添加新对象时不会删除旧对象。
多对多关联的两端均自动获取访问另一端的 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])
一对一关联与多对一关联非常类似。若在模型中定义了 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
其它对象关联映射实现要求你在两边都定义关联关系。而 Django 开发者坚信这违反了 DRY
原则(不要自我重复),故 Django 仅要求你在一端定义关联关系。
但这是如何实现的呢,给你一个模型类,模型类并不知道是否有其它模型类关联它,直到其它模型类被加载?
答案在于 应用注册。 Django 启动时,它会导入 INSTALLED_APPS
列出的每个应用,和每个应用中的 model
模块。无论何时创建了一个新模型类,Django 为每个关联模型添加反向关联。若被关联的模型未被导入,Django 会持续追踪这些关联,并在关联模型被导入时添加关联关系。
出于这个原因,包含你所使用的所有模型的应用必须列在 INSTALLED_APPS
中。否则,反向关联可能不会正常工作。
涉及关联对象的查询与涉及普通字段的查询遵守同样的规则。未查询条件指定值时,你可以使用对象实例,或该实例的主键。
例如,若有个博客对象 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