11

I've got a ModelAdmin class that includes a foreign key field in its list_display. But the admin list page for that model is doing hundreds of queries, one query per row to get the data from the other table instead of a join (select_related()).

The Django docs indicate you can add list_select_related = True as an attribute to your ModelAdmin to make this go away, but it doesn't seem to work at all for me. This SO question seems to give a similar problem, but his resolution is unclear, and it doesn't work in my case.

Here's a cut-down version of my model and model admin:

class Device(models.Model):
    serial_number = models.CharField(max_length=80, blank=True, unique=True)
    label = models.CharField(max_length=80, blank=True)

    def __str__(self):
        s = str(self.serial_number)
        if self.label:
            s += ': {0}'.format(self.label)
        return s

class Event(models.Model):
    device = models.ForeignKey(Device, null=True)
    type = models.CharField(max_length=40, null=False, blank=True, default='')

class EventAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'device')
    list_select_related = True

However, adding that list_selected_related = True didn't change anything. I still get lots of queries like this instead of an SQL join:

Lots of queries, not a join

Any ideas why the Django admin seems to be ignoring my list_select_related and doing N queries? I'm using Python 2.7 and Django 1.3.3.

Community
  • 1
  • 1
Ben Hoyt
  • 10,694
  • 5
  • 60
  • 84

3 Answers3

18

The issue here is that setting list_select_related = True just adds a basic select_related() onto the query, but that call does not by default follow ForeignKeys with null=True. So the answer is to define the queryset the changelist uses yourself, and specify the FK to follow:

class EventAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'device')
    def queryset(self, request):
        return super(EventAdmin, self).queryset(request).select_related('device')
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 2
    Thanks! The `null=True` on the foreign key was definitely preventing the selected_related doing its thing. I guess if I'd followed the [docs](https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.select_related) all the way down, I'd have seen that: "Note that, by default, select_related() does not follow foreign keys that have null=True" – Ben Hoyt Mar 04 '13 at 23:00
  • Out of interest, do you know why `list_select_related = True` isn't the default for the Django admin? That would seem to be a much more sensible default. (I can see why by default `select_related()` doesn't follow foreign keys that have null=True, because that could be a performance issue, but I'm wondering about `list_select_related` in other cases.) – Ben Hoyt Mar 10 '13 at 20:54
  • 2
    In Django>=1.6 the method is now named `get_queryset`. – TAH Jul 29 '17 at 19:29
10

Since Django 1.6, list_select_related accepts a boolean, list or tuple with the names of the fields to include in the select_related() call. Hence you can now use:

class EventAdmin(admin.ModelAdmin):
    list_display = ('__str__', 'device')
    list_select_related = ['device']
Fush
  • 2,469
  • 21
  • 19
  • +1 While Daniel Roseman's answer provides a good peek into how things work. This answer is the simpler/updated version. – Nitin Nain Feb 21 '20 at 17:34
0

Although select_related is generally the way to go, there are time when one requires more control, then overiding the get_queryset becomes more applicable, this is a more modern version of Daniel Roseman's answer:

Where foo and bar are foreign key fields:

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return qs.select_related('foo', 'foo__bar').only('foo__field1', 'foo__bar__field2')
run_the_race
  • 1,344
  • 2
  • 36
  • 62