Opened 54 minutes ago
#37300 new Uncategorized
Custom Prefetch querysets on forward FK/O2O fields are no longer routed to the parent queryset's database
| Reported by: | Norbert Kwizera | Owned by: | |
|---|---|---|---|
| Component: | Uncategorized | Version: | 6.1 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
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.