Skip to content

Commit

Permalink
Added optimize queries chapter
Browse files Browse the repository at this point in the history
  • Loading branch information
shabda committed Feb 6, 2018
1 parent ffcf3e7 commit 7da12ca
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 5 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# This will track teh Django version against whihch this is written.
# This will track the Django version against which this is written.
version = '2.0'

release = '2.0'
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Calculated fields
:maxdepth: 1

calculated_fields
many_to_many
optimize_queries
sorting_calculated_fields
filtering_calculated_fields

Expand Down Expand Up @@ -82,6 +82,7 @@ Listview Page
disable_pagination
date_based_filtering
boolean_fields
many_to_many

Changeview Page
++++++++++++++++++++++++++++++++++++++++++
Expand Down Expand Up @@ -110,7 +111,6 @@ Misc
add_model_twice
override_save
database_view
optimize_queries
set_ordering


Expand Down
45 changes: 45 additions & 0 deletions docs/optimize_queries.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
How to optimize queries in Django admin?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

If you have a lot of calculated fields in your admin, you can be running multipel queries per object leading to your admin can becoming quite slow.
To fix this you can override the :code:`get_queryset` method on model admin to annotate the calculated fields.

Lets take the example of this `ModelAdmin` we have for `Origin`::

@admin.register(Origin)
class OriginAdmin(admin.ModelAdmin):
list_display = ("name", "hero_count", "villain_count")

def hero_count(self, obj):
return obj.hero_set.count()

def villain_count(self, obj):
return obj.villain_set.count()


This add two extra queries per row in your listview page. To fix this you can override the :code:`get_queryset` to annotate the counted fields,
and then use the annotated fields in your ModelAdmin methods.

With the changes, your ModelAdmin field looks like this::


@admin.register(Origin)
class OriginAdmin(admin.ModelAdmin):
list_display = ("name", "hero_count", "villain_count")

def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_hero_count=Count("hero", distinct=True),
_villain_count=Count("villain", distinct=True),
)
return queryset

def hero_count(self, obj):
return obj._hero_count

def villain_count(self, obj):
return obj._villain_count

There are no per object extra queries. Your admin continues to look like it did before the :code:`annotate` call.

.. image:: calculated_field.png

12 changes: 10 additions & 2 deletions heroes_and_monsters/entities/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib import admin
from django.db.models import Count

from .models import Hero, Villain, Category, Origin

Expand All @@ -19,9 +20,16 @@ class CategoryAdmin(admin.ModelAdmin):
class OriginAdmin(admin.ModelAdmin):
list_display = ("name", "hero_count", "villain_count")

def get_queryset(self, request):
queryset = super().get_queryset(request)
queryset = queryset.annotate(
_hero_count=Count("hero", distinct=True),
_villain_count=Count("villain", distinct=True),
)
return queryset

def hero_count(self, obj):
return obj.hero_set.count()
return obj._hero_count

def villain_count(self, obj):
return obj.villain_set.count()
return obj._villain_count

0 comments on commit 7da12ca

Please sign in to comment.