简体   繁体   English

如何使递归多对多关系与 Django 对称

[英]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 .如果billted是朋友,我希望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 .这将使billted成为朋友,并使tedbill成为朋友。 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() .如果您想为中间模型上的额外字段设置值,在我的情况下startend ,请使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM