diff options
| author | Loic Bistuer <loic.bistuer@sixmedia.com> | 2013-11-07 00:25:05 +0700 |
|---|---|---|
| committer | Anssi Kääriäinen <akaariai@gmail.com> | 2013-11-07 14:49:49 +0200 |
| commit | f51c1f590085556abca44fd2a49618162203b2ec (patch) | |
| tree | 4577d32e1e99bb7f1032ee394a80692e552846e5 /docs/ref | |
| parent | b1b04df06522b092a9b4768d2d61c52956e00eca (diff) | |
Fixed #17001 -- Custom querysets for prefetch_related.
This patch introduces the Prefetch object which allows customizing prefetch
operations.
This enables things like filtering prefetched relations, calling select_related
from a prefetched relation, or prefetching the same relation multiple times
with different querysets.
When a Prefetch instance specifies a to_attr argument, the result is stored
in a list rather than a QuerySet. This has the fortunate consequence of being
significantly faster. The preformance improvement is due to the fact that we
save the costly creation of a QuerySet instance.
Thanks @akaariai for the original patch and @bmispelon and @timgraham
for the reviews.
Diffstat (limited to 'docs/ref')
| -rw-r--r-- | docs/ref/models/queries.txt | 29 | ||||
| -rw-r--r-- | docs/ref/models/querysets.txt | 87 |
2 files changed, 115 insertions, 1 deletions
diff --git a/docs/ref/models/queries.txt b/docs/ref/models/queries.txt index d07156c0b9..1979c16d40 100644 --- a/docs/ref/models/queries.txt +++ b/docs/ref/models/queries.txt @@ -129,3 +129,32 @@ In general, ``Q() objects`` make it possible to define and reuse conditions. This permits the :ref:`construction of complex database queries <complex-lookups-with-q>` using ``|`` (``OR``) and ``&`` (``AND``) operators; in particular, it is not otherwise possible to use ``OR`` in ``QuerySets``. + +``Prefetch()`` objects +====================== + +.. versionadded:: 1.7 + +.. class:: Prefetch(lookup, queryset=None, to_attr=None) + +The ``Prefetch()`` object can be used to control the operation of +:meth:`~django.db.models.query.QuerySet.prefetch_related()`. + +The ``lookup`` argument describes the relations to follow and works the same +as the string based lookups passed to +:meth:`~django.db.models.query.QuerySet.prefetch_related()`. + +The ``queryset`` argument supplies a base ``QuerySet`` for the given lookup. +This is useful to further filter down the prefetch operation, or to call +:meth:`~django.db.models.query.QuerySet.select_related()` from the prefetched +relation, hence reducing the number of queries even further. + +The ``to_attr`` argument sets the result of the prefetch operation to a custom +attribute. + +.. note:: + + When using ``to_attr`` the prefetched result is stored in a list. + This can provide a significant speed improvement over traditional + ``prefetch_related`` calls which store the cached result within a + ``QuerySet`` instance. diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 170df975bd..d1b6caba8d 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -898,7 +898,7 @@ objects have already been fetched, and it will skip fetching them again. Chaining ``prefetch_related`` calls will accumulate the lookups that are prefetched. To clear any ``prefetch_related`` behavior, pass ``None`` as a -parameter:: +parameter: >>> non_prefetched = qs.prefetch_related(None) @@ -925,6 +925,91 @@ profile for your use case! Note that if you use ``iterator()`` to run the query, ``prefetch_related()`` calls will be ignored since these two optimizations do not make sense together. +.. versionadded:: 1.7 + +You can use the :class:`~django.db.models.Prefetch` object to further control +the prefetch operation. + +In its simplest form ``Prefetch`` is equivalent to the traditional string based +lookups: + + >>> Restaurant.objects.prefetch_related(Prefetch('pizzas__toppings')) + +You can provide a custom queryset with the optional ``queryset`` argument. +This can be used to change the default ordering of the queryset: + + >>> Restaurant.objects.prefetch_related( + ... Prefetch('pizzas__toppings', queryset=Toppings.objects.order_by('name'))) + +Or to call :meth:`~django.db.models.query.QuerySet.select_related()` when +applicable to reduce the number of queries even further: + + >>> Pizza.objects.prefetch_related( + ... Prefetch('restaurants', queryset=Restaurant.objects.select_related('best_pizza'))) + +You can also assign the prefetched result to a custom attribute with the optional +``to_attr`` argument. The result will be stored directly in a list. + +This allows prefetching the same relation multiple times with a different +``QuerySet``; for instance: + + >>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True) + >>> Restaurant.objects.prefetch_related( + ... Prefetch('pizzas', to_attr('menu')), + ... Prefetch('pizzas', queryset=vegetarian_pizzas to_attr='vegetarian_menu')) + +Lookups created with custom ``to_attr`` can still be traversed as usual by other +lookups: + + >>> vegetarian_pizzas = Pizza.objects.filter(vegetarian=True) + >>> Restaurant.objects.prefetch_related( + ... Prefetch('pizzas', queryset=vegetarian_pizzas to_attr='vegetarian_menu'), + ... 'vegetarian_menu__toppings') + +Using ``to_attr`` is recommended when filtering down the prefetch result as it is +less ambiguous than storing a filtered result in the related manager's cache: + + >>> queryset = Pizza.objects.filter(vegetarian=True) + >>> + >>> # Recommended: + >>> restaurants = Restaurant.objects.prefetch_related( + ... Prefetch('pizzas', to_attr='vegetarian_pizzas' queryset=queryset)) + >>> vegetarian_pizzas = restaurants[0].vegetarian_pizzas + >>> + >>> # Not recommended: + >>> restaurants = Restaurant.objects.prefetch_related( + ... Prefetch('pizzas', queryset=queryset)) + >>> vegetarian_pizzas = restaurants[0].pizzas.all() + +.. note:: + + The ordering of lookups matters. + + Take the following examples: + + >>> prefetch_related('pizzas__toppings', 'pizzas') + + This works even though it's unordered because ``'pizzas__toppings'`` + already contains all the needed information, therefore the second argument + ``'pizzas'`` is actually redundant. + + >>> prefetch_related('pizzas__toppings', Prefetch('pizzas', queryset=Pizza.objects.all())) + + This will raise a ``ValueError`` because of the attempt to redefine the + queryset of a previously seen lookup. Note that an implicit queryset was + created to traverse ``'pizzas'`` as part of the ``'pizzas__toppings'`` + lookup. + + >>> prefetch_related('pizza_list__toppings', Prefetch('pizzas', to_attr='pizza_list')) + + This will trigger an ``AttributeError`` because ``'pizza_list'`` doesn't exist yet + when ``'pizza_list__toppings'`` is being processed. + + This consideration is not limited to the use of ``Prefetch`` objects. Some + advanced techniques may require that the lookups be performed in a + specific order to avoid creating extra queries; therefore it's recommended + to always carefully order ``prefetch_related`` arguments. + extra ~~~~~ |
