5
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

我希望能够获得 Food_Tags 之间的关系,例如:

>>> 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()
[]

但是对于肉,related_tags 是空的。我意识到这与 'symmetrical=False' 参数有关,但我如何设置模型以使 'meat.related_tags.all()' 返回所有相关的 Food_Tags?

4

5 回答 5

6

文档中所述

因此,在 Django 中,(还?)不可能与额外的字段建立对称的、递归的多对多关系。这是一个“选择两个”的交易。

于 2010-11-09T15:53:29.470 回答
2

我发现Charles Leifer提出的这种方法似乎是克服这个 Django 限制的好方法。

于 2015-04-13T16:25:19.070 回答
1

由于您没有明确说它们需要不对称,所以我建议的第一件事是设置symmetrical=True. 正如您所描述的,这将导致关系以两种方式工作。through正如 eternicode 指出的那样,当您使用M2M 关系模型时,您不能这样做。如果您负担得起不使用该through模型的费用,则可以设置symmetrical=True以获得您所描述的行为。

但是,如果它们需要保持不对称,您可以将关键字参数添加related_name="sources"related_tags字段(您可能需要考虑重命名以targets使事情更清晰),然后使用meat.sources.all().

于 2010-11-09T07:48:44.180 回答
0

要创建对称关系,您有两种选择:

1)创建两个Tag_Relation对象 - 一个steak作为源,另一个steak作为目标:

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

2)向Food_Tag模型添加另一个ManyToManyField:

class Food_Tag(models.Model):
    name = models.CharField(max_length=200)
    related_source_tags = models.ManyToManyField('self', blank=True, symmetrical=False, through='Tag_Relation', through_fields=('source', 'target'))
    related_target_tags = models.ManyToManyField('self', blank=True, symmetrical=False, through='Tag_Relation', through_fields=('target', 'source'))

class Tag_Relation(models.Model):
    source = models.ForeignKey(Food_Tag, related_name='source_set')
    target = models.ForeignKey(Food_Tag, related_name='target_set')

作为说明,我会尝试使用比您的模型字段更具描述source性的内容。target

于 2015-05-04T17:32:09.317 回答
0

这个问题的最佳解决方案(经过多次调查)是在save()调用时手动创建对称数据库记录。当然,这会导致 DB 数据冗余,因为您创建了 2 条记录而不是 1 条记录。在您的示例中,保存后Tag_Relation(source=source, target=target, ...)您应该像这样保存反向关系Tag_Relation(source=target, target=source, ...)

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);
    has_a = models.BooleanField(default=False);

    class Meta:
        unique_together = ('source', 'target')

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        # create/update reverse relation using pure DB-level functions
        # we cannot just save() reverse relation because there will be a recursion
        reverse = Tag_Relation.objects.filter(source=self.target, target=self.source)
        if reverse.exists():
            reverse.update(is_a=self.is_a, has_a=self.has_a)
        else:
            Tag_Relation.objects.bulk_create([
                Tag_Relation(source=self.target, target=self.source, is_a=self.is_a, has_a=self.has_a)
            ])

此实现的唯一缺点是重复Tag_Relation条目,但除此之外一切正常,您甚至可以在 InlineAdmin 中使用 Tag_Relation。

更新 不要忘记定义delete将删除反向关系的方法。

于 2016-10-14T11:37:31.670 回答