如果这个问题难以理解,请提前致歉——我不确定如何表达。基本上,我试图在 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 查找可能会变得很昂贵(每次查找需要额外的数据库往返;这实际上是一个循环)。
祝你好运!
