From 0560bfb705687c831e2769b1202706e2ceb1f7a7 Mon Sep 17 00:00:00 2001
From: Ramiro Morales <cramm0@gmail.com>
Date: Mon, 11 Feb 2013 12:49:30 -0300
Subject: [PATCH] Mention backward relationships in aggregate docs.

Thanks Anssi and Marc Tamlyn for reviewing.

Fixes #19803.
---
 docs/topics/db/aggregation.txt | 53 +++++++++++++++++++++++++++++++---
 1 file changed, 49 insertions(+), 4 deletions(-)

diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt
index 3a4d287864..49134e24c0 100644
--- a/docs/topics/db/aggregation.txt
+++ b/docs/topics/db/aggregation.txt
@@ -21,14 +21,12 @@ used to track the inventory for a series of online bookstores:
     class Author(models.Model):
        name = models.CharField(max_length=100)
        age = models.IntegerField()
-       friends = models.ManyToManyField('self', blank=True)
 
     class Publisher(models.Model):
        name = models.CharField(max_length=300)
        num_awards = models.IntegerField()
 
     class Book(models.Model):
-       isbn = models.CharField(max_length=9)
        name = models.CharField(max_length=300)
        pages = models.IntegerField()
        price = models.DecimalField(max_digits=10, decimal_places=2)
@@ -40,6 +38,7 @@ used to track the inventory for a series of online bookstores:
     class Store(models.Model):
        name = models.CharField(max_length=300)
        books = models.ManyToManyField(Book)
+       registered_users = models.PositiveIntegerField()
 
 Cheat sheet
 ===========
@@ -64,6 +63,9 @@ In a hurry? Here's how to do common aggregate queries, assuming the models above
     >>> Book.objects.all().aggregate(Max('price'))
     {'price__max': Decimal('81.20')}
 
+    # All the following queries involve traversing the Book<->Publisher
+    # many-to-many relationship backward
+
     # Each publisher, each with a count of books as a "num_books" attribute.
     >>> from django.db.models import Count
     >>> pubs = Publisher.objects.annotate(num_books=Count('book'))
@@ -73,7 +75,6 @@ In a hurry? Here's how to do common aggregate queries, assuming the models above
     73
 
     # The top 5 publishers, in order by number of books.
-    >>> from django.db.models import Count
     >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
     >>> pubs[0].num_books
     1323
@@ -169,7 +170,7 @@ specify the annotation::
 Unlike ``aggregate()``, ``annotate()`` is *not* a terminal clause. The output
 of the ``annotate()`` clause is a ``QuerySet``; this ``QuerySet`` can be
 modified using any other ``QuerySet`` operation, including ``filter()``,
-``order_by``, or even additional calls to ``annotate()``.
+``order_by()``, or even additional calls to ``annotate()``.
 
 Joins and aggregates
 ====================
@@ -205,6 +206,50 @@ issue the query::
 
     >>> Store.objects.aggregate(youngest_age=Min('books__authors__age'))
 
+Following relationships backwards
+---------------------------------
+
+In a way similar to :ref:`lookups-that-span-relationships`, aggregations and
+annotations on fields of models or models that are related to the one you are
+querying can include traversing "reverse" relationships. The lowercase name
+of related models and double-underscores are used here too.
+
+For example, we can ask for all publishers, annotated with their respective
+total book stock counters (note how we use `'book'` to specify the
+Publisher->Book reverse foreign key hop)::
+
+    >>> from django.db.models import Count, Min, Sum, Max, Avg
+    >>> Publisher.objects.annotate(Count('book'))
+
+(Every Publisher in the resulting QuerySet will have an extra attribute called
+``book__count``.)
+
+We can also ask for the oldest book of any of those managed by every publisher::
+
+    >>> Publisher.objects.aggregate(oldest_pubdate=Min('book__pubdate'))
+
+(The resulting dictionary will have a key called ``'oldest_pubdate'``. If no
+such alias was specified, it would be the rather long ``'book__pubdate__min'``.)
+
+This doesn't apply just to foreign keys. It also works with many-to-many
+relations. For example, we can ask for every author, annotated with the total
+number of pages considering all the books he/she has (co-)authored (note how we
+use `'book'` to specify the Author->Book reverse many-to-many hop)::
+
+    >>> Author.objects.annotate(total_pages=Sum('book__pages'))
+
+(Every Author in the resulting QuerySet will have an extra attribute called
+``total_pages``. If no such alias was specified, it would be the rather long
+``book__pages__sum``.)
+
+Or ask for the average rating of all the books written by author(s) we have on
+file::
+
+    >>> Author.objects.aggregate(average_rating=Avg('book__rating'))
+
+(The resulting dictionary will have a key called ``'average__rating'``. If no
+such alias was specified, it would be the rather long ``'book__rating__avg'``.)
+
 Aggregations and other QuerySet clauses
 =======================================