#35637: Paginator executes additional SQL query when using QuerySet
-------------------------------------+-------------------------------------
     Reporter:  amir-mahdih86        |                     Type:
                                     |  Uncategorized
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  5.0                  |                 Severity:  Normal
     Keywords:  Paginator,           |             Triage Stage:
  Queryset, Performance, SQL         |  Unreviewed
  Queries                            |
    Has patch:  1                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 When using Django's `Paginator` class with a `QuerySet`, it executes an
 additional SQL count query to determine the total count of items. This
 does not happen when a `list` is passed to the `Paginator`.

 **Steps to Reproduce:**
 1. Create a view that uses `Paginator` with a `QuerySet` object.
 2. Compare the SQL queries executed with those executed when a `list` is
 used instead.

 **Expected Behavior:**
 The `Paginator` shouldn't execute an additional query to count a
 QuerySet's items while it can use `len()` to do that.

 **Actual Behavior:**
 An additional query is executed when a `QuerySet` is passed while it's
 possible to convert `QuerySet` to a `list` before passing it to
 `Paginator` with no difference in returned object but 1 less database
 query execution.

 **Example Code:**

 {{{
 # This code is simplified to be smaller

 # This bellow view executes 3 SQL queries
 class PostList(APIView):
     def get(self, request):
         posts = Post.objects.filter(is_published=True)
         paginator = Paginator(posts, 10)
         posts = paginator.page(1)
         serializer = PostSerializer(posts, many=True)
         return Response(serializer.data)


 # But this one executes 2 SQL queries
 class PostList(APIView):
     def get(self, request):
         posts = list(Post.objects.filter(is_published=True))
         paginator = Paginator(posts, 10)
         posts = paginator.page(1)
         serializer = PostSerializer(posts, many=True)
         return Response(serializer.data)
 }}}

 **My Solution:**
 A simple solution is to modify the `__init__` method of the `Paginator`
 class to convert `object_list` to a `list` object.
 This is just a simple solution and it may be necessary to enhance the
 problem in a deeper layer.

 Here is the modified version of that method:
 {{{
     def __init__(
         self,
         object_list,
         per_page,
         orphans=0,
         allow_empty_first_page=True,
         error_messages=None,
     ):
         self.object_list = list(object_list)
         self._check_object_list_is_ordered()
         self.per_page = int(per_page)
         self.orphans = int(orphans)
         self.allow_empty_first_page = allow_empty_first_page
         self.error_messages = (
             self.default_error_messages
             if error_messages is None
             else self.default_error_messages | error_messages
         )
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35637>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070190f381df72-16d1f284-e429-4402-9535-fc859ef4ffa1-000000%40eu-central-1.amazonses.com.

Reply via email to