From e3654e9da731fedca85bb31e4fa1afe252f88268 Mon Sep 17 00:00:00 2001
From: Andre Terra <andreterra@gmail.com>
Date: Mon, 28 May 2012 18:22:27 -0300
Subject: [PATCH] Added conditional aggregates.

---
 django/db/models/aggregates.py        |    9 ++-
 django/db/models/expressions.py       |    8 +-
 django/db/models/sql/aggregates.py    |   43 +++++++++++---
 django/db/models/sql/compiler.py      |   39 ++++++++-----
 django/db/models/sql/expressions.py   |   16 ++++--
 django/db/models/sql/query.py         |  103 ++++++++++++++++++++------------
 django/db/models/sql/where.py         |   10 +++
 tests/modeltests/aggregation/tests.py |   93 +++++++++++++++++++++++++++++
 8 files changed, 247 insertions(+), 74 deletions(-)
 mode change 100644 => 100755 django/db/models/sql/aggregates.py
 mode change 100644 => 100755 django/db/models/sql/where.py

diff --git a/django/db/models/aggregates.py b/django/db/models/aggregates.py
index a2349cf..d816aa7 100644
--- a/django/db/models/aggregates.py
+++ b/django/db/models/aggregates.py
@@ -6,10 +6,11 @@ class Aggregate(object):
     """
     Default Aggregate definition.
     """
-    def __init__(self, lookup, **extra):
+    def __init__(self, lookup, only=None, **extra):
         """Instantiate a new aggregate.
 
          * lookup is the field on which the aggregate operates.
+         * only is a Q-object used in conditional aggregation.
          * extra is a dictionary of additional data to provide for the
            aggregate definition
 
@@ -18,8 +19,12 @@ class Aggregate(object):
         """
         self.lookup = lookup
         self.extra = extra
+        self.only = only
+        self.condition = None
 
     def _default_alias(self):
+        if hasattr(self.lookup, 'evaluate'):
+             raise ValueError('When aggregating over an expression, you need to give an alias.')
         return '%s__%s' % (self.lookup, self.name.lower())
     default_alias = property(_default_alias)
 
@@ -42,7 +47,7 @@ class Aggregate(object):
            summary value rather than an annotation.
         """
         klass = getattr(query.aggregates_module, self.name)
-        aggregate = klass(col, source=source, is_summary=is_summary, **self.extra)
+        aggregate = klass(col, source=source, is_summary=is_summary, condition=self.condition, **self.extra)
         query.aggregates[alias] = aggregate
 
 class Avg(Aggregate):
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index a71f4a3..390f475 100644
--- a/django/db/models/expressions.py
+++ b/django/db/models/expressions.py
@@ -39,8 +39,8 @@ class ExpressionNode(tree.Node):
     # VISITOR METHODS #
     ###################
 
-    def prepare(self, evaluator, query, allow_joins):
-        return evaluator.prepare_node(self, query, allow_joins)
+    def prepare(self, evaluator, query, allow_joins, promote_joins=False):
+        return evaluator.prepare_node(self, query, allow_joins, promote_joins)
 
     def evaluate(self, evaluator, qn, connection):
         return evaluator.evaluate_node(self, qn, connection)
@@ -107,8 +107,8 @@ class F(ExpressionNode):
         obj.name = self.name
         return obj
 
-    def prepare(self, evaluator, query, allow_joins):
-        return evaluator.prepare_leaf(self, query, allow_joins)
+    def prepare(self, evaluator, query, allow_joins, promote_joins=False):
+        return evaluator.prepare_leaf(self, query, allow_joins, promote_joins)
 
     def evaluate(self, evaluator, qn, connection):
         return evaluator.evaluate_leaf(self, qn, connection)
diff --git a/django/db/models/sql/aggregates.py b/django/db/models/sql/aggregates.py
old mode 100644
new mode 100755
index b41314a..5fe2215
--- a/django/db/models/sql/aggregates.py
+++ b/django/db/models/sql/aggregates.py
@@ -3,6 +3,7 @@ Classes to represent the default SQL aggregate functions
 """
 
 from django.db.models.fields import IntegerField, FloatField
