如何使递归多对多关系与 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)])

如果billted 是朋友,我希望bill.friends.all() 包括tedted.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)。这将使billted 成为朋友,tedbill 成为朋友。如果您想为中间模型上的额外字段设置值,在我的情况下为startend,请为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 对称的主要内容,如果未能解决你的问题,请参考以下文章

用于递归多对多的 Django Admin 内联

在 Django 中过滤第二级多对多关系

django之间的区别 - 一对一,多对一,多对多

如何以 django 形式过滤多对多字段

如何在 django 中处理未保存的多对多关系?

Django:如何聚合/注释多对多关系?