Fixed #35309 -- Made prefetch clear ordering for single-valued relationships.

This commit is contained in:
Laurent Lyaudet 2024-03-16 06:51:38 +01:00 committed by Mariusz Felisiak
parent 921670c694
commit f2388a4b73
2 changed files with 75 additions and 3 deletions

View File

@ -195,6 +195,9 @@ class ForwardManyToOneDescriptor:
else:
query = {"%s__in" % self.field.related_query_name(): instances}
queryset = queryset.filter(**query)
# There can be only one object prefetched for each instance so clear
# ordering if the query allows it without side effects.
queryset.query.clear_ordering()
# Since we're going to assign directly in the cache,
# we must manage the reverse relation cache manually.
@ -469,6 +472,9 @@ class ReverseOneToOneDescriptor:
instances_dict = {instance_attr(inst): inst for inst in instances}
query = {"%s__in" % self.related.field.name: instances}
queryset = queryset.filter(**query)
# There can be only one object prefetched for each instance so clear
# ordering if the query allows it without side effects.
queryset.query.clear_ordering()
# Since we're going to assign directly in the cache,
# we must manage the reverse relation cache manually.

View File

@ -1,7 +1,7 @@
from django.db.models import Prefetch, prefetch_related_objects
from django.test import TestCase
from .models import Author, Book, Reader
from .models import Author, Book, House, Reader, Room
class PrefetchRelatedObjectsTests(TestCase):
@ -33,6 +33,17 @@ class PrefetchRelatedObjectsTests(TestCase):
cls.reader1.books_read.add(cls.book1, cls.book4)
cls.reader2.books_read.add(cls.book2, cls.book4)
cls.house1 = House.objects.create(name="b1", address="1")
cls.house2 = House.objects.create(name="b2", address="2")
cls.room1 = Room.objects.create(name="a1", house=cls.house1)
cls.room2 = Room.objects.create(name="a2", house=cls.house2)
cls.house1.main_room = cls.room1
cls.house1.save()
cls.house2.main_room = cls.room2
cls.house2.save()
def test_unknown(self):
book1 = Book.objects.get(id=self.book1.id)
with self.assertRaises(AttributeError):
@ -58,20 +69,75 @@ class PrefetchRelatedObjectsTests(TestCase):
def test_foreignkey_forward(self):
authors = list(Author.objects.all())
with self.assertNumQueries(1):
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(authors, "first_book")
self.assertNotIn("ORDER BY", ctx.captured_queries[0]["sql"])
with self.assertNumQueries(0):
[author.first_book for author in authors]
authors = list(Author.objects.all())
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(
authors,
Prefetch("first_book", queryset=Book.objects.order_by("-title")),
)
self.assertNotIn("ORDER BY", ctx.captured_queries[0]["sql"])
def test_foreignkey_reverse(self):
books = list(Book.objects.all())
with self.assertNumQueries(1):
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(books, "first_time_authors")
self.assertIn("ORDER BY", ctx.captured_queries[0]["sql"])
with self.assertNumQueries(0):
[list(book.first_time_authors.all()) for book in books]
books = list(Book.objects.all())
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(
books,
Prefetch(
"first_time_authors",
queryset=Author.objects.order_by("-name"),
),
)
self.assertIn("ORDER BY", ctx.captured_queries[0]["sql"])
def test_one_to_one_forward(self):
houses = list(House.objects.all())
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(houses, "main_room")
self.assertNotIn("ORDER BY", ctx.captured_queries[0]["sql"])
with self.assertNumQueries(0):
[house.main_room for house in houses]
houses = list(House.objects.all())
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(
houses,
Prefetch("main_room", queryset=Room.objects.order_by("-name")),
)
self.assertNotIn("ORDER BY", ctx.captured_queries[0]["sql"])
def test_one_to_one_reverse(self):
rooms = list(Room.objects.all())
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(rooms, "main_room_of")
self.assertNotIn("ORDER BY", ctx.captured_queries[0]["sql"])
with self.assertNumQueries(0):
[room.main_room_of for room in rooms]
rooms = list(Room.objects.all())
with self.assertNumQueries(1) as ctx:
prefetch_related_objects(
rooms,
Prefetch("main_room_of", queryset=House.objects.order_by("-name")),
)
self.assertNotIn("ORDER BY", ctx.captured_queries[0]["sql"])
def test_m2m_then_m2m(self):
"""A m2m can be followed through another m2m."""
authors = list(Author.objects.all())