+from django.db.models.sql.expressions import SQLEvaluator
 
 # Fake fields used to identify aggregate types in data-conversion operations.
 ordinal_aggregate_field = IntegerField()
@@ -15,8 +16,9 @@ class Aggregate(object):
     is_ordinal = False
     is_computed = False
     sql_template = '%(function)s(%(field)s)'
+    conditional_template = "CASE WHEN %(condition)s THEN %(field_name)s ELSE null END"
 
-    def __init__(self, col, source=None, is_summary=False, **extra):
+    def __init__(self, col, source=None, is_summary=False, condition=None, **extra):
         """Instantiate an SQL aggregate
 
          * col is a column reference describing the subject field
@@ -26,8 +28,9 @@ class Aggregate(object):
            the column reference. If the aggregate is not an ordinal or
            computed type, this reference is used to determine the coerced
            output type of the aggregate.
+         * condition is used in conditional aggregation.
          * extra is a dictionary of additional data to provide for the
-           aggregate definition
+           aggregate definition.
 
         Also utilizes the class variables:
          * sql_function, the name of the SQL function that implements the
@@ -35,7 +38,7 @@ class Aggregate(object):
          * sql_template, a template string that is used to render the
            aggregate into SQL.
          * is_ordinal, a boolean indicating if the output of this aggregate
-           is an integer (e.g., a count)
+           is an integer (e.g., a count).
          * is_computed, a boolean indicating if this output of this aggregate
            is a computed float (e.g., an average), regardless of the input
            type.
@@ -45,6 +48,7 @@ class Aggregate(object):
         self.source = source
         self.is_summary = is_summary
         self.extra = extra
+        self.condition = condition
 
         # Follow the chain of aggregate sources back until you find an
         # actual field, or an aggregate that forces a particular output
@@ -65,24 +69,45 @@ class Aggregate(object):
     def relabel_aliases(self, change_map):
         if isinstance(self.col, (list, tuple)):
             self.col = (change_map.get(self.col[0], self.col[0]), self.col[1])
+        else:
+            self.col.relabel_aliases(change_map)
+        if self.condition:
+            self.condition.relabel_aliases(change_map)
 
     def as_sql(self, qn, connection):
         "Return the aggregate, rendered as SQL."
 
+        condition_params = []
+        col_params = []
         if hasattr(self.col, 'as_sql'):
-            field_name = self.col.as_sql(qn, connection)
+            if isinstance(self.col, SQLEvaluator):
+                field_name, col_params = self.col.as_sql(qn, connection)
+            else:
+                field_name = self.col.as_sql(qn, connection)
         elif isinstance(self.col, (list, tuple)):
             field_name = '.'.join([qn(c) for c in self.col])
         else:
             field_name = self.col
 
-        params = {
-            'function': self.sql_function,
-            'field': field_name
-        }
+        if self.condition:
+            condition, condition_params = self.condition.as_sql(qn, connection)
+            conditional_field = self.conditional_template % {
+                'condition': condition,
+                'field_name': field_name
+            }
+            params = {
+                'function': self.sql_function,
+                'field': conditional_field,
+            }
+        else:
+            params = {
+                'function': self.sql_function,
+                'field': field_name
+            }
         params.update(self.extra)
 
-        return self.sql_template % params
+        condition_params.extend(col_params)
+        return (self.sql_template % params, condition_params)
 
 
 class Avg(Aggregate):
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 5801b2f..fa1b15a 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -68,7 +68,7 @@ class SQLCompiler(object):
         # as the pre_sql_setup will modify query state in a way that forbids
         # another run of it.
         self.refcounts_before = self.query.alias_refcount.copy()
-        out_cols = self.get_columns(with_col_aliases)
+        out_cols, c_params = self.get_columns(with_col_aliases)
         ordering, ordering_group_by = self.get_ordering()
 
         distinct_fields = self.get_distinct()
@@ -84,6 +84,8 @@ class SQLCompiler(object):
         params = []
         for val in self.query.extra_select.itervalues():
             params.extend(val[1])
+        # Extra-select comes before aggregation in the select list
+        params.extend(c_params)
 
         result = ['SELECT']
 
@@ -178,6 +180,7 @@ class SQLCompiler(object):
         qn = self.quote_name_unless_alias
         qn2 = self.connection.ops.quote_name
         result = ['(%s) AS %s' % (col[0], qn2(alias)) for alias, col in self.query.extra_select.iteritems()]
+        query_params = []
         aliases = set(self.query.extra_select.keys())
         if with_aliases:
             col_aliases = aliases.copy()
@@ -220,15 +223,17 @@ class SQLCompiler(object):
             aliases.update(new_aliases)
 
         max_name_length = self.connection.ops.max_name_length()
-        result.extend([
-            '%s%s' % (
-                aggregate.as_sql(qn, self.connection),
-                alias is not None
-                    and ' AS %s' % qn(truncate_name(alias, max_name_length))
-                    or ''
+        for alias, aggregate in self.query.aggregate_select.items():
+            sql, params = aggregate.as_sql(qn, self.connection)
+            result.append(
+                '%s%s' % (
+                    sql,
+                    alias is not None
+                       and ' AS %s' % qn(truncate_name(alias, max_name_length))
+                       or ''
+                )
             )
-            for alias, aggregate in self.query.aggregate_select.items()
-        ])
+            query_params.extend(params)
 
         for table, col in self.query.related_select_cols:
             r = '%s.%s' % (qn(table), qn(col))
@@ -243,7 +248,7 @@ class SQLCompiler(object):
                 col_aliases.add(col)
 
         self._select_aliases = aliases
-        return result
+        return result, query_params
 
     def get_default_columns(self, with_aliases=False, col_aliases=None,
             start_alias=None, opts=None, as_pairs=False, local_only=False):
@@ -1053,15 +1058,19 @@ class SQLAggregateCompiler(SQLCompiler):
         """
         if qn is None:
             qn = self.quote_name_unless_alias
