N + 1 problem with inline

Hi everyone!

My models are:

class Game(SlugifyMixin, IGDBIdMixin):
    title = models.CharField(max_length=250)
    storyline = models.TextField(null=True, blank=True)
    summary = models.TextField(null=True, blank=True)

    platforms = models.ManyToManyField(
        "Platform",
        through="GamePlatformRelease",
        related_name="games",
        blank=True
    )


class BaseGameAttribute(SlugifyMixin, IGDBIdMixin):
    class Meta:
        abstract = True

    title = models.CharField(max_length=250)

    def __str__(self):
        return self.title


class Platform(BaseGameAttribute):
    pass


class GamePlatformRelease(models.Model):
    game = models.ForeignKey(
        Game,
        on_delete=models.CASCADE,
        related_name="releases"
    )
    platform = models.ForeignKey(
        Platform,
        on_delete=models.CASCADE,
        related_name="releases"
    )
    date = models.DateField()

And I try to customize my admin panel:

class GamePlatformReleaseInline(admin.TabularInline):
    model = GamePlatformRelease
    extra = 1


@admin.register(Game)
class GameAdmin(admin.ModelAdmin):
    inlines = (GamePlatformReleaseInline, )

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return qs.prefetch_related("releases", "releases__platform",)

The problem is redundant queries:

SELECT "games_db_platform"."id",
       "games_db_platform"."slug",
       "games_db_platform"."igdb_id",
       "games_db_platform"."title"
  FROM "games_db_platform"
 6 similar queries.  Duplicated 6 times.

Please help

There’s a Game m2m to platform too. Can it be that there’s a missing prefetch related for that one? So that the final code should be:

return qs.prefetch_related("platforms", "releases", "releases__platform",)

Thank You for the reply, but no, doesn’t help.

Perhaps you should use select_related within the inline’s get_queryset method. This will fetch the related Platform for each release in a single query.

from django.contrib import admin
from .models import Game, GamePlatformRelease, Platform

class GamePlatformReleaseInline(admin.TabularInline):
    model = GamePlatformRelease
    extra = 1

    def get_queryset(self, request):
        # Use select_related to join the Platform table.
        # This prevents a separate query for each platform in the inline.
        queryset = super().get_queryset(request)
        return queryset.select_related('platform')

@admin.register(Game)
class GameAdmin(admin.ModelAdmin):
    inlines = (GamePlatformReleaseInline,)

    def get_queryset(self, request):
        # This prefetch is still useful for the Game list view,
        # but it doesn't affect the inlines on the change/detail view.
        queryset = super().get_queryset(request)
        # You can combine these into a single lookup.
        return queryset.prefetch_related("releases__platform")

By adding .select_related('platform') to the inline’s queryset, you are telling Django: "When you fetch the GamePlatformRelease objects for this game, also fetch the corresponding Platform data for each one in the same query.”.

I hope this helps.