如何使递归多对多关系与 Django 对称
How to make a recursive ManyToMany relationship symmetrical with Django
I have read the Django Docs regarding symmetrical=True
. I have also read 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
设置相同。
When you have more than one foreign key on an intermediary model to any (or even both) of the models participating in a many-to-many relationship, you must specify through_fields
. This also applies to recursive relationships when an intermediary model is used and there are more than two foreign keys to the model, or you want to explicitly specify which two Django should use.
添加关系的正确方法是bill.friends.add(ted)
。这将使 bill
成为 ted
的朋友,并使 ted
成为 bill
的朋友。如果你想为中间模型的额外字段设置值,在我的例子中 start
和 end
,使用 through_defaults
参数 add()
.
...
>>> 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
函数为字段分配适当的值。
I have read the Django Docs regarding symmetrical=True
. I have also read 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
设置相同。
When you have more than one foreign key on an intermediary model to any (or even both) of the models participating in a many-to-many relationship, you must specify
through_fields
. This also applies to recursive relationships when an intermediary model is used and there are more than two foreign keys to the model, or you want to explicitly specify which two Django should use.
添加关系的正确方法是bill.friends.add(ted)
。这将使 bill
成为 ted
的朋友,并使 ted
成为 bill
的朋友。如果你想为中间模型的额外字段设置值,在我的例子中 start
和 end
,使用 through_defaults
参数 add()
.
...
>>> 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
函数为字段分配适当的值。