#35677: Unexpected behaviour of Prefetch with queryset filtering on a through 
model
-------------------------------------+-------------------------------------
     Reporter:  David Glenck         |                    Owner:  (none)
         Type:  Bug                  |                   Status:  closed
    Component:  Database layer       |                  Version:  5.1
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:  needsinfo
     Keywords:  Prefetch,            |             Triage Stage:
  prefetch_related, many-to-many     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):

 * cc: Simon Charette (added)
 * type:  Uncategorized => Bug

Comment:

 I suspect that the reason why
 `subscriber.subscriptions.filter(status__is_active=True)` works is that
 the descriptor for `Subscriber.subscription` calls `_next_is_sticky()`
 before any filters is applied while when it's not the case for the
 queryset passed to `Prefetch`.

 From the `_next_is_sticky` docstring

 > Indicate that the next filter call and the one following that should be
 treated as a single filter.

 In the case of `subscriber.subscription.filter(status__is_active)` the
 resulting chain is

 {{{#!python
 
Subcription.objects.all()._next_is_sticky().filter(subscribers=subscriber).filter(status__is_active)
 }}}

 while the call chain of `prefetch(Prefetch("subscriptions",
 Subscripton.objects.filter(status__is_active))` is

 {{{#!python
 
Subscripton.objects.filter(status__is_active))._next_is_sticky().filter(subscribers=subscriber)
 }}}

 Which doesn't have the intended effect since `_next_is_sticky()` is not
 called prior to the first `filter()` call.

 Calling `_next_is_sticky()` (which is effectively what you emulated with
 your `StickyQueryset`) before calling `filter(status__is_active)` has the
 same effect

 {{{#!python
 prefetched = Subscriber.objects.filter(pk__in=[subscriber1.pk,
 subscriber2.pk]).prefetch_related(
     Prefetch(
         'subscriptions',
 
queryset=Subscription.objects.all()._next_is_sticky().filter(status__is_active=True)._next_is_sticky(),
         to_attr='active_subscriptions'
     )
 )
 }}}

 The second `_next_is_sticky` call is necessary because
 `ManyRelatedManager.get_prefetch_querysets` calls to `using` triggers
 `_chain` and clears it.

 All in all this whole ''sticky'' notion is kind of ''hacky'' and simply
 doesn't appear appropriate in the context of
 `ManyRelatedManager.get_prefetch_querysets` (as there is no follow up
 `filter` call). It seems that we need in there is not `_next_is_sticky`
 but a way to let the ORM know that some filter calls against multi-valued
 relationships should reuse existing JOINs no matter what. I know we have a
 ticket for that but I can't find it.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/35677#comment:4>
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/01070191535e4975-c459c19d-977d-4998-a0f3-bbf39a180de3-000000%40eu-central-1.amazonses.com.

Reply via email to