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?
Since you didn't explicitly say that they need to be asymmetrical, the first thing I'll suggest is settingAs eternicode pointed out, you can't do this when you're using asymmetrical=True
. This will cause the relation to work both ways as you described.through
model for the M2M relationship. If you can afford to go without thethrough
model, you can setsymmetrical=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 therelated_tags
field (which you might want to consider renaming totargets
to make things more clear) and then access the related tags usingmeat.sources.all()
.To create a symmetrical relationship, you have two options:
1) Create two
Tag_Relation
objects - one withsteak
as the source, and another withsteak
as the target:2) Add another ManyToManyField to the
Food_Tag
model:As a note, I'd try to use something more descriptive than
source
andtarget
for your through model fields.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.
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 savingTag_Relation(source=source, target=target, ...)
you should save reverse relationTag_Relation(source=target, target=source, ...)
like this: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.I found this approach made by Charles Leifer which seems to be a good approach to overcome this Django limitation.