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