M2M relation to itself through another model // 1-minute guide 🐍↩️

Say, you want your Tag model to contain a subtags field that has Tag instances. And you need to make a through table.

models.py

class Tag(models.Model)
   name = models.Charfield(max_length=100)
   subtags = models.ManyToManyField('self',
                                    through='ToSubTag',
                                    related_name='main_tag',
                                    # through_fields='tag',
                                    blank=True,
                                    symmetrical=False)

class ToSubTag(models.Model):
    tag = models.ForeignKey(Tag,
                            related_name='maintag_sub',
                            on_delete=models.CASCADE,
                            verbose_name='Тема')
    subtag = models.ForeignKey(Tag,
                               db_index=True,
                               related_name='subtag',
                               on_delete=models.CASCADE,
                               verbose_name='Подтема')
    level = models.PositiveIntegerField()

Breaking it down:

  • 'self' is used to establish a relation to the same model.
  • through='ToSubTag' is used to establish a table with additional information about this Tag to Subtag relation. In my example, it is level = models.PositiveIntegerField().
  • related_name = 'main_tag' is used to refer to the main Tag model from a sub Tag model. In the ToSubTag model they are mandatory for both relations to Tag, because if there are two of them.
  • through_fields = 'tag' is used to let Django know which ToSubTag field is assigned to the main Tag model (because ToSubTag has two ForeignKey relations to Tag – tag and subtag); BUT when I use it and make an Inline in admin.py, I get an error: The intermediary model 'testapp.ToSubTag' has no field 'a'. That’s why it is commented. Instead of it, I use fk_name property in the inline (see it below).
  • symmetrical = False is used because this particular relation has a strict hierarchy: Tag -> Subtag. Otherwise if you added a subTag to mainTag.subtags, then this mainTag would be added to subTag.subtags. This property has a good explanation in the docs – if you add a secondPerson to firstPerson.friends, then the firstPerson would be added to the secondPerson.friends, because they can only be friends mutually.

admin.py

class SubTagsToTag(admin.TabularInline):
    model = Tag.subtags.through
    fk_name = "tag"

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    inlines = [
        SubTagsToTag
    ]

Everything here is pretty standard, except for:

  • fk_name = "tag" is basically the same as through_fields = 'tag'.

References

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.