【发布时间】:2021-08-05 08:32:08
【问题描述】:
所以我有一些看起来像这样的模型
class Person(BaseModel):
name = models.CharField(max_length=50)
# other fields declared here...
friends = models.ManyToManyField(
to="self",
through="Friendship",
related_name="friends_to",
symmetrical=True
)
class Friendship(BaseModel):
friend_from = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name="friendships_from")
friend_to = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name="friendships_to")
state = models.CharField(
max_length=20, choices=FriendshipState.choices, default=FriendshipState.pending)
所以基本上,我正在尝试模拟一种类似 Facebook 的朋友情况,其中有不同的人,并且可以要求任何其他人成为朋友。这种关系通过最后一个模型Friendship 来表达。
到目前为止一切顺利。 但我想避免三种情况:
-
- 友谊不能在
friend_from和friend_to字段中包含同一个人
- 友谊不能在
-
- 对于一组两个朋友,只允许一个
Friendship。
- 对于一组两个朋友,只允许一个
我最接近的方法是将其添加到 Friendship 模型下:
class Meta:
constraints = [
constraints.UniqueConstraint(
fields=['friend_from', 'friend_to'], name="unique_friendship_reverse"
),
models.CheckConstraint(
name="prevent_self_follow",
check=~models.Q(friend_from=models.F("friend_to")),
)
]
这完全解决了情况1,避免某人与自己成为朋友,使用CheckConstraint。
并部分解决了第 2 种情况,因为它避免了两个 Friendships 像这样:
p1 = Person.objects.create(name="Foo")
p2 = Person.objects.create(name="Bar")
Friendship.objects.create(friend_from=p1, friend_to=p2) # This one gets created OK
Friendship.objects.create(friend_from=p1, friend_to=p2) # This one fails and raises an IntegrityError, which is perfect
现在有一种情况需要避免,但这种情况仍然可能发生:
Friendship.objects.create(friend_from=p1, friend_to=p2) # This one gets created OK
Friendship.objects.create(friend_from=p2, friend_to=p1) # This one won't fail, but I'd want to
如何让UniqueConstraint 在这个“两个方向”上工作?或者我该如何添加另一个约束来涵盖这种情况?
当然,我可以覆盖模型的 save 方法或以其他方式强制执行此操作,但我很好奇这应该如何在数据库级别完成。
【问题讨论】:
-
这看起来与stackoverflow.com/questions/67540575/… 非常相似?无论如何,当您设置
symmetrical=True并自己创建反向副本时,您可以做 Django 所做的事情。 -
@AbdulAzizBarkat 嘿,是的,这几乎是完全相同的问题。老实说,除了在自定义
save方法中强制执行约束之外,我没有想过自己创建反向副本的可能性,这可能是另一种选择。话虽如此,我仍然很好奇,因为我几乎可以肯定它可以通过UniqueConstraint和Qoperator 来完成,我只是没有找到自己的方法。 -
创建反向副本是这里的最佳选择(实际上这也是 Django 正在做的事情)。这是因为如果关系是对称的,那么在尝试获取相关对象 (
person.friends.all()) 或进行查询时,我们希望所有对象都以一种或另一种方式引用,所以我们需要使这些复杂化通过添加更多条件等进行查询。虽然反向复制可能有些多余,但在检索实例的效率方面仍然可以接受。 -
你想要的虽然不能在当前版本的 Django 中完成,但在未来的某些版本中
UniqueConstraints (Django docs) 将支持表达式,因此你可以编写一些表达式将两个字段与一些分隔符连接起来并以某种顺序有效地实现您想要的。
标签: python-3.x django django-models unique-constraint