如何在Django中构建对称的递归ManyToManyField关系并包含额外字段?

5 投票
5 回答
3713 浏览
提问于 2025-04-16 06:45
class Food_Tag(models.Model):
    name = models.CharField(max_length=200)
    related_tags = models.ManyToManyField('self', blank=True, symmetrical=False, through='Tag_Relation')

    def __unicode__(self):
     return self.name

class Tag_Relation(models.Model):
    source = models.ForeignKey(Food_Tag, related_name='source_set')
    target = models.ForeignKey(Food_Tag, related_name='target_set')
    is_a = models.BooleanField(default=False); # True if source is a target
    has_a = models.BooleanField(default=False); # True if source has a target

我想获取食物标签之间的关系,比如:

>>> steak = Food_Tag.objects.create(name="steak")
>>> meat = Food_Tag.objects.create(name="meat")
>>> r = Tag_Relation(source=steak, target=meat, is_a=True)
>>> r.save()
>>> steak.related_tags.all()
[<Food_Tag: meat>]
>>> meat.related_tags.all()
[]

但是肉类的相关标签是空的。我意识到这和'symmetrical=False'这个参数有关,但我该如何设置模型,让'meat.related_tags.all()'返回所有相关的食物标签呢?

5 个回答

1

因为你没有明确说它们需要是不对称的,所以我首先建议你设置 symmetrical=True。这样的话,关系就会像你描述的那样双向工作。

不过,正如 eternicode 指出的那样,当你使用 through 模型来处理多对多关系时,是不能这样做的。如果你可以不使用 through 模型,那么设置 symmetrical=True 就能得到你想要的效果。

如果它们需要保持不对称的话,你可以在 related_tags 字段中添加一个关键字参数 related_name="sources"(你可能还想考虑把它改名为 targets,这样会更清楚),然后可以通过 meat.sources.all() 来访问相关的标签。

2

我发现了一个由 Charles Leifer 提出的办法,这个方法看起来是个不错的解决方案,可以克服Django的一些限制。

6

正如在文档中提到的:

  • 当你想要在一个模型中定义一个自我关联的多对多关系,并且使用一个中介模型时,你必须使用symmetrical=False(具体可以查看模型字段参考)。

所以,目前在Django中,无法实现带有额外字段的对称递归多对多关系。这就像是“选两个”的情况。

撰写回答