Fixed #28600 -- Added prefetch_related() support to RawQuerySet.
This commit is contained in:
parent
f2026ca5e2
commit
534d8d875e
|
@ -727,7 +727,9 @@ class QuerySet:
|
||||||
def raw(self, raw_query, params=None, translations=None, using=None):
|
def raw(self, raw_query, params=None, translations=None, using=None):
|
||||||
if using is None:
|
if using is None:
|
||||||
using = self.db
|
using = self.db
|
||||||
return RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
|
qs = RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
|
||||||
|
qs._prefetch_related_lookups = self._prefetch_related_lookups[:]
|
||||||
|
return qs
|
||||||
|
|
||||||
def _values(self, *fields, **expressions):
|
def _values(self, *fields, **expressions):
|
||||||
clone = self._chain()
|
clone = self._chain()
|
||||||
|
@ -1278,6 +1280,8 @@ class RawQuerySet:
|
||||||
self.params = params or ()
|
self.params = params or ()
|
||||||
self.translations = translations or {}
|
self.translations = translations or {}
|
||||||
self._result_cache = None
|
self._result_cache = None
|
||||||
|
self._prefetch_related_lookups = ()
|
||||||
|
self._prefetch_done = False
|
||||||
|
|
||||||
def resolve_model_init_order(self):
|
def resolve_model_init_order(self):
|
||||||
"""Resolve the init field names and value positions."""
|
"""Resolve the init field names and value positions."""
|
||||||
|
@ -1289,9 +1293,33 @@ class RawQuerySet:
|
||||||
model_init_names = [f.attname for f in model_init_fields]
|
model_init_names = [f.attname for f in model_init_fields]
|
||||||
return model_init_names, model_init_order, annotation_fields
|
return model_init_names, model_init_order, annotation_fields
|
||||||
|
|
||||||
|
def prefetch_related(self, *lookups):
|
||||||
|
"""Same as QuerySet.prefetch_related()"""
|
||||||
|
clone = self._clone()
|
||||||
|
if lookups == (None,):
|
||||||
|
clone._prefetch_related_lookups = ()
|
||||||
|
else:
|
||||||
|
clone._prefetch_related_lookups = clone._prefetch_related_lookups + lookups
|
||||||
|
return clone
|
||||||
|
|
||||||
|
def _prefetch_related_objects(self):
|
||||||
|
prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
|
||||||
|
self._prefetch_done = True
|
||||||
|
|
||||||
|
def _clone(self):
|
||||||
|
"""Same as QuerySet._clone()"""
|
||||||
|
c = self.__class__(
|
||||||
|
self.raw_query, model=self.model, query=self.query, params=self.params,
|
||||||
|
translations=self.translations, using=self._db, hints=self._hints
|
||||||
|
)
|
||||||
|
c._prefetch_related_lookups = self._prefetch_related_lookups[:]
|
||||||
|
return c
|
||||||
|
|
||||||
def _fetch_all(self):
|
def _fetch_all(self):
|
||||||
if self._result_cache is None:
|
if self._result_cache is None:
|
||||||
self._result_cache = list(self.iterator())
|
self._result_cache = list(self.iterator())
|
||||||
|
if self._prefetch_related_lookups and not self._prefetch_done:
|
||||||
|
self._prefetch_related_objects()
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
self._fetch_all()
|
self._fetch_all()
|
||||||
|
|
|
@ -239,6 +239,8 @@ Models
|
||||||
* The new :meth:`.QuerySet.explain` method displays the database's execution
|
* The new :meth:`.QuerySet.explain` method displays the database's execution
|
||||||
plan of a queryset's query.
|
plan of a queryset's query.
|
||||||
|
|
||||||
|
* :meth:`.QuerySet.raw` now supports :meth:`~.QuerySet.prefetch_related`.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ from .models import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PrefetchRelatedTests(TestCase):
|
class TestDataMixin:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.book1 = Book.objects.create(title='Poems')
|
cls.book1 = Book.objects.create(title='Poems')
|
||||||
|
@ -38,6 +38,8 @@ class PrefetchRelatedTests(TestCase):
|
||||||
cls.reader1.books_read.add(cls.book1, cls.book4)
|
cls.reader1.books_read.add(cls.book1, cls.book4)
|
||||||
cls.reader2.books_read.add(cls.book2, cls.book4)
|
cls.reader2.books_read.add(cls.book2, cls.book4)
|
||||||
|
|
||||||
|
|
||||||
|
class PrefetchRelatedTests(TestDataMixin, TestCase):
|
||||||
def assertWhereContains(self, sql, needle):
|
def assertWhereContains(self, sql, needle):
|
||||||
where_idx = sql.index('WHERE')
|
where_idx = sql.index('WHERE')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -281,6 +283,38 @@ class PrefetchRelatedTests(TestCase):
|
||||||
self.assertWhereContains(sql, self.author1.id)
|
self.assertWhereContains(sql, self.author1.id)
|
||||||
|
|
||||||
|
|
||||||
|
class RawQuerySetTests(TestDataMixin, TestCase):
|
||||||
|
def test_basic(self):
|
||||||
|
with self.assertNumQueries(2):
|
||||||
|
books = Book.objects.raw(
|
||||||
|
"SELECT * FROM prefetch_related_book WHERE id = %s",
|
||||||
|
(self.book1.id,)
|
||||||
|
).prefetch_related('authors')
|
||||||
|
book1 = list(books)[0]
|
||||||
|
|
||||||
|
with self.assertNumQueries(0):
|
||||||
|
self.assertCountEqual(book1.authors.all(), [self.author1, self.author2, self.author3])
|
||||||
|
|
||||||
|
def test_prefetch_before_raw(self):
|
||||||
|
with self.assertNumQueries(2):
|
||||||
|
books = Book.objects.prefetch_related('authors').raw(
|
||||||
|
"SELECT * FROM prefetch_related_book WHERE id = %s",
|
||||||
|
(self.book1.id,)
|
||||||
|
)
|
||||||
|
book1 = list(books)[0]
|
||||||
|
|
||||||
|
with self.assertNumQueries(0):
|
||||||
|
self.assertCountEqual(book1.authors.all(), [self.author1, self.author2, self.author3])
|
||||||
|
|
||||||
|
def test_clear(self):
|
||||||
|
with self.assertNumQueries(5):
|
||||||
|
with_prefetch = Author.objects.raw(
|
||||||
|
"SELECT * FROM prefetch_related_author"
|
||||||
|
).prefetch_related('books')
|
||||||
|
without_prefetch = with_prefetch.prefetch_related(None)
|
||||||
|
[list(a.books.all()) for a in without_prefetch]
|
||||||
|
|
||||||
|
|
||||||
class CustomPrefetchTests(TestCase):
|
class CustomPrefetchTests(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def traverse_qs(cls, obj_iter, path):
|
def traverse_qs(cls, obj_iter, path):
|
||||||
|
|
Loading…
Reference in New Issue