如何在 Django 中使具有额外字段对称的递归 ManyToManyField 关系?

发布于 2024-09-30 21:37:22 字数 1064 浏览 2 评论 0原文

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 对于肉来说是空的。我意识到这与 'symmetry=False' 参数有关,但是如何设置模型以使 'meat.lated_tags.all()' 返回所有相关的 Food_Tags?

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

I want to be able to get the relations between Food_Tags like:

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

but related_tags is empty for meat. I realize this has to do with the 'symmetrical=False' argument, but how can I set up the model such that 'meat.related_tags.all()' returns all related Food_Tags?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

小猫一只 2024-10-07 21:37:22

正如中提到的文档

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

As mentioned in the docs:

Thus, it is not (yet?) possible to have a symmetrical, recursive many-to-many relationship with extra fields, in Django. It's a "pick two" sorta deal.

沙与沫 2024-10-07 21:37:22

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

I found this approach made by Charles Leifer which seems to be a good approach to overcome this Django limitation.

狼性发作 2024-10-07 21:37:22

由于您没有明确表示它们需要不对称,因此我建议的第一件事是设置symmetry=True。这将导致关系按照您所描述的方式双向工作。 正如 eternicode 所指出的,当您使用 through 模型来建立 M2M 关系时,您无法执行此操作。如果您可以不使用 through 模型,则可以设置 symmetry=True 来准确获得您所描述的行为。

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

Since you didn't explicitly say that they need to be asymmetrical, the first thing I'll suggest is setting symmetrical=True. This will cause the relation to work both ways as you described. As eternicode pointed out, you can't do this when you're using a through model for the M2M relationship. If you can afford to go without the through model, you can set symmetrical=True to get exactly the behavior you describe.

If they need to remain asymmetrical however, you can add the keyword argument related_name="sources" to the related_tags field (which you might want to consider renaming to targets to make things more clear) and then access the related tags using meat.sources.all().

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

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

作为注释,我会尝试使用比 sourcetarget 更具描述性的内容code> 用于您的模型字段。

To create a symmetrical relationship, you have two options:

1) Create two Tag_Relation objects - one with steak as the source, and another with steak as the target:

>>> 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) Add another ManyToManyField to the Food_Tag model:

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

As a note, I'd try to use something more descriptive than source and target for your through model fields.

世界等同你 2024-10-07 21:37:22

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

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。

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

The best solution of this problem (after many investigations) was to manually create symmetrical db record on save() call. This results in DB data redundancy, of course, because you create 2 records instead of one. In your example, after saving Tag_Relation(source=source, target=target, ...) you should save reverse relation Tag_Relation(source=target, target=source, ...) like this:

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

The only disadvantage of this implementation is duplicating Tag_Relation entry, but except this everything works fine, you can even use Tag_Relation in InlineAdmin.

UPDATE
Do not forget to define delete method as well which will remove reverse relation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文