如何使递归多对多关系与 Django 对称
Posted
技术标签:
【中文标题】如何使递归多对多关系与 Django 对称【英文标题】:How to make a recursive ManyToMany relationship symmetrical with Django 【发布时间】:2021-01-28 10:49:31 【问题描述】:I have read the Django Docs regarding symmetrical=True
. 我也读过this question asking the same question for an older version of Django,但以下代码不像 Django 文档描述的那样工作。
# people.models
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=255)
friends = models.ManyToManyField("self",
through='Friendship',
through_fields=('personA', 'personB'),
symmetrical=True,
)
def __str__(self):
return self.name
class Friendship(models.Model):
personA = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='personA')
personB = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='personB')
start = models.DateField(null=True, blank=True)
end = models.DateField(null=True, blank=True)
def __str__(self):
return ' and '.join([str(self.personA), str(self.personB)])
如果bill
和ted
是朋友,我希望bill.friends.all()
包括ted
,ted.friends.all()
包括bill
。这不是发生的事情。 bill
的查询包含ted
,但ted
的查询不包含账单。
>>> from people.models import Person, Friendship
>>> bill = Person(name='bill')
>>> bill.save()
>>> ted = Person(name='ted')
>>> ted.save()
>>> bill_and_ted = Friendship(personA=bill, personB=ted)
>>> bill_and_ted.save()
>>> bill.friends.all()
<QuerySet [<Person: ted>]>
>>> ted.friends.all()
<QuerySet []>
>>> ted.refresh_from_db()
>>> ted.friends.all()
<QuerySet []>
>>> ted = Person.objects.get(name='ted')
>>> ted.friends.all()
<QuerySet []>
这是一个错误还是我误解了什么?
编辑:更新代码以显示行为与 through_fields
设置相同。
【问题讨论】:
【参考方案1】:添加关系的正确方法是bill.friends.add(ted)
。这将使bill
与ted
成为朋友,ted
与bill
成为朋友。如果您想为中间模型上的额外字段设置值,在我的情况下为start
和end
,请为add()
使用through_defaults
参数。
...
>>> bill.friends.add(ted, through_defaults='start': datetime.now()
在某些情况下,您希望 bill
-> ted
之间的关系在中间模型上具有与 ted
-> bill
不同的值。例如,bill
认为 ted
很“酷”,当他们第一次见面时,ted
认为 bill
是“卑鄙的”。在这种情况下,您将需要辅助函数。
# people.models
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=255)
friends = models.ManyToManyField("self", through='Friendship')
def __str__(self):
return self.name
def add_friendship(self, person, impressionA, impressionB, recursive=True):
self.friends.add(person, through_defaults='personA_impression': impressionA, 'personB_impression': impressionB)
if recursive:
person.add_friendship(self, impressionB, impressionA, False)
class Friendship(models.Model):
personA = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='a')
personB = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='b')
personA_impression = models.CharField(max_length=255)
personB_impression = models.CharField(max_length=255)
def __str__(self):
return ' and '.join([str(self.personA), str(self.personB)])
调用 bill.friends.add(ted, through_defaults="personA_impression": "cool", "personB_impression": "mean")
会产生以下结果:
...
>>> bill_and_ted = Friendship.objects.get(personA=bill)
>>> ted_and_bill = Friendship.objects.get(personA=ted)
>>> bill_and_ted.personA_impression
"cool" # bill thinks ted is cool
>>> bill_and_ted.personB_impression
"mean" # ted thinks bill is mean
>>> ted_and_bill.personA_impression
"cool" # ted thinks bill is cool. This contradicts the bill_and_ted intermediate model
使用add_friendship
函数为字段分配正确的值。
【讨论】:
【参考方案2】:来自the documentation:
当您在参与多对多关系的任何(或什至两者)模型的中间模型上有多个外键时,您必须指定
through_fields
。这也适用于recursive relationships,当使用中间模型并且模型有两个以上的外键时,或者您想明确指定应该使用哪两个 Django。
【讨论】:
行为与through_fields
设置相同。我已经更新了我的问题以上是关于如何使递归多对多关系与 Django 对称的主要内容,如果未能解决你的问题,请参考以下文章