Django4 中文入门教程 Django4.0 执行查询-关联对象

2024-02-25 开发教程 Django4 中文入门教程 匿名 6

当你在模型中定义了关联关系(如 ​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