Commit 009e237c authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

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.
parent 006c2b8f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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)
+14 −4
Original line number Diff line number Diff line
@@ -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,7 +113,17 @@ 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:

        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)
+10 −0
Original line number Diff line number Diff line
@@ -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):
+2 −0
Original line number Diff line number Diff line
@@ -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/$',