Fixed #11295: If ModelAdmin.queryset returns a filtered QS don't require a 2nd count call

Original patch rewritten, added tests and get_filters_params method for ChangeList class.
Thanks Alex for the report.
This commit is contained in:
Wiktor Kolodziej 2013-02-24 13:18:21 +01:00
parent e4ee3d8fca
commit f07a5f0a21
2 changed files with 46 additions and 11 deletions

View File

@ -79,15 +79,23 @@ class ChangeList(object):
self.title = title % force_text(self.opts.verbose_name) self.title = title % force_text(self.opts.verbose_name)
self.pk_attname = self.lookup_opts.pk.attname self.pk_attname = self.lookup_opts.pk.attname
def get_filters(self, request): def get_filters_params(self, params=None):
lookup_params = self.params.copy() # a dictionary of the query string """
use_distinct = False Returns all params except IGNORED_PARAMS
"""
if not params:
params = self.params
lookup_params = params.copy() # a dictionary of the query string
# Remove all the parameters that are globally and systematically # Remove all the parameters that are globally and systematically
# ignored. # ignored.
for ignored in IGNORED_PARAMS: for ignored in IGNORED_PARAMS:
if ignored in lookup_params: if ignored in lookup_params:
del lookup_params[ignored] del lookup_params[ignored]
return lookup_params
def get_filters(self, request):
lookup_params = self.get_filters_params()
use_distinct = False
# Normalize the types of keys # Normalize the types of keys
for key, value in lookup_params.items(): for key, value in lookup_params.items():
@ -166,14 +174,13 @@ class ChangeList(object):
result_count = paginator.count result_count = paginator.count
# Get the total number of objects, with no admin filters applied. # Get the total number of objects, with no admin filters applied.
# Perform a slight optimization: Check to see whether any filters were # Perform a slight optimization:
# given. If not, use paginator.hits to calculate the number of objects, # full_result_count is equal to paginator.count if no filters
# because we've already done paginator.hits and the value is cached. # were applied
if not self.query_set.query.where: if self.get_filters_params():
full_result_count = result_count
else:
full_result_count = self.root_query_set.count() full_result_count = self.root_query_set.count()
else:
full_result_count = result_count
can_show_all = result_count <= self.list_max_show_all can_show_all = result_count <= self.list_max_show_all
multi_page = result_count > self.list_per_page multi_page = result_count > self.list_per_page

View File

@ -2537,6 +2537,34 @@ class AdminCustomQuerysetTest(TestCase):
else: else:
self.assertNotContains(response, 'Primary key = %s' % i) self.assertNotContains(response, 'Primary key = %s' % i)
def test_changelist_view_count_queries(self):
#create 2 Person objects
Person.objects.create(name='person1', gender=1)
Person.objects.create(name='person2', gender=2)
# 4 queries are expected: 1 for the session, 1 for the user,
# 1 for the count and 1 for the objects on the page
with self.assertNumQueries(4):
resp = self.client.get('/test_admin/admin/admin_views/person/')
self.assertEqual(resp.context['selection_note'], '0 of 2 selected')
self.assertEqual(resp.context['selection_note_all'], 'All 2 selected')
with self.assertNumQueries(4):
extra = {'q': 'not_in_name'}
resp = self.client.get('/test_admin/admin/admin_views/person/', extra)
self.assertEqual(resp.context['selection_note'], '0 of 0 selected')
self.assertEqual(resp.context['selection_note_all'], 'All 0 selected')
with self.assertNumQueries(4):
extra = {'q': 'person'}
resp = self.client.get('/test_admin/admin/admin_views/person/', extra)
self.assertEqual(resp.context['selection_note'], '0 of 2 selected')
self.assertEqual(resp.context['selection_note_all'], 'All 2 selected')
# here one more count(*) query will run, because filters were applied
with self.assertNumQueries(5):
extra = {'gender__exact': '1'}
resp = self.client.get('/test_admin/admin/admin_views/person/', extra)
self.assertEqual(resp.context['selection_note'], '0 of 1 selected')
self.assertEqual(resp.context['selection_note_all'], '1 selected')
def test_change_view(self): def test_change_view(self):
for i in self.pks: for i in self.pks:
response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i) response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i)