Fixed #17535 -- Optimized list generic views.
When allow_empty is False, prevented the view from loading the entire queryset in memory when pagination is enabled.
This commit is contained in:
parent
006c2b8fc1
commit
009e237cf0
|
@ -273,7 +273,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View):
|
|||
if not allow_empty:
|
||||
# When pagination is enabled, it's better to do a cheap query
|
||||
# than to load the unpaginated queryset in memory.
|
||||
is_empty = not bool(qs) if paginate_by is None else not qs.exists()
|
||||
is_empty = len(qs) == 0 if paginate_by is None else not qs.exists()
|
||||
if is_empty:
|
||||
raise Http404(_(u"No %(verbose_name_plural)s available") % {
|
||||
'verbose_name_plural': force_unicode(qs.model._meta.verbose_name_plural)
|
||||
|
|
|
@ -16,7 +16,7 @@ class MultipleObjectMixin(ContextMixin):
|
|||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Get the list of items for this view. This must be an interable, and may
|
||||
Get the list of items for this view. This must be an iterable, and may
|
||||
be a queryset (in which qs-specific behavior will be enabled).
|
||||
"""
|
||||
if self.queryset is not None:
|
||||
|
@ -113,9 +113,19 @@ class BaseListView(MultipleObjectMixin, View):
|
|||
def get(self, request, *args, **kwargs):
|
||||
self.object_list = self.get_queryset()
|
||||
allow_empty = self.get_allow_empty()
|
||||
if not allow_empty and len(self.object_list) == 0:
|
||||
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
|
||||
% {'class_name': self.__class__.__name__})
|
||||
|
||||
if not allow_empty:
|
||||
# When pagination is enabled and object_list is a queryset,
|
||||
# it's better to do a cheap query than to load the unpaginated
|
||||
# queryset in memory.
|
||||
if (self.get_paginate_by(self.object_list) is not None
|
||||
and hasattr(self.object_list, 'exists')):
|
||||
is_empty = not self.object_list.exists()
|
||||
else:
|
||||
is_empty = len(self.object_list) == 0
|
||||
if is_empty:
|
||||
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
|
||||
% {'class_name': self.__class__.__name__})
|
||||
context = self.get_context_data(object_list=self.object_list)
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
|
|
@ -159,6 +159,16 @@ class ListViewTests(TestCase):
|
|||
def test_missing_items(self):
|
||||
self.assertRaises(ImproperlyConfigured, self.client.get, '/list/authors/invalid/')
|
||||
|
||||
def test_paginated_list_view_does_not_load_entire_table(self):
|
||||
# Regression test for #17535
|
||||
self._make_authors(3)
|
||||
# 1 query for authors
|
||||
with self.assertNumQueries(1):
|
||||
self.client.get('/list/authors/notempty/')
|
||||
# same as above + 1 query to test if authors exist + 1 query for pagination
|
||||
with self.assertNumQueries(3):
|
||||
self.client.get('/list/authors/notempty/paginated/')
|
||||
|
||||
def _make_authors(self, n):
|
||||
Author.objects.all().delete()
|
||||
for i in range(n):
|
||||
|
|
|
@ -128,6 +128,8 @@ urlpatterns = patterns('',
|
|||
views.AuthorList.as_view(paginate_by=30)),
|
||||
(r'^list/authors/notempty/$',
|
||||
views.AuthorList.as_view(allow_empty=False)),
|
||||
(r'^list/authors/notempty/paginated/$',
|
||||
views.AuthorList.as_view(allow_empty=False, paginate_by=2)),
|
||||
(r'^list/authors/template_name/$',
|
||||
views.AuthorList.as_view(template_name='generic_views/list.html')),
|
||||
(r'^list/authors/template_name_suffix/$',
|
||||
|
|
Loading…
Reference in New Issue