#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 [email protected].
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