+        buf = []
+        a_params = []
+        for aggregate in self.query.aggregate_select.values():
+            sql, query_params = aggregate.as_sql(qn, self.connection)
+            buf.append(sql)
+            a_params.extend(query_params)
+        aggregate_sql = ', '.join(buf)
 
         sql = ('SELECT %s FROM (%s) subquery' % (
-            ', '.join([
-                aggregate.as_sql(qn, self.connection)
-                for aggregate in self.query.aggregate_select.values()
-            ]),
+            aggregate_sql,
             self.query.subquery)
         )
-        params = self.query.sub_params
+        params = tuple(a_params) + (self.query.sub_params)
         return (sql, params)
 
 class SQLDateCompiler(SQLCompiler):
diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py
index 1bbf742..3df33f0 100644
--- a/django/db/models/sql/expressions.py
+++ b/django/db/models/sql/expressions.py
@@ -3,13 +3,13 @@ from django.db.models.fields import FieldDoesNotExist
 from django.db.models.sql.constants import LOOKUP_SEP
 
 class SQLEvaluator(object):
-    def __init__(self, expression, query, allow_joins=True):
+    def __init__(self, expression, query, allow_joins=True, promote_joins=False):
         self.expression = expression
         self.opts = query.get_meta()
         self.cols = {}
 
         self.contains_aggregate = False
-        self.expression.prepare(self, query, allow_joins)
+        self.expression.prepare(self, query, allow_joins, promote_joins)
 
     def prepare(self):
         return self
@@ -28,12 +28,12 @@ class SQLEvaluator(object):
     # Vistor methods for initial expression preparation #
     #####################################################
 
-    def prepare_node(self, node, query, allow_joins):
+    def prepare_node(self, node, query, allow_joins, promote_joins):
         for child in node.children:
             if hasattr(child, 'prepare'):
-                child.prepare(self, query, allow_joins)
+                child.prepare(self, query, allow_joins, promote_joins)
 
