Search code examples
django-modelsdjango-adminmany-to-many

Trying to display the reverse of a Many to Many Relationship in Django Admin


Say I'm working on a database that tracks the members in different bands. Each Band will have multiple Musicians, but each Musician can only be in one Band. If my models look like this:

class Band(models.Model):
    id = models.IntegerField().primary_key = True
    name = models.CharField(max_length=2083, blank=True, null=True)
    members = models.ManyToManyField('Musician', blank=True)

class Musician(models.Model):
    id = models.IntegerField().primary_key = True
    name = models.CharField(max_length=2083, blank=True, null=True)
    <NAME OF BAND>

After adding the Musicians in my db to the Bands, when I go to edit a Musician on the Admin page I want to see which Band they are in. What do I need to add to my models or my ModelAdmins to make that happen?

Right now where the above say I have: member_of: Band.name

I would expect that to return the name of the band but it's giving me the name of the Musician

Quick Edit: I forgot to mention that I have readonly_fields = ["member_of"] in my MusicianAdmin


Solution

  • If you specify a ManyToManyField (or any other relation), you can query in reverse automatically with:

    my_musician.band_set.all()

    This is a QuerySet of all Bands related to the Musician named musician.

    You can add these to your ModelAdmin for the musicians with:

    class BandMembersInline(admin.TabularInline):
        model = Band.members.through
    
    
    @admin.register(Musician)
    class MusicianAdmin(admin.ModelAdmin):
        inlines = [
            BandMembersInline,
        ]

    but each Musician can only be in one Band.

    If each musician can only have one band, the modeling is wrong. In that case you model this with a ForeignKey [Django-doc] from Musician to Band:

    class Band(models.Model):
        name = models.CharField(max_length=2083)
    
        def __str__(self):
            return self.name
    
    
    class Musician(models.Model):
        name = models.CharField(max_length=2083)
        band = models.ForeignKey(
            Band, related_name='members', on_delete=models.CASCADE
        )

    as admin, you can then use:

    class MusicianInline(admin.TabularInline):
        model = Musician
    
    
    @admin.register(Musician)
    class MusicianAdmin(admin.ModelAdmin):
        # …
        pass
    
    
    @admin.register(Band)
    class BandAdmin(admin.ModelAdmin):
        # …
        inlines = [MusicianInline]