[英]How to make recursive ManyToManyField relationships that have extra fields symmetrical in Django?
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: 我希望能够得到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()
[]
but related_tags is empty for meat. 但是related_tags对于肉是空的。 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? 我意识到这与'symmetrical = False'参数有关,但我怎样才能设置模型,使'meat.related_tags.all()'返回所有相关的Food_Tags?
As mentioned in the docs : 如文档中所述 :
- When defining a many-to-many relationship from a model to itself, using an intermediary model, you must use
symmetrical=False
(see the model field reference ). 在使用中间模型定义从模型到自身的多对多关系时, 必须使用symmetrical=False
(请参阅模型字段引用 )。
Thus, it is not (yet?) possible to have a symmetrical, recursive many-to-many relationship with extra fields, in Django. 因此,在Django中,(还有?)可能与额外的字段具有对称的,递归的多对多关系。 It's a "pick two" sorta deal. 这是一个“挑选两个”的交易。
我发现Charles Leifer的这种方法似乎是克服这种Django限制的好方法。
Since you didn't explicitly say that they need to be asymmetrical, the first thing I'll suggest is setting
symmetrical=True
.
既然你没有明确说他们需要不对称,我建议的第一件事就是设置
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. 正如eternicode指出的那样,当您使用through
模型进行M2M关系时,您无法做到这一点。 If you can afford to go without the through
model, you can set symmetrical=True
to get exactly the behavior you describe. 如果你能够没有through
模型,你可以设置symmetrical=True
来准确获得你描述的行为。
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()
. 但是,如果它们需要保持不对称,则可以将关键字参数related_name="sources"
到related_tags
字段(您可能需要考虑重命名为targets
以使事情更加清晰),然后使用meat.sources.all()
访问相关标记meat.sources.all()
。
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: 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) Add another ManyToManyField to the Food_Tag
model: 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')
As a note, I'd try to use something more descriptive than source
and target
for your through model fields. 作为一个注释,我会尝试使用比source
和target
更具描述性的内容来完成您的直通模型字段。
The best solution of this problem (after many investigations) was to manually create symmetrical db record on save()
call. 此问题的最佳解决方案(经过多次调查后)是在save()
调用上手动创建对称db记录。 This results in DB data redundancy, of course, because you create 2 records instead of one. 当然,这会导致DB数据冗余,因为您创建了2条记录而不是1条记录。 In your example, after saving Tag_Relation(source=source, target=target, ...)
you should save reverse relation Tag_Relation(source=target, target=source, ...)
like this: 在您的示例中,在保存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)
])
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. 此实现的唯一缺点是重复Tag_Relation
条目,但除此之外一切正常,您甚至可以在InlineAdmin中使用Tag_Relation。
UPDATE Do not forget to define delete
method as well which will remove reverse relation. 更新不要忘记定义delete
方法,这将删除反向关系。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.