Skip to main content
 首页 » 编程设计

python之Django 模型字段实际上是对相关模型中字段的引用

2024年12月31日25qlqwjy

如果这个问题难以理解,请提前致歉——我不确定如何表达。基本上,我试图在 Django 模型中创建一种“伪字段”,它的工作方式与任何其他 Django 字段完全一样,只是它实际上是对相关模型上字段的引用。

举个例子,假设我经营一家狗旅馆。在我的旅馆里,我有房间,每个房间都分配给一位顾客并包含一只狗。

class Customer(Model): 
    name = models.CharField(max_length=256, null=False) 
 
class Dog(Model): 
    name = models.CharField(max_length=256, null=False) 
    customer = model.ForeignKey(Customer, null=True, on_delete=models.CASCADE) # The dog's owner 
 
class Room(Model): 
    customer = model.ForeignKey(Owner, null=True, on_delete=models.SET_NULL) 
    dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL) 

数据库中对应的表是这样的

CUSTOMER: 
 
| ID | NAME       | 
___________________ 
| 01 | John Smith | 
| 02 | Jane Doe   | 
 
 
DOG: 
 
| ID | NAME  | CUSTOMER_ID | 
____________________________ 
| 01 | Rover |     01      | 
| 02 | Fido  |     01      | 
| 03 | Spot  |     02      | 
 
 
ROOM: 
 
| ID | DOG_ID | CUSTOMER_ID | 
_____________________________ 
| 01 |   01   |      01     | 
| 02 |   03   |      02     | 

因此,我的老板注意到我们在数据库中存储了冗余数据:rooms 表实际上不需要有自己的客户 ID 列:客户始终是住家狗的主人,每个房间只包含一个dog,每只狗都有一个主人,所以我们总是可以通过去狗表并查找主人来将客户分配到房间。我被要求以对代码库的其余部分“完全透明”的方式从房间表中删除客户 ID 列。

首先,我可以使用自定义 getter 将 customer 转换为 Room 类中的 @property:

class Room(Model): 
    dog = model.ForeignKey(Dog, null=True, on_delete=models.SET_NULL) 
 
    @property 
    def customer(self): 
        return self.dog.customer 

所以现在在我们执行类似 c = room.customer 的代码中,它会继续工作。问题是,代码库中充满了像 room__customer 这样的 Django 字段查询,当我将 customer 变成 @property 时,它们都停止工作了房间。我可以将它们全部更改为 room__dog__customer,这工作正常,但这样更改就不会“完全透明”。

我做了一些研究,并尝试实现自定义管理器并为 Rooms 的查询集添加注释:

class RoomManager(models.Manager): 
    def get_queryset(self): 
        return super().get_queryset().annotate(customer=F('dog__customer')) 

当我这样做时,我可以在查询中使用 room__customer,但不能使用 room__customer__name,我想是因为 F() 正在返回主要的键值而不是模型实例 ( https://docs.djangoproject.com/en/2.1/ref/models/expressions/#using-f-with-annotations )。

所以我的问题是,是否有一种我不知道的方法可以让这项工作发挥作用?一种使 Room 表现得像与 Customer 有直接外键关系的方法,而无需将 customer_id 存储在 rooms 表中?

请您参考如下方法:

我猜你的经理所说的“完全透明”只是一种表达“无论你改变什么都不应该搞砸其他业务逻辑”的说法。换句话说,应用程序应该像今天一样继续工作。我非常怀疑他是否关心你编辑了多少文件(尽管他可能是一个微观管理者,在这种情况下......我很抱歉)。

也就是说,我认为将 room__customer 更改为 room__dog__customer 的解决方案是最佳行动方案。这一变化使其他 Django 开发人员可以清楚地知道发生了什么(你正在处理关系),这是一个相当简单的变化。是的,您可能最终不得不接触大量文件,但这就是您处理后端模式的生活。

然而,对于此更改,您需要考虑的一件事是潜在的性能影响。您可能需要对查询引入 select_related 调用,以确保正确连接表。否则,执行 room -> dog -> customer 查找可能会变得很昂贵(每次查找需要额外的数据库往返;这实际上是一个循环)。

祝你好运!