[Django]-How to force Django Admin to use select_related?

54👍

you can try this

class Foo(admin.ModelAdmin):
    list_select_related = (
        'foreign_key1',
        'foreign_key2',
    )

https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_select_related

45👍

Although dr jimbob’s answer makes sense, for my needs, I was able to simply override the get_queryset() method with a one-liner, even selecting a foreign key’s foreign key. Maybe this could be helpful to someone.

class MyModelAdmin(admin.ModelAdmin):
    model = MyModel
    ...
    def get_queryset(self, request):
        return super(MyModelAdmin, self).get_queryset(request).select_related(
            'foreign_key1', 'foreign_key2__fk2_foreign_key')

14👍

For my particular model, the particularly slow aspect is going through ForeignKeys when they were being displayed in forms, which aren’t called using select_related, so that’s the part I’m going to speed up.

Looking through the relevant django source, you see in django/contrib/admin/options.py that the method formfield_for_foreignkeys takes each FK db_field and calls the ForeignKey class’s formfield method, which is defined in django/db/models/fields/related/ like:

def formfield(self, **kwargs):
    db = kwargs.pop('using', None)
    defaults = {
        'form_class': forms.ModelChoiceField,
        'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
        'to_field_name': self.rel.field_name,
    }
    defaults.update(kwargs)
    return super(ForeignKey, self).formfield(**defaults)

From this, we see if we provide the db_field with a kwargs['queryset'] we can define a custom queryset that will be use select_related (this can be provided by formfield_for_foreignkey).

So basically what we want to do is override admin.ModelAdmin with SelectRelatedModelAdmin and then make our ModelAdmin subclasses of SelectRelatedModelAdmin instead of admin.ModelAdmin

class SelectRelatedModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if 'queryset' in kwargs:
            kwargs['queryset'] = kwargs['queryset'].select_related()
        else:
            db = kwargs.pop('using', None)
            kwargs['queryset'] = db_field.rel.to._default_manager.using(db).complex_filter(db_field.rel.limit_choices_to).select_related()
        return super(SelectRelatedModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

This code sample doesn’t cover admin Inlines or ManyToManyFields, or foreign_key traversal in functions called by readonly_fields or custom select_related queries, but a similar approach should work for those cases.

8👍

In Django 2.0+, a good way to improve performance of ForeignKey and ManyToMany relationships is to use autocomplete fields.

These fields don’t show all related objects and therefore load with many fewer queries.

7👍

For the admin edit/change a specific item page, foreign key select boxes may take a long time to load, to alter the way django queries the data for the foreign key:

Django docs on Using formfield_for_foreignkey

Say I have a field called foo on my Example model, and I wish to select ralated bar objects:

class ExampleAdmin(admin.ModelAdmin):

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
            if db_field.name == "foo":
                kwargs["queryset"] = Example.objects.select_related('bar')
            return super().formfield_for_foreignkey(db_field, request, **kwargs)

3👍

For the sake of completeness, I would like to add another option that was the most suitable for my use case.

As others have pointed out, the problem is often loading the data for select boxes. list_select_related does not help in this case.

In case you don’t actually want to edit the foreign key field via admin, the easiest fix is making the respective field readonly:

class Foo(admin.ModelAdmin):
    readonly_fields = ('foreign_key_field1','foreign_key_field2',)

You can still display these fields, there will simply not be a select box, hence Django does not need to retrieve all the select box options from the database.

Leave a comment