I am developing a Django project, which already got plenty of real world data so I can see its performance.
The performances of few DjangoAdmin lists are just terrible.
I have one admin list, lets call it devices
. In that list I am fetching additional informations for each row, those fields are related from other tables and connected via FK/PK/M2N.
List contains about 500 records and loading of that screen takes, according to django-debug-toolbar
, around 6.5 seconds, which is unbearable.
This admin class
@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
list_select_related = True
list_display = ('id', 'name', 'project', 'location', 'machine', 'type', 'last_maintenance_log')
inlines = [CommentInline, TestLogInline]
def project(self, obj):
try:
return Device.objects.get(pk=obj.pk).machine.location.project.project_name
except AttributeError:
return '-'
def location(self, obj):
try:
return Device.objects.get(pk=obj.pk).machine.location.name
except AttributeError:
return '-'
def last_maintenance_log(self, obj):
try:
log = AdminLog.objects.filter(object_id=obj.pk).latest('time')
return '{} | {}'.format(log.time.strftime("%d/%m/%Y, %-I:%M %p"), log.title)
except AttributeError:
return '-'
All Machine
, Location
and Project
are tables in database.
After looking into queries in django-debug-toolbar
I discovered something terrible.
That screen required 287 sql queries ! Yes, over two hundreds!
Is there anything I can do to reduce this time to something reasonable?
EDIT:
Thanks to Bruno I removed Device.objects.get(pk=obj.id)
(as it was really redundant, I totally overlooked this.
So everywhere i put obj.
for example obj.machine.location.project.project_name
Only this alone reduces the speed and query count to half, so far so good.
I not have trouble fusing obj
approach and select_related
approach. This is my current code, which is worse than only obj
approach.
def project(self, obj):
try:
Device.objects.select_related('machine__location__project').get(id=obj.pk).machine.location.project.project_name
except AttributeError:
return '-'
Which creates a nice INNER JOINs in queries, but the performance is around 15% worse than without it and using only obj.machine.location.project.project_name
What am I doing wrong?
EDIT2:
Best performance I got is with this code:
@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
list_select_related = True
save_as = True
form = DeviceForm
list_display = ('id', 'name', 'project', 'location', 'machine', 'type', 'last_maintenance_log')
inlines = [CommentInline, TestLogInline]
def project(self, obj):
try:
return obj.machine.location.project.project_name
except AttributeError:
return '-'
def location(self, obj):
try:
return obj.machine.location.name
except AttributeError:
return '-'
def last_maintenance_log(self, obj):
try:
log = AdminLog.objects.filter(object_id=obj.pk).latest('time')
return '{} | {}'.format(log.time.strftime("%d/%m/%Y, %-I:%M %p"), log.title)
except AttributeError:
return '-'
def get_queryset(self, request):
return Device.objects.select_related('machine__location__project').all()
Which pushed this down to 104 queries (from almost 300) and time reduction over 50%. Can this be improved any further?