-    def prepare_leaf(self, node, query, allow_joins):
+    def prepare_leaf(self, node, query, allow_joins, promote_joins):
         if not allow_joins and LOOKUP_SEP in node.name:
             raise FieldError("Joined field references are not permitted in this query")
 
@@ -48,6 +48,9 @@ class SQLEvaluator(object):
                     field_list, query.get_meta(),
                     query.get_initial_alias(), False)
                 col, _, join_list = query.trim_joins(source, join_list, last, False)
+                if promote_joins:
+                    for column_alias in join_list:
+                        query.promote_alias(column_alias, unconditional=True)
 
                 self.cols[node] = (join_list[-1], col)
             except FieldDoesNotExist:
@@ -65,6 +68,9 @@ class SQLEvaluator(object):
         for child in node.children:
             if hasattr(child, 'evaluate'):
                 sql, params = child.evaluate(self, qn, connection)
+                if isinstance(sql, tuple):
+                    expression_params.extend(sql[1])
+                    sql = sql[0]
             else:
                 sql, params = '%s', (child,)
 
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 7f331bf..a13c3d4 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -974,46 +974,71 @@ class Query(object):
         Adds a single aggregate expression to the Query
         """
         opts = model._meta
-        field_list = aggregate.lookup.split(LOOKUP_SEP)
-        if len(field_list) == 1 and aggregate.lookup in self.aggregates:
-            # Aggregate is over an annotation
-            field_name = field_list[0]
-            col = field_name
-            source = self.aggregates[field_name]
-            if not is_summary:
-                raise FieldError("Cannot compute %s('%s'): '%s' is an aggregate" % (
-                    aggregate.name, field_name, field_name))
-        elif ((len(field_list) > 1) or
-            (field_list[0] not in [i.name for i in opts.fields]) or
-            self.group_by is None or
-            not is_summary):
-            # If:
-            #   - the field descriptor has more than one part (foo__bar), or
-            #   - the field descriptor is referencing an m2m/m2o field, or
-            #   - this is a reference to a model field (possibly inherited), or
-            #   - this is an annotation over a model field
-            # then we need to explore the joins that are required.
-
-            field, source, opts, join_list, last, _ = self.setup_joins(
-                field_list, opts, self.get_initial_alias(), False)
-
-            # Process the join chain to see if it can be trimmed
-            col, _, join_list = self.trim_joins(source, join_list, last, False)
-
-            # If the aggregate references a model or field that requires a join,
-            # those joins must be LEFT OUTER - empty join rows must be returned
-            # in order for zeros to be returned for those aggregates.
-            for column_alias in join_list:
-                self.promote_alias(column_alias, unconditional=True)
-
-            col = (join_list[-1], col)
+        only = aggregate.only
+        if hasattr(aggregate.lookup, 'evaluate'):
+            # If lookup is a query expression, evaluate it
+            col = SQLEvaluator(aggregate.lookup, self, promote_joins=True)
+            # TODO: find out the real source of this field. If any field has
+            # is_computed, then source can be set to is_computed.
+            source = None
         else:
-            # The simplest cases. No joins required -
-            # just reference the provided column alias.
-            field_name = field_list[0]
-            source = opts.get_field(field_name)
-            col = field_name
-
+            field_list = aggregate.lookup.split(LOOKUP_SEP)
+            join_list = []
+            if len(field_list) == 1 and aggregate.lookup in self.aggregates:
+                # Aggregate is over an annotation
+                field_name = field_list[0]
+                col = field_name
+                source = self.aggregates[field_name]
+                if not is_summary:
+                    raise FieldError("Cannot compute %s('%s'): '%s' is an aggregate" % (
+                        aggregate.name, field_name, field_name))
+                if only:
+                    raise FieldError("Cannot use aggregated fields in conditional aggregates")
+            elif ((len(field_list) > 1) or
+                (field_list[0] not in [i.name for i in opts.fields]) or
+                self.group_by is None or
+                not is_summary):
+                # If:
+                #   - the field descriptor has more than one part (foo__bar), or
+                #   - the field descriptor is referencing an m2m/m2o field, or
+                #   - this is a reference to a model field (possibly inherited), or
+                #   - this is an annotation over a model field
+                # then we need to explore the joins that are required.
+
+                field, source, opts, join_list, last, _ = self.setup_joins(
+                    field_list, opts, self.get_initial_alias(), False)
+
+                # Process the join chain to see if it can be trimmed
+                col, _, join_list = self.trim_joins(source, join_list, last, False)
+
+                # If the aggregate references a model or field that requires a join,
+                # those joins must be LEFT OUTER - empty join rows must be returned
+                # in order for zeros to be returned for those aggregates.
+                for column_alias in join_list:
+                    self.promote_alias(column_alias, unconditional=True)
+
+                col = (join_list[-1], col)
+            else:
+                # The simplest cases. No joins required -
+                # just reference the provided column alias.
+                field_name = field_list[0]
+                source = opts.get_field(field_name)
+                col = field_name
+
+        if only:
+            original_where = self.where
+            original_having = self.having
+            aggregate.condition = self.where_class()
+            self.where = aggregate.condition
+            self.having = self.where_class()
+            original_alias_map = self.alias_map.keys()[:]
+            self.add_q(only, used_aliases=set(original_alias_map))
+            if original_alias_map != self.alias_map.keys():
+                raise FieldError("Aggregate's only condition can not require additional joins, Original joins: %s, joins after: %s" % (original_alias_map, self.alias_map.keys()))
+            if self.having.children:
+                raise FieldError("Aggregate's only condition can not reference annotated fields")
+            self.having = original_having
+            self.where = original_where
         # Add the aggregate to the query
         aggregate.add_to_query(self, alias, col=col, source=source, is_summary=is_summary)
 
diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py
old mode 100644
new mode 100755
index 5515bc4..90f96c2
--- a/django/db/models/sql/where.py
+++ b/django/db/models/sql/where.py
@@ -139,6 +139,7 @@ class WhereNode(tree.Node):
         it.
         """
         lvalue, lookup_type, value_annotation, params_or_value = child
