#37300: Custom Prefetch querysets on forward FK/O2O fields are no longer routed to
the parent queryset's database
--------------------------+-----------------------------------------
Reporter: norkans7 | Type: Uncategorized
Status: new | Component: Uncategorized
Version: 6.1 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
--------------------------+-----------------------------------------
In Django ≤ 6.0, when a Prefetch with a custom queryset (with no explicit
.using()) was used to prefetch a forward ForeignKey or OneToOneField,
get_prefetch_querysets() called
queryset._add_hints(instance=instances[0]), so the default router's
db_for_read() resolved to the parent instances' database via the instance
hint. The custom prefetch queryset therefore ran against the same database
as the outer queryset.
In 6.1, commit 821619aa876158e8d6dcedebd0dbc2e0e0b41295 (Refs #28586,
"Simplified related descriptor get_queryset() methods") moved the hint
into get_queryset(instance=...), which is only called when no custom
queryset is passed. The custom-queryset path in
ForwardManyToOneDescriptor.get_prefetch_querysets() and
ReverseOneToOneDescriptor.get_prefetch_querysets() lost the _add_hints()
call entirely, so a custom prefetch queryset now silently runs against
"default" even when the outer queryset uses another alias. The reverse
many-to-one and many-to-many manager paths still call _add_hints() for
custom querysets, so those relation types are unaffected — making the
forward FK/O2O behavior inconsistent with them as well as with 6.0.
Reproduction, with a "replica" alias configured identically to "default":
{{{
from django.db.models import Prefetch
book = Book.objects.using("replica").prefetch_related(
Prefetch("author", queryset=Author.objects.only("id", "name")),
)[0]
book._state.db # "replica" in both versions
book.author._state.db # 6.0: "replica" / 6.1: "default"
Without the custom queryset (.prefetch_related("author")), 6.1 still
correctly returns "replica".
}}}
Beyond queries silently hitting the wrong database (e.g. a primary instead
of a read replica), this breaks code that assigns prefetched objects onto
instances from the outer database — the forward descriptor's __set__ then
raises:
ValueError: Cannot assign "<Author: ...>": the current database router
prevents this relation.
because the two instances' _state.db values no longer match under the
default router's allow_relation().
This isn't mentioned in the 6.1 release notes, and the commit message
suggests the hint was intended to be set earlier, not dropped for custom
querysets, so it appears unintentional. Restoring
queryset._add_hints(instance=instances[0]) in the custom-queryset branches
of both descriptors' get_prefetch_querysets() fixes it. Still present on
main and stable/6.1.x as of this report.
--
Ticket URL: <
https://code.djangoproject.com/ticket/37300>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.