[英]How to make a recursive ManyToMany relationship symmetrical with Django
I have read the Django Docs regarding symmetrical=True
. 我已经阅读了关于 Django Docs 的
symmetrical=True
。 I have also read this question asking the same question for an older version of Django but the following code is not working as the Django docs describe.我也读过这个问题,对旧版本的 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)])
If bill
and ted
are friends, I would expect bill.friends.all()
to included ted
, and ted.friends.all()
to include bill
.如果
bill
和ted
是朋友,我希望bill.friends.all()
包含ted
,而ted.friends.all()
包含bill
。 This is not what happens.这不是发生的事情。
bill
's query includes ted
, but ted
's query does not include bill. bill
的查询包括ted
,但ted
的查询不包括 bill 。
>>> 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 []>
Is this a bug or am I misunderstanding something?这是一个错误还是我误解了什么?
EDIT: Updated code to show the behavior is the same with through_fields
set.编辑:更新代码以显示与
through_fields
设置相同的行为。
The proper way to add the relationship is bill.friends.add(ted)
.添加关系的正确方法是
bill.friends.add(ted)
。 This will make bill
friends with ted
and ted
friends with bill
.这将使
bill
与ted
成为朋友,并使ted
与bill
成为朋友。 If you want to set values for the extra fields on the intermediate model, in my case start
and end
, use the through_defaults
argument for add()
.如果您想为中间模型上的额外字段设置值,在我的情况下
start
和end
,请使用add()
的through_defaults
参数。
...
>>> bill.friends.add(ted, through_defaults={'start': datetime.now()}
There are cases where you want the relationship between bill
-> ted
to have different values on the intermediate model than ted
-> bill
.在某些情况下,您希望
bill
-> ted
之间的关系在中间模型上具有与ted
-> bill
不同的值。 For example, bill
thinks ted
is "cool", when they first meet, but ted
thinks bill
is "mean".例如,当他们第一次见面时,
bill
认为ted
很“酷”,但ted
认为bill
很“卑鄙”。 You'll need helper function in that case.在这种情况下,您将需要辅助功能。
# 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)])
Calling bill.friends.add(ted, through_defaults={"personA_impression": "cool", "personB_impression": "mean"})
results in the following:调用
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
Using the add_friendship
function assigns the proper values to the fields.使用
add_friendship
函数为字段分配适当的值。
From the documentation :从文档:
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
.当您在一个中间模型上有多个外键到参与多对多关系的任何(或什至两个)模型时,您必须指定
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.当使用中间模型并且模型有两个以上的外键时,这也适用于递归关系,或者您想明确指定应该使用哪两个 Django。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.