Django外键访问是如何工作的

12 投票
3 回答
12833 浏览
提问于 2025-04-18 14:17

假设我有一个这样的模型。

class Job(models.Model):
    client = models.ForeignKey(Contacts, null=True)

然后假设我有一个工作 j。我知道可以这样访问与 j 相关的客户:

 j.client

但还有另外一种方式:

 j.client_id

所以我的问题是,访问 j.client 是怎么回事?

Django 是不是存储了 client__id,当我调用 j.client 时,它会查询来找到正确的对象?

还是说对象的引用是存储在 j 里的,访问 client__id 是从 Contact 对象中获取 id?

我查了一下源代码,但没找到我想要的答案。

3 个回答

3

j.client 会给你一个 models.Model 对象。你可以像这样访问它的属性...

client = j.client

id = client.id
name = client.name

但是,j.client__id 这个字段是不存在的。你应该使用 j.client.id 来获取 id 字段。虽然你可以用 j.client__id 来进行过滤等操作。

所以,

id = j.client.id # good
id = j.client__id # bad

还有

job = Job.objects.get(client__id=1) # good
job = Job.objects.get(client.id=1) # bad
11

你可能在说的是 clientclient_id(单下划线)。

client_id 是一个普通的(整数)属性。它是保存到数据库中的外键。即使你指定了 ForeignKeyclient,在数据库中你只会看到 client_id 这一列。

client 属性是一个对象描述符实例。它是一个特殊的类,重写了 __get____set__ 方法,所以设置和访问这个属性时会调用这个类的方法。这就是让你能够访问实际相关模型实例的“魔法”。如果 client_id 属性对应的模型实例还没有加载,__get__ 会根据 client_id 从数据库中获取正确的模型实例。而 __set__ 则会把 client_id 属性设置为相关对象的主键,这样 client_id 始终是最新的。

注意,这个属性在查询查找中也可以使用,非常方便。例如,如果你只有一个外部对象的主键,而没有模型实例本身,以下查询看起来非常相似:

job = Job.objects.filter(client__id=pk)
job = Job.objects.filter(client_id=pk)

不过,第一个查询是在访问相关对象的一个属性(双下划线),并执行了一个 OUTER JOIN。而第二个查询只访问一个本地属性,因此不需要执行 OUTER JOIN 语句,从而提高了性能。

10

这个在文档里有解释:
https://docs.djangoproject.com/en/dev/ref/models/fields/#database-representation

在数据库里,只有一个叫 client_id 的字段(注意是一个下划线)。

在模型实例中,你会看到一个 client 属性,当你访问这个属性时,Django 会从数据库中加载相关的对象,并把它当作另一个模型实例来使用。

你还会有一个 client_id 属性(注意是一个下划线),它存储的是相关对象的主键值,也就是在数据库字段中保存的值。

在进行ORM查询时,你可以使用 client__id(两个下划线)这种语法来查找相关模型的字段,比如如果 Client 模型有一个 name 字段,你也可以用 client__name 来查询。这会变成一个跨两个模型的SQL JOIN查询。

例如:

Job.objects.get(client__id=1)
Job.objects.filter(client__name='John')

client = Client.objects.get(pk=1)
Job.objects.get(client=client)

撰写回答