mirror of
				https://github.com/django/django.git
				synced 2025-10-30 17:16:10 +00:00 
			
		
		
		
	Fixed #28600 -- Added prefetch_related() support to RawQuerySet.
This commit is contained in:
		| @@ -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): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user