【问题标题】:How to make recursive ManyToManyField relationships that have extra fields symmetrical in Django?如何在Django中建立具有对称额外字段的递归ManyToManyField关系?
【发布时间】:2011-05-06 23:29:07
【问题描述】:
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?

【问题讨论】:

    标签: python django manytomanyfield


    【解决方案1】:

    the docs中所述:

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

    【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        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) 将另一个 ManyToManyField 添加到 Food_Tag 模型:

        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')
        

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

        【讨论】:

          【解决方案5】:

          这个问题的最佳解决方案(经过多次调查)是在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 方法,这将删除反向关系。

          【讨论】:

          • 实际上,Django 就是以这种方式实现对称的。它在数据库中创建另一条记录。
          猜你喜欢
          • 2018-02-19
          • 2013-06-05
          • 2018-10-04
          • 1970-01-01
          • 1970-01-01
          • 2014-01-11
          • 2014-12-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多