mirror of https://github.com/django/django.git
Fixed #25279 -- Made prefetch_related_objects() public.
This commit is contained in:
parent
d5f89ff6e8
commit
ef33bc2d4d
|
@ -14,7 +14,9 @@ from django.db.models.fields.files import FileField, ImageField # NOQA
|
|||
from django.db.models.fields.proxy import OrderWrt # NOQA
|
||||
from django.db.models.lookups import Lookup, Transform # NOQA
|
||||
from django.db.models.manager import Manager # NOQA
|
||||
from django.db.models.query import Q, Prefetch, QuerySet # NOQA
|
||||
from django.db.models.query import ( # NOQA
|
||||
Q, Prefetch, QuerySet, prefetch_related_objects,
|
||||
)
|
||||
|
||||
# Imports that would create circular imports if sorted
|
||||
from django.db.models.base import Model # NOQA isort:skip
|
||||
|
|
|
@ -654,7 +654,7 @@ class QuerySet(object):
|
|||
|
||||
def _prefetch_related_objects(self):
|
||||
# This method can only be called once the result cache has been filled.
|
||||
prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
|
||||
prefetch_related_objects(self._result_cache, *self._prefetch_related_lookups)
|
||||
self._prefetch_done = True
|
||||
|
||||
##################################################
|
||||
|
@ -1368,15 +1368,12 @@ def normalize_prefetch_lookups(lookups, prefix=None):
|
|||
return ret
|
||||
|
||||
|
||||
def prefetch_related_objects(result_cache, related_lookups):
|
||||
def prefetch_related_objects(model_instances, *related_lookups):
|
||||
"""
|
||||
Helper function for prefetch_related functionality
|
||||
|
||||
Populates prefetched objects caches for a list of results
|
||||
from a QuerySet
|
||||
Populate prefetched object caches for a list of model instances based on
|
||||
the lookups/Prefetch instances given.
|
||||
"""
|
||||
|
||||
if len(result_cache) == 0:
|
||||
if len(model_instances) == 0:
|
||||
return # nothing to do
|
||||
|
||||
related_lookups = normalize_prefetch_lookups(related_lookups)
|
||||
|
@ -1401,7 +1398,7 @@ def prefetch_related_objects(result_cache, related_lookups):
|
|||
|
||||
# Top level, the list of objects to decorate is the result cache
|
||||
# from the primary QuerySet. It won't be for deeper levels.
|
||||
obj_list = result_cache
|
||||
obj_list = model_instances
|
||||
|
||||
through_attrs = lookup.prefetch_through.split(LOOKUP_SEP)
|
||||
for level, through_attr in enumerate(through_attrs):
|
||||
|
|
|
@ -920,6 +920,10 @@ results; these ``QuerySets`` are then used in the ``self.toppings.all()`` calls.
|
|||
The additional queries in ``prefetch_related()`` are executed after the
|
||||
``QuerySet`` has begun to be evaluated and the primary query has been executed.
|
||||
|
||||
If you have an iterable of model instances, you can prefetch related attributes
|
||||
on those instances using the :func:`~django.db.models.prefetch_related_objects`
|
||||
function.
|
||||
|
||||
Note that the result cache of the primary ``QuerySet`` and all specified related
|
||||
objects will then be fully loaded into memory. This changes the typical
|
||||
behavior of ``QuerySets``, which normally try to avoid loading all objects into
|
||||
|
@ -2998,8 +3002,8 @@ by the aggregate.
|
|||
|
||||
.. _SQLite documentation: https://www.sqlite.org/contrib
|
||||
|
||||
Query-related classes
|
||||
=====================
|
||||
Query-related tools
|
||||
===================
|
||||
|
||||
This section provides reference material for query-related tools not documented
|
||||
elsewhere.
|
||||
|
@ -3064,3 +3068,21 @@ attribute:
|
|||
provide a significant speed improvement over traditional
|
||||
``prefetch_related`` calls which store the cached result within a
|
||||
``QuerySet`` instance.
|
||||
|
||||
``prefetch_related_objects()``
|
||||
------------------------------
|
||||
|
||||
.. function:: prefetch_related_objects(model_instances, *related_lookups)
|
||||
|
||||
.. versionadded:: 1.10
|
||||
|
||||
Prefetches the given lookups on an iterable of model instances. This is useful
|
||||
in code that receives a list of model instances as opposed to a ``QuerySet``;
|
||||
for example, when fetching models from a cache or instantiating them manually.
|
||||
|
||||
Pass an iterable of model instances (must all be of the same class) and the
|
||||
lookups or :class:`Prefetch` objects you want to prefetch for. For example::
|
||||
|
||||
>>> from django.db.models import prefetch_related_objects
|
||||
>>> restaurants = fetch_top_restaurants_from_cache() # A list of Restaurants
|
||||
>>> prefetch_related_objects(restaurants, 'pizzas__toppings')
|
||||
|
|
|
@ -312,6 +312,9 @@ Models
|
|||
app label and class interpolation using the ``'%(app_label)s'`` and
|
||||
``'%(class)s'`` strings.
|
||||
|
||||
* The :func:`~django.db.models.prefetch_related_objects` function is now a
|
||||
public API.
|
||||
|
||||
Requests and Responses
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
from django.db.models import Prefetch, prefetch_related_objects
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import Author, Book, Reader
|
||||
|
||||
|
||||
class PrefetchRelatedObjectsTests(TestCase):
|
||||
"""
|
||||
Since prefetch_related_objects() is just the inner part of
|
||||
prefetch_related(), only do basic tests to ensure its API hasn't changed.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.book1 = Book.objects.create(title='Poems')
|
||||
cls.book2 = Book.objects.create(title='Jane Eyre')
|
||||
cls.book3 = Book.objects.create(title='Wuthering Heights')
|
||||
cls.book4 = Book.objects.create(title='Sense and Sensibility')
|
||||
|
||||
cls.author1 = Author.objects.create(name='Charlotte', first_book=cls.book1)
|
||||
cls.author2 = Author.objects.create(name='Anne', first_book=cls.book1)
|
||||
cls.author3 = Author.objects.create(name='Emily', first_book=cls.book1)
|
||||
cls.author4 = Author.objects.create(name='Jane', first_book=cls.book4)
|
||||
|
||||
cls.book1.authors.add(cls.author1, cls.author2, cls.author3)
|
||||
cls.book2.authors.add(cls.author1)
|
||||
cls.book3.authors.add(cls.author3)
|
||||
cls.book4.authors.add(cls.author4)
|
||||
|
||||
cls.reader1 = Reader.objects.create(name='Amy')
|
||||
cls.reader2 = Reader.objects.create(name='Belinda')
|
||||
|
||||
cls.reader1.books_read.add(cls.book1, cls.book4)
|
||||
cls.reader2.books_read.add(cls.book2, cls.book4)
|
||||
|
||||
def test_unknown(self):
|
||||
book1 = Book.objects.get(id=self.book1.id)
|
||||
with self.assertRaises(AttributeError):
|
||||
prefetch_related_objects([book1], 'unknown_attribute')
|
||||
|
||||
def test_m2m_forward(self):
|
||||
book1 = Book.objects.get(id=self.book1.id)
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects([book1], 'authors')
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
self.assertEqual(set(book1.authors.all()), {self.author1, self.author2, self.author3})
|
||||
|
||||
def test_m2m_reverse(self):
|
||||
author1 = Author.objects.get(id=self.author1.id)
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects([author1], 'books')
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
self.assertEqual(set(author1.books.all()), {self.book1, self.book2})
|
||||
|
||||
def test_foreignkey_forward(self):
|
||||
authors = list(Author.objects.all())
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects(authors, 'first_book')
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
[author.first_book for author in authors]
|
||||
|
||||
def test_foreignkey_reverse(self):
|
||||
books = list(Book.objects.all())
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects(books, 'first_time_authors')
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
[list(book.first_time_authors.all()) for book in books]
|
||||
|
||||
def test_m2m_then_m2m(self):
|
||||
"""
|
||||
We can follow a m2m and another m2m.
|
||||
"""
|
||||
authors = list(Author.objects.all())
|
||||
with self.assertNumQueries(2):
|
||||
prefetch_related_objects(authors, 'books__read_by')
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
self.assertEqual(
|
||||
[
|
||||
[[str(r) for r in b.read_by.all()] for b in a.books.all()]
|
||||
for a in authors
|
||||
],
|
||||
[
|
||||
[['Amy'], ['Belinda']], # Charlotte - Poems, Jane Eyre
|
||||
[['Amy']], # Anne - Poems
|
||||
[['Amy'], []], # Emily - Poems, Wuthering Heights
|
||||
[['Amy', 'Belinda']], # Jane - Sense and Sense
|
||||
]
|
||||
)
|
||||
|
||||
def test_prefetch_object(self):
|
||||
book1 = Book.objects.get(id=self.book1.id)
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects([book1], Prefetch('authors'))
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
self.assertEqual(set(book1.authors.all()), {self.author1, self.author2, self.author3})
|
||||
|
||||
def test_prefetch_object_to_attr(self):
|
||||
book1 = Book.objects.get(id=self.book1.id)
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects([book1], Prefetch('authors', to_attr='the_authors'))
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
self.assertEqual(set(book1.the_authors), {self.author1, self.author2, self.author3})
|
||||
|
||||
def test_prefetch_queryset(self):
|
||||
book1 = Book.objects.get(id=self.book1.id)
|
||||
with self.assertNumQueries(1):
|
||||
prefetch_related_objects(
|
||||
[book1],
|
||||
Prefetch('authors', queryset=Author.objects.filter(id__in=[self.author1.id, self.author2.id]))
|
||||
)
|
||||
|
||||
with self.assertNumQueries(0):
|
||||
self.assertEqual(set(book1.authors.all()), {self.author1, self.author2})
|
Loading…
Reference in New Issue