summaryrefslogtreecommitdiff
path: root/docs/ref
diff options
context:
space:
mode:
authorLoic Bistuer <loic.bistuer@sixmedia.com>2013-11-07 00:25:05 +0700
committerAnssi Kääriäinen <akaariai@gmail.com>2013-11-07 14:49:49 +0200
commitf51c1f590085556abca44fd2a49618162203b2ec (patch)
tree4577d32e1e99bb7f1032ee394a80692e552846e5 /docs/ref
parentb1b04df06522b092a9b4768d2d61c52956e00eca (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.txt29
-rw-r--r--docs/ref/models/querysets.txt87
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
~~~~~