#34067: django.core.Paginator wrong query slicing
-------------------------------------+-------------------------------------
     Reporter:  Hristo Trendafilov   |                    Owner:  nobody
         Type:  Bug                  |                   Status:  closed
    Component:  Core (Other)         |                  Version:  3.2
     Severity:  Normal               |               Resolution:  needsinfo
     Keywords:                       |             Triage Stage:
  Paginator,slice,queryset           |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by Hristo Trendafilov:

Old description:

> A have a ListView defined like so:
>
> {{{
> class AllPracticesListView(LoginRequiredMixin, PermissionRequiredMixin,
> ListView, ToolBoxFunctions):
>     template_name = 'practice_list.html'
>     model = Practice
>     paginate_by = 6
>     permission_required = 'customauth.access_practices'
>
>     def get_queryset(self):
>         qs = self.get_all_practices_by_user().order_by(
>             'company_branch__pk',
> 'practice_profile__personal_profile__last_name').prefetch_related('practice_profile')
>
>         # method < get_all_practices_by_user > is a toolbox method that
> returns:
>         #
> Practice.objects.filter(company_branch__owner=self.request.user).order_by('pk')
>
>         return qs
>
> }}}
>
> Models are defined like so:
> {{{
>
>  class Practice(models.Model):
>                 company_branch = models.ForeignKey(
>                          CompanyBranch,
>                          on_delete=models.PROTECT,
>                          related_name='practices',
>                     )
>                 # CompanyBranch model has an owner field which ForeignKey
> to the user model
>
>                 practice_profile = models.ForeignKey(
>                     PracticeProfile,
>                     on_delete=models.PROTECT,
>                     related_name='practices',
>                 )
>
> class PracticeProfile(models.Model):
>                     personal_profile = models.ForeignKey(
>                         PersonalProfile,
>                         on_delete=models.PROTECT,
>                         blank=True,
>                         null=True,
>                     )
>                     # PersonalProfile model has a field called <
> last_name >
> }}}
> Or schematically:
> {{{
> Practice object relations:
>       -> CompanyBranch
>       -> PracticeProfile
>           -> PersonalProfile /*NULLABLE RELATION/
>
> }}}
>
> When the code is run, the view does not display results correctly.
>
> I did like so to get the WRONG result:
> Added a breakpoint on  {{{Paginator._get_page}}} return statement
>
> Queryset is passed okay and is available in
> {{{Paginator.object_list}}}
>
> But in the args of {{{Paginator._get_page}}} the queryset is totally
> different than expected / should be a slice from the first six elements
> of {{{Paginator.object_list}}} /
>
> I have tried to add a breakpoint like so
> [[Image(https://imgur.com/SdaQUt6)]]
> Then I get this result [[Image(https://imgur.com/5zzLNV0)]].
>
> As you can see, PKs 1632, 1624, etc. are missing, objects are
> different/marked red and purple/, and are actually removed and never
> shown in the view.
>
> If you run slice manually at this point - everything is again fine
> [[Image(https://imgur.com/yvn8KMO)]]
> This happens only on NULL PersonalProfile objects.
>

> I did like so to get OKAY results:
>
> 1. Added a simple breakpoint is added on {{{Paginator.page}}} like so
> [[Image(https://imgur.com/d7J5BMZ)]] and that breakpoint is just run then
> the result is okay [[Image(https://imgur.com/ILDHAMN)]]
>
> 2. changed paginate_by to 10
>
> 3. changed {{{order_by}}} in {{{get_queryset}}} to {{{-pk}}}
>
> 4. call again the {{{page = paginator.page(page_number)}}}
>
> P.S. That is also happening in django 2.2

New description:

 A have a ListView defined like so:

 {{{
 class AllPracticesListView(LoginRequiredMixin, PermissionRequiredMixin,
 ListView, ToolBoxFunctions):
     template_name = 'practice_list.html'
     model = Practice
     paginate_by = 6
     permission_required = 'customauth.access_practices'

     def get_queryset(self):
         qs = self.get_all_practices_by_user().order_by(
             'company_branch__pk',
 
'practice_profile__personal_profile__last_name').prefetch_related('practice_profile')

         # method < get_all_practices_by_user > is a toolbox method that
 returns:
         #
 Practice.objects.filter(company_branch__owner=self.request.user).order_by('pk')

         return qs

 }}}

 Models are defined like so:
 {{{

  class Practice(models.Model):
                 company_branch = models.ForeignKey(
                          CompanyBranch,
                          on_delete=models.PROTECT,
                          related_name='practices',
                     )
                 # CompanyBranch model has an owner field which ForeignKey
 to the user model

                 practice_profile = models.ForeignKey(
                     PracticeProfile,
                     on_delete=models.PROTECT,
                     related_name='practices',
                 )

 class PracticeProfile(models.Model):
                     personal_profile = models.ForeignKey(
                         PersonalProfile,
                         on_delete=models.PROTECT,
                         blank=True,
                         null=True,
                     )
                     # PersonalProfile model has a field called < last_name
 >
 }}}
 Or schematically:
 {{{
 Practice object relations:
       -> CompanyBranch
       -> PracticeProfile
           -> PersonalProfile /*NULLABLE RELATION/

 }}}

 When the code is run, the view does not display results correctly.

 I did like so to get the WRONG result:
 Added a breakpoint on  {{{Paginator._get_page}}} return statement

 Queryset is passed okay and is available in
 {{{Paginator.object_list}}}

 But in the args of {{{Paginator._get_page}}} the queryset is totally
 different than expected / should be a slice from the first six elements of
 {{{Paginator.object_list}}} /

 I have tried to add a breakpoint like so
 [[Image(https://imgur.com/SdaQUt6)]]
 Then I get this result [[Image(https://imgur.com/5zzLNV0)]].

 As you can see, PKs 1632, 1624, etc. are missing, objects are
 different/marked red and purple/, and are actually removed and never shown
 in the view.

 If you run slice manually at this point - everything is again fine
 [[Image(https://imgur.com/yvn8KMO)]]
 This happens only on NULL PersonalProfile objects.


 I did like so to get OKAY results:

 1. Added a simple breakpoint is added on {{{Paginator.page}}} like so
 [[Image(https://imgur.com/d7J5BMZ)]] and that breakpoint is just run then
 the result is okay [[Image(https://imgur.com/ILDHAMN)]]

 2. changed paginate_by to 10

 3. changed {{{order_by}}} in {{{get_queryset}}} to {{{-pk}}}

 4. call again the {{{page = paginator.page(page_number)}}}


 It is Django fault for those reasons:

 * `order_by` might be complicated but is supported by the framework
 * Chained `order_by` is also supported
 * queryset slicing is a feature of Django queryset API.
 * queryset slicing should act like list and string slicing and namely to
 provide you with results `[ from : to ]` indexes provided and not to
 change or lose data in between. Or in other words - what is passed in the
 `Paginator.object_list` should be just sliced `[ from : to ]`. `Paginator`
 should never care for the queryset, what is the ordering of the queryset,
 what is excluded and so on - it should just work with it.
 * changing `paginate_by` to `10` or greater fixes the problem.
 `paginate_by` is a standard and documented Django ListView setting.
 * changing `order_by` to some other `order_by` also fixes the problem.
 `order_by` should work the same or if any limitations those should be well
 documented
 * removing `order_by` also fixes the problem.

 P.S. That is also happening in Django 2.2

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34067#comment:6>
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/010701839c961d5d-95ef6060-da73-4015-9bf2-24c9ecca8180-000000%40eu-central-1.amazonses.com.

Reply via email to