Different queryset optimisation for list view and change view in Django Admin

The get_queryset method for an admin model can be overridden; I use it to select/prefetch objects that are OneToOneFields or ManyToManyFields. However, the list view for my model shows only concise information while the change view contains many more objects. It would not make sense to prefetch ManyToManyField relations in the list view if these will not be displayed anyway.

Sample model:

class Location(TimeStampedModel):
    owner = models.ForeignKey('Profile', on_delete=models.CASCADE)
    postcode = models.CharField("postcode", max_length=11, blank=True)
    tenants = models.ManyToManyField('Profile', blank=True)

Sample admin model:

@admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
    list_display = ('owner', 'postcode')
    fields = ('owner', 'postcode', 'tenants')
    filter_horizontal = ('tenants',)

    def get_queryset(self, request):
        qs = super(LocationAdmin, self).get_queryset(request).select_related('owner__user')
        qs = qs.prefetch_related('tenants')
        return qs

Is it possible to define different optimisations for the queryset returned for the list view of the model and the queryset returned for the change/add view of the same model?

That is, in the sample admin model above, the qs.prefetch_related('tenants') line will be relevant for the change/add view only?


Solution 1:

The easiest way to achieve this is to use the request.resolver_match attribute which can be used to work out which view you are executing. The following is slightly hacky/fragile (it is essentially using some internals), but works:

class LocationAdmin(admin.ModelAdmin):
    list_display = ['owner', 'postcode']
    fields = ['owner', 'postcode', 'tenants']
    filter_horizontal = ['tenants']

    def get_queryset(self, request):
        qs = super(LocationAdmin, self).get_queryset(request)
        qs = qs.select_related('owner__user')
        if request.resolver_match.func.__name__ == 'change_view':
            qs = qs.prefetch_related('tenants')
        return qs

You should also consider whether you need this and whether it will actually work. The change view shows only one main object, which means that the N+1 problem for a list of objects often does not apply. In addition, the queries for inlines and the widgets for foreign key and many-to-many fields may not use the querysets you provide from get_queryset. For this case, testing with Django 1.10.2, the prefetch_related call did not reduce the number of queries the 'change' view executed. The 'add' view did not use the get_queryset method at all.

See complete demo app at https://github.com/spookylukey/djangoadmintips/tree/master/queryset_optimization