#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.