class Food_Tag(models.Model):
name = models.CharField(max_length=200)
related_tags = models.ManyToManyField(\'self\', blank=True, symmetrical=False, through=
Since you didn't explicitly say that they need to be asymmetrical, the first thing I'll suggest is setting As eternicode pointed out, you can't do this when you're using a symmetrical=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 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().
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).
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.
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.
I found this approach made by Charles Leifer which seems to be a good approach to overcome this Django limitation.
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.