Opened 15 years ago

Closed 15 years ago

Last modified 15 years ago

#10722 closed (invalid)

Changelist view does not use select_related() on a nullable foreign key

Reported by: mrts Owned by: nobody
Component: contrib.admin Version: dev
Severity: Keywords: efficient-admin
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Given the following models.py:

from django.db import models

class Base(models.Model):
    name = models.CharField(max_length=10)
    lots_of_text = models.TextField()

    class Meta:
        abstract = True

    def __unicode__(self):
        return self.name

class A(Base):
    a_field = models.CharField(max_length=10)

class B(Base):
    b_field = models.CharField(max_length=10)

class C(Base):
    a = models.ForeignKey(A)
    b = models.ForeignKey(B)
    is_published = models.BooleanField()

and the following admin.py:

from django.contrib import admin
from improve_admin.models import A, B, C

class CAdmin(admin.ModelAdmin):
    list_display = ('name', 'a', 'b', 'is_published')

admin.site.register(A)
admin.site.register(B)
admin.site.register(C, CAdmin)

select_related() is used as documented in http://docs.djangoproject.com/en/dev/ref/contrib/admin/#list-select-related , resulting in the following query:

SELECT
	"improve_admin_c"."id", "improve_admin_c"."name", "improve_admin_c"."lots_of_text", "improve_admin_c"."a_id", "improve_admin_c"."b_id", "improve_admin_c"."is_published", "improve_admin_a"."id", "improve_admin_a"."name", "improve_admin_a"."lots_of_text", "improve_admin_a"."a_field", "improve_admin_b"."id", "improve_admin_b"."name", "improve_admin_b"."lots_of_text", "improve_admin_b"."b_field"
FROM
	"improve_admin_c"
	INNER JOIN "improve_admin_a" ON ("improve_admin_c"."a_id" = "improve_admin_a"."id")
	INNER JOIN "improve_admin_b" ON ("improve_admin_c"."b_id" = "improve_admin_b"."id")
ORDER BY
	"improve_admin_c"."id" DESC

Setting one foreign key field to be nullable:

# diff between the original and new models.py
 class C(Base):
-    a = models.ForeignKey(A)
+    a = models.ForeignKey(A, blank=True, null=True)
     b = models.ForeignKey(B)

results in the following SQL:

SELECT
	"improve_admin_c"."id", "improve_admin_c"."name", "improve_admin_c"."lots_of_text", "improve_admin_c"."a_id", "improve_admin_c"."b_id", "improve_admin_c"."is_published", "improve_admin_b"."id", "improve_admin_b"."name", "improve_admin_b"."lots_of_text", "improve_admin_b"."b_field"
FROM
	"improve_admin_c"
	INNER JOIN "improve_admin_b" ON ("improve_admin_c"."b_id" = "improve_admin_b"."id")
ORDER BY
	"improve_admin_c"."id" DESC

and additional n queries for each of the referred a fields:

SELECT
	"improve_admin_a"."id", "improve_admin_a"."name", "improve_admin_a"."lots_of_text", "improve_admin_a"."a_field"
FROM
	"improve_admin_a"
WHERE
	"improve_admin_a"."id" = 2

Change History (5)

comment:1 by mrts, 15 years ago

Component: Uncategorizeddjango.contrib.admin

comment:2 by Karen Tracey, 15 years ago

Resolution: invalid
Status: newclosed

Admin doesn't introspect the model and decide not to use select_related() if null=True on a ForeignKey. Rather, select_related() itself does not follow null=True ForeignKeys, as documented: http://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related

"Note that, by default, select_related() does not follow foreign keys that have null=True."

This strikes me as another case where if you want to control the queries the admin is going to issue to this level of detail, you'll need to specify the ModelAdmin queryset() yourself.

comment:3 by mrts, 15 years ago

Thanks for pointing that out!

comment:5 by mrts, 15 years ago

Keywords: efficient-admin added
Note: See TracTickets for help on using tickets.
Back to Top