+        additional_params = []
         if isinstance(lvalue, Constraint):
             try:
                 lvalue, params = lvalue.process(lookup_type, params_or_value, connection)
@@ -156,6 +157,10 @@ class WhereNode(tree.Node):
         else:
             # A smart object with an as_sql() method.
             field_sql = lvalue.as_sql(qn, connection)
+            if isinstance(field_sql, tuple):
+                # It also returned params
+                additional_params.extend(field_sql[1])
+                field_sql = field_sql[0]
 
         if value_annotation is datetime.datetime:
             cast_sql = connection.ops.datetime_cast_sql()
@@ -164,6 +169,9 @@ class WhereNode(tree.Node):
 
         if hasattr(params, 'as_sql'):
             extra, params = params.as_sql(qn, connection)
+            if isinstance(extra, tuple):
+                params = params + tuple(extra[1])
+                extra = extra[0]
             cast_sql = ''
         else:
             extra = ''
@@ -173,6 +181,8 @@ class WhereNode(tree.Node):
             lookup_type = 'isnull'
             value_annotation = True
 
+        additional_params.extend(params)
+        params = additional_params
         if lookup_type in connection.operators:
             format = "%s %%s %%s" % (connection.ops.lookup_cast(lookup_type),)
             return (format % (field_sql,
diff --git a/tests/modeltests/aggregation/tests.py b/tests/modeltests/aggregation/tests.py
index a35dbb3..1e94784 100644
--- a/tests/modeltests/aggregation/tests.py
+++ b/tests/modeltests/aggregation/tests.py
@@ -4,6 +4,8 @@ import datetime
 from decimal import Decimal
 
 from django.db.models import Avg, Sum, Count, Max, Min
+from django.db.models import Q, F
+from django.core.exceptions import FieldError
 from django.test import TestCase, Approximate
 
 from .models import Author, Publisher, Book, Store
@@ -18,21 +20,48 @@ class BaseAggregateTestCase(TestCase):
     def test_single_aggregate(self):
         vals = Author.objects.aggregate(Avg("age"))
         self.assertEqual(vals, {"age__avg": Approximate(37.4, places=1)})
+        vals = Author.objects.aggregate(Sum("age", only=Q(age__gt=29)))
+        self.assertEqual(vals, {"age__sum": 254})
+        vals = Author.objects.extra(select={'testparams':'age < %s'}, select_params=[0])\
+               .aggregate(Sum("age", only=Q(age__gt=29)))
+        self.assertEqual(vals, {"age__sum": 254})
+        vals = Author.objects.aggregate(Sum("age", only=Q(name__icontains='jaco')|Q(name__icontains='adrian')))
+        self.assertEqual(vals, {"age__sum": 69})
 
     def test_multiple_aggregates(self):
         vals = Author.objects.aggregate(Sum("age"), Avg("age"))
         self.assertEqual(vals, {"age__sum": 337, "age__avg": Approximate(37.4, places=1)})
+        vals = Author.objects.aggregate(Sum("age", only=Q(age__gt=29)), Avg("age"))
+        self.assertEqual(vals, {"age__sum": 254, "age__avg": Approximate(37.4, places=1)})
 
     def test_filter_aggregate(self):
         vals = Author.objects.filter(age__gt=29).aggregate(Sum("age"))
         self.assertEqual(len(vals), 1)
         self.assertEqual(vals["age__sum"], 254)
+        vals = Author.objects.filter(age__gt=29).aggregate(Sum("age", only=Q(age__lt=29)))
+        # If there are no matching aggregates, then None, not 0 is the answer.
+        self.assertEqual(vals["age__sum"], None)
 
     def test_related_aggregate(self):
         vals = Author.objects.aggregate(Avg("friends__age"))
         self.assertEqual(len(vals), 1)
         self.assertAlmostEqual(vals["friends__age__avg"], 34.07, places=2)
 
+        vals = Author.objects.aggregate(Avg("friends__age", only=Q(age__lt=29)))
+        self.assertEqual(len(vals), 1)
+        self.assertAlmostEqual(vals["friends__age__avg"], 33.67, places=2)
+        vals2 = Author.objects.filter(age__lt=29).aggregate(Avg("friends__age"))
+        self.assertEqual(vals, vals2)
+
+        vals = Author.objects.aggregate(Avg("friends__age", only=Q(friends__age__lt=35)))
+        self.assertEqual(len(vals), 1)
+        self.assertAlmostEqual(vals["friends__age__avg"], 28.75, places=2)
+
+        # The average age of author's friends, whose age is lower than the authors age.
+        vals = Author.objects.aggregate(Avg("friends__age", only=Q(friends__age__lt=F('age'))))
+        self.assertEqual(len(vals), 1)
+        self.assertAlmostEqual(vals["friends__age__avg"], 30.43, places=2)
+
         vals = Book.objects.filter(rating__lt=4.5).aggregate(Avg("authors__age"))
         self.assertEqual(len(vals), 1)
         self.assertAlmostEqual(vals["authors__age__avg"], 38.2857, places=2)
@@ -54,6 +83,10 @@ class BaseAggregateTestCase(TestCase):
         self.assertEqual(len(vals), 1)
         self.assertEqual(vals["books__authors__age__max"], 57)
 
+        vals = Store.objects.aggregate(Max("books__authors__age", only=Q(books__authors__age__lt=56)))
+        self.assertEqual(len(vals), 1)
+        self.assertEqual(vals["books__authors__age__max"], 46)
+
         vals = Author.objects.aggregate(Min("book__publisher__num_awards"))
         self.assertEqual(len(vals), 1)
         self.assertEqual(vals["book__publisher__num_awards__min"], 1)
@@ -84,6 +117,34 @@ class BaseAggregateTestCase(TestCase):
         )
         self.assertEqual(b.mean_age, 34.5)
 
+        # Test extra-select
+        books = Book.objects.annotate(mean_age=Avg("authors__age"))
+        books = books.annotate(mean_age2=Avg('authors__age', only=Q(authors__age__gte=0)))
+        books = books.extra(select={'testparams': 'publisher_id = %s'}, select_params=[1])
+        b = books.get(pk=1)
+        self.assertEqual(b.mean_age, 34.5)
+        self.assertEqual(b.mean_age2, 34.5)
+        self.assertEqual(b.testparams, True)
+
+        # Test relabel_aliases
+        excluded_authors = Author.objects.annotate(book_rating=Min(F('book__rating') + 5, only=Q(pk__gte=1)))
+        excluded_authors = excluded_authors.filter(book_rating__lt=0)
+        books = books.exclude(authors__in=excluded_authors)
+        b = books.get(pk=1)
+        self.assertEqual(b.mean_age, 34.5)
+
+        # Test joins in F-based annotation
+        books = Book.objects.annotate(oldest=Max(F('authors__age')))
+        books = books.values_list('rating', 'oldest').order_by('rating', 'oldest')
+        self.assertEqual(
+            list(books),
+            [(3.0, 45), (4.0, 29), (4.0, 37), (4.0, 57), (4.5, 35), (5.0, 57)]
+        )
+
+        publishers = Publisher.objects.annotate(avg_rating=Avg(F('book__rating') - 0))
+        publishers = publishers.values_list('id', 'avg_rating').order_by('id')
+        self.assertEqual(list(publishers), [(1, 4.25), (2, 3.0), (3, 4.0), (4, 5.0), (5, None)])
+
     def test_annotate_m2m(self):
         books = Book.objects.filter(rating__lt=4.5).annotate(Avg("authors__age")).order_by("name")
         self.assertQuerysetEqual(
@@ -109,6 +170,11 @@ class BaseAggregateTestCase(TestCase):
             lambda b: (b.name, b.num_authors)
         )
 
+        def raises_exception():
+            list(Book.objects.annotate(num_authors=Count("authors")).annotate(num_authors2=Count("authors", only=Q(num_authors__gt=1))).order_by("name"))
+
+        self.assertRaises(FieldError, raises_exception)
+
     def test_backwards_m2m_annotate(self):
         authors = Author.objects.filter(name__contains="a").annotate(Avg("book__rating")).order_by("name")
         self.assertQuerysetEqual(
@@ -194,6 +260,16 @@ class BaseAggregateTestCase(TestCase):
                 }
             ]
         )
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age', only=Q(authors__age__lt=35))).values('pk', 'isbn', 'mean_age')
+        self.assertEqual(
+            list(books), [
+                {
+                    "pk": 1,
+                    "isbn": "159059725",
+                    "mean_age": 34.0,
+                }
+            ]
+        )
 
         books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values("name")
         self.assertEqual(
@@ -271,6 +347,16 @@ class BaseAggregateTestCase(TestCase):
 
         vals = Book.objects.aggregate(Count("rating", distinct=True))
         self.assertEqual(vals, {"rating__count": 4})
+        vals = Book.objects.aggregate(
+            low_count=Count("rating", only=Q(rating__lt=4)),
+            high_count=Count("rating", only=Q(rating__gte=4))
+        )
+        self.assertEqual(vals, {"low_count": 1, 'high_count': 5})
+        vals = Book.objects.aggregate(
+            low_count=Count("rating", distinct=True, only=Q(rating__lt=4)),
+            high_count=Count("rating", distinct=True, only=Q(rating__gte=4))
+        )
+        self.assertEqual(vals, {"low_count": 1, 'high_count': 3})
 
     def test_fkey_aggregate(self):
         explicit = list(Author.objects.annotate(Count('book__id')))
@@ -390,6 +476,13 @@ class BaseAggregateTestCase(TestCase):
             ],
             lambda p: p.name,
         )
+        publishers = Publisher.objects.annotate(num_books=Count("book__id", only=Q(book__id__gt=5))).filter(num_books__gt=1, book__price__lt=Decimal("40.0")).order_by("pk")
+        self.assertQuerysetEqual(
+            publishers, [
+                "Expensive Publisher",
+            ],
+            lambda p: p.name,
+        )
 
         publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk")
         self.assertQuerysetEqual(
-- 
1.7.9

