Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13895 -- Refactored aggregation_regress doctests. Thanks to Al…

…ex Gaynor for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13614 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 1bf25e9bc6df6c2ae4c0b10fb839e101471e8373 1 parent c2e3ba3
Russell Keith-Magee authored August 20, 2010
1  django/test/__init__.py
@@ -4,3 +4,4 @@
4 4
 
5 5
 from django.test.client import Client
6 6
 from django.test.testcases import TestCase, TransactionTestCase
  7
+from django.test.utils import Approximate
19  django/test/utils.py
... ...
@@ -1,4 +1,6 @@
1  
-import sys, time, os
  1
+import sys
  2
+import time
  3
+import os
2 4
 from django.conf import settings
3 5
 from django.core import mail
4 6
 from django.core.mail.backends import locmem
@@ -6,6 +8,21 @@
6 8
 from django.template import Template
7 9
 from django.utils.translation import deactivate
8 10
 
  11
+
  12
+class Approximate(object):
  13
+    def __init__(self, val, places=7):
  14
+        self.val = val
  15
+        self.places = places
  16
+
  17
+    def __repr__(self):
  18
+        return repr(self.val)
  19
+
  20
+    def __eq__(self, other):
  21
+        if self.val == other:
  22
+            return True
  23
+        return round(abs(self.val-other), self.places) == 0
  24
+
  25
+
9 26
 class ContextList(list):
10 27
     """A wrapper that provides direct key access to context items contained
11 28
     in a list of context objects.
15  tests/modeltests/aggregation/tests.py
@@ -2,24 +2,11 @@
2 2
 from decimal import Decimal
3 3
 
4 4
 from django.db.models import Avg, Sum, Count, Max, Min
5  
-from django.test import TestCase
  5
+from django.test import TestCase, Approximate
6 6
 
7 7
 from models import Author, Publisher, Book, Store
8 8
 
9 9
 
10  
-class Approximate(object):
11  
-    def __init__(self, val, places=7):
12  
-        self.val = val
13  
-        self.places = places
14  
-
15  
-    def __repr__(self):
16  
-        return repr(self.val)
17  
-
18  
-    def __eq__(self, other):
19  
-        if self.val == other:
20  
-            return True
21  
-        return round(abs(self.val-other), self.places) == 0
22  
-
23 10
 class BaseAggregateTestCase(TestCase):
24 11
     fixtures = ["initial_data.json"]
25 12
 
323  tests/regressiontests/aggregation_regress/models.py
@@ -4,6 +4,7 @@
4 4
 from django.db import connection, models, DEFAULT_DB_ALIAS
5 5
 from django.conf import settings
6 6
 
  7
+
7 8
 class Author(models.Model):
8 9
     name = models.CharField(max_length=100)
9 10
     age = models.IntegerField()
@@ -12,6 +13,7 @@ class Author(models.Model):
12 13
     def __unicode__(self):
13 14
         return self.name
14 15
 
  16
+
15 17
 class Publisher(models.Model):
16 18
     name = models.CharField(max_length=255)
17 19
     num_awards = models.IntegerField()
@@ -19,6 +21,7 @@ class Publisher(models.Model):
19 21
     def __unicode__(self):
20 22
         return self.name
21 23
 
  24
+
22 25
 class Book(models.Model):
23 26
     isbn = models.CharField(max_length=9)
24 27
     name = models.CharField(max_length=255)
@@ -36,6 +39,7 @@ class Meta:
36 39
     def __unicode__(self):
37 40
         return self.name
38 41
 
  42
+
39 43
 class Store(models.Model):
40 44
     name = models.CharField(max_length=255)
41 45
     books = models.ManyToManyField(Book)
@@ -45,334 +49,21 @@ class Store(models.Model):
45 49
     def __unicode__(self):
46 50
         return self.name
47 51
 
  52
+
48 53
 class Entries(models.Model):
49 54
     EntryID = models.AutoField(primary_key=True, db_column='Entry ID')
50 55
     Entry = models.CharField(unique=True, max_length=50)
51 56
     Exclude = models.BooleanField()
52 57
 
  58
+
53 59
 class Clues(models.Model):
54 60
     ID = models.AutoField(primary_key=True)
55 61
     EntryID = models.ForeignKey(Entries, verbose_name='Entry', db_column = 'Entry ID')
56 62
     Clue = models.CharField(max_length=150)
57 63
 
  64
+
58 65
 class HardbackBook(Book):
59 66
     weight = models.FloatField()
60 67
 
61 68
     def __unicode__(self):
62 69
         return "%s (hardback): %s" % (self.name, self.weight)
63  
-
64  
-__test__ = {'API_TESTS': """
65  
->>> from django.core import management
66  
->>> from django.db.models import get_app, F
67  
-
68  
-# Reset the database representation of this app.
69  
-# This will return the database to a clean initial state.
70  
->>> management.call_command('flush', verbosity=0, interactive=False)
71  
-
72  
->>> from django.db.models import Avg, Sum, Count, Max, Min, StdDev, Variance
73  
-
74  
-# Ordering requests are ignored
75  
->>> Author.objects.all().order_by('name').aggregate(Avg('age'))
76  
-{'age__avg': 37.4...}
77  
-
78  
-# Implicit ordering is also ignored
79  
->>> Book.objects.all().aggregate(Sum('pages'))
80  
-{'pages__sum': 3703}
81  
-
82  
-# Baseline results
83  
->>> Book.objects.all().aggregate(Sum('pages'), Avg('pages'))
84  
-{'pages__sum': 3703, 'pages__avg': 617.1...}
85  
-
86  
-# Empty values query doesn't affect grouping or results
87  
->>> Book.objects.all().values().aggregate(Sum('pages'), Avg('pages'))
88  
-{'pages__sum': 3703, 'pages__avg': 617.1...}
89  
-
90  
-# Aggregate overrides extra selected column
91  
->>> Book.objects.all().extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages'))
92  
-{'pages__sum': 3703}
93  
-
94  
-# Annotations get combined with extra select clauses
95  
->>> sorted((k,v) for k,v in Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).__dict__.items() if k != '_state')
96  
-[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)]
97  
-
98  
-# Order of the annotate/extra in the query doesn't matter
99  
->>> sorted((k,v) for k,v in Book.objects.all().extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2).__dict__.items()if k != '_state')
100  
-[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)]
101  
-
102  
-# Values queries can be combined with annotate and extra
103  
->>> sorted((k,v) for k,v in Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2).items()if k != '_state')
104  
-[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)]
105  
-
106  
-# The order of the (empty) values, annotate and extra clauses doesn't matter
107  
->>> sorted((k,v) for k,v in Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).items()if k != '_state')
108  
-[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)]
109  
-
110  
-# If the annotation precedes the values clause, it won't be included
111  
-# unless it is explicitly named
112  
->>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1).items())
113  
-[('name', u'The Definitive Guide to Django: Web Development Done Right')]
114  
-
115  
->>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name','mean_auth_age').get(pk=1).items())
116  
-[('mean_auth_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right')]
117  
-
118  
-# If an annotation isn't included in the values, it can still be used in a filter
119  
->>> Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2)
120  
-[{'name': u'Python Web Development with Django'}]
121  
-
122  
-# The annotations are added to values output if values() precedes annotate()
123  
->>> sorted(Book.objects.all().values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1).items())
124  
-[('mean_auth_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right')]
125  
-
126  
-# Check that all of the objects are getting counted (allow_nulls) and that values respects the amount of objects
127  
->>> len(Author.objects.all().annotate(Avg('friends__age')).values())
128  
-9
129  
-
130  
-# Check that consecutive calls to annotate accumulate in the query
131  
->>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest', 'price').annotate(Max('publisher__num_awards'))
132  
-[{'price': Decimal("30..."), 'oldest': 35, 'publisher__num_awards__max': 3}, {'price': Decimal("29.69"), 'oldest': 37, 'publisher__num_awards__max': 7}, {'price': Decimal("23.09"), 'oldest': 45, 'publisher__num_awards__max': 1}, {'price': Decimal("75..."), 'oldest': 57, 'publisher__num_awards__max': 9}, {'price': Decimal("82.8..."), 'oldest': 57, 'publisher__num_awards__max': 7}]
133  
-
134  
-# Aggregates can be composed over annotations.
135  
-# The return type is derived from the composed aggregate
136  
->>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('pages'), Max('price'), Sum('num_authors'), Avg('num_authors'))
137  
-{'num_authors__sum': 10, 'num_authors__avg': 1.66..., 'pages__max': 1132, 'price__max': Decimal("82.80")}
138  
-
139  
-# Bad field requests in aggregates are caught and reported
140  
->>> Book.objects.all().aggregate(num_authors=Count('foo'))
141  
-Traceback (most recent call last):
142  
-...
143  
-FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, hardbackbook, id, isbn, name, pages, price, pubdate, publisher, rating, store
144  
-
145  
->>> Book.objects.all().annotate(num_authors=Count('foo'))
146  
-Traceback (most recent call last):
147  
-...
148  
-FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, hardbackbook, id, isbn, name, pages, price, pubdate, publisher, rating, store
149  
-
150  
->>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo'))
151  
-Traceback (most recent call last):
152  
-...
153  
-FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, hardbackbook, id, isbn, name, pages, price, pubdate, publisher, rating, store, num_authors
154  
-
155  
-# Old-style count aggregations can be mixed with new-style
156  
->>> Book.objects.annotate(num_authors=Count('authors')).count()
157  
-6
158  
-
159  
-# Non-ordinal, non-computed Aggregates over annotations correctly inherit
160  
-# the annotation's internal type if the annotation is ordinal or computed
161  
->>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Max('num_authors'))
162  
-{'num_authors__max': 3}
163  
-
164  
->>> Publisher.objects.annotate(avg_price=Avg('book__price')).aggregate(Max('avg_price'))
165  
-{'avg_price__max': 75.0...}
166  
-
167  
-# Aliases are quoted to protected aliases that might be reserved names
168  
->>> Book.objects.aggregate(number=Max('pages'), select=Max('pages'))
169  
-{'number': 1132, 'select': 1132}
170  
-
171  
-# Regression for #10064: select_related() plays nice with aggregates
172  
->>> sorted(Book.objects.select_related('publisher').annotate(num_authors=Count('authors')).values()[0].iteritems())
173  
-[('contact_id', 8), ('id', 5), ('isbn', u'013790395'), ('name', u'Artificial Intelligence: A Modern Approach'), ('num_authors', 2), ('pages', 1132), ('price', Decimal("82.8...")), ('pubdate', datetime.date(1995, 1, 15)), ('publisher_id', 3), ('rating', 4.0)]
174  
-
175  
-# Regression for #10010: exclude on an aggregate field is correctly negated
176  
->>> len(Book.objects.annotate(num_authors=Count('authors')))
177  
-6
178  
->>> len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=2))
179  
-1
180  
->>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__gt=2))
181  
-5
182  
-
183  
->>> len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__lt=3).exclude(num_authors__lt=2))
184  
-2
185  
->>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3))
186  
-2
187  
-
188  
-# Aggregates can be used with F() expressions
189  
-# ... where the F() is pushed into the HAVING clause
190  
->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
191  
-[{'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}]
192  
-
193  
->>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
194  
-[{'num_books': 2, 'name': u'Apress', 'num_awards': 3}, {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, {'num_books': 1, 'name': u'Sams', 'num_awards': 1}]
195  
-
196  
-# ... and where the F() references an aggregate
197  
->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards')
198  
-[{'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}]
199  
-
200  
->>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
201  
-[{'num_books': 2, 'name': u'Apress', 'num_awards': 3}, {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, {'num_books': 1, 'name': u'Sams', 'num_awards': 1}]
202  
-
203  
-# Tests on fields with non-default table and column names.
204  
->>> Clues.objects.values('EntryID__Entry').annotate(Appearances=Count('EntryID'), Distinct_Clues=Count('Clue', distinct=True))
205  
-[]
206  
-
207  
->>> Entries.objects.annotate(clue_count=Count('clues__ID'))
208  
-[]
209  
-
210  
-# Regression for #10089: Check handling of empty result sets with aggregates
211  
->>> Book.objects.filter(id__in=[]).count()
212  
-0
213  
-
214  
->>> Book.objects.filter(id__in=[]).aggregate(num_authors=Count('authors'), avg_authors=Avg('authors'), max_authors=Max('authors'), max_price=Max('price'), max_rating=Max('rating'))
215  
-{'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None}
216  
-
217  
->>> list(Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values()) == [{'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None}]
218  
-True
219  
-
220  
-# Regression for #10113 - Fields mentioned in order_by() must be included in the GROUP BY.
221  
-# This only becomes a problem when the order_by introduces a new join.
222  
->>> Book.objects.annotate(num_authors=Count('authors')).order_by('publisher__name', 'name')
223  
-[<Book: Practical Django Projects>, <Book: The Definitive Guide to Django: Web Development Done Right>, <Book: Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp>, <Book: Artificial Intelligence: A Modern Approach>, <Book: Python Web Development with Django>, <Book: Sams Teach Yourself Django in 24 Hours>]
224  
-
225  
-# Regression for #10127 - Empty select_related() works with annotate
226  
->>> books = Book.objects.all().filter(rating__lt=4.5).select_related().annotate(Avg('authors__age'))
227  
->>> sorted([(b.name, b.authors__age__avg, b.publisher.name, b.contact.name) for b in books])
228  
-[(u'Artificial Intelligence: A Modern Approach', 51.5, u'Prentice Hall', u'Peter Norvig'), (u'Practical Django Projects', 29.0, u'Apress', u'James Bennett'), (u'Python Web Development with Django', 30.3..., u'Prentice Hall', u'Jeffrey Forcier'), (u'Sams Teach Yourself Django in 24 Hours', 45.0, u'Sams', u'Brad Dayley')]
229  
-
230  
-# Regression for #10132 - If the values() clause only mentioned extra(select=) columns, those columns are used for grouping
231  
->>> Book.objects.extra(select={'pub':'publisher_id'}).values('pub').annotate(Count('id')).order_by('pub')
232  
-[{'pub': 1, 'id__count': 2}, {'pub': 2, 'id__count': 1}, {'pub': 3, 'id__count': 2}, {'pub': 4, 'id__count': 1}]
233  
-
234  
->>> Book.objects.extra(select={'pub':'publisher_id','foo':'pages'}).values('pub').annotate(Count('id')).order_by('pub')
235  
-[{'pub': 1, 'id__count': 2}, {'pub': 2, 'id__count': 1}, {'pub': 3, 'id__count': 2}, {'pub': 4, 'id__count': 1}]
236  
-
237  
-# Regression for #10182 - Queries with aggregate calls are correctly realiased when used in a subquery
238  
->>> ids = Book.objects.filter(pages__gt=100).annotate(n_authors=Count('authors')).filter(n_authors__gt=2).order_by('n_authors')
239  
->>> Book.objects.filter(id__in=ids)
240  
-[<Book: Python Web Development with Django>]
241  
-
242  
-# Regression for #10197 -- Queries with aggregates can be pickled.
243  
-# First check that pickling is possible at all. No crash = success
244  
->>> qs = Book.objects.annotate(num_authors=Count('authors'))
245  
->>> out = pickle.dumps(qs)
246  
-
247  
-# Then check that the round trip works.
248  
->>> query = qs.query.get_compiler(qs.db).as_sql()[0]
249  
->>> select_fields = qs.query.select_fields
250  
->>> query2 = pickle.loads(pickle.dumps(qs))
251  
->>> query2.query.get_compiler(query2.db).as_sql()[0] == query
252  
-True
253  
->>> query2.query.select_fields = select_fields
254  
-
255  
-# Regression for #10199 - Aggregate calls clone the original query so the original query can still be used
256  
->>> books = Book.objects.all()
257  
->>> _ = books.aggregate(Avg('authors__age'))
258  
->>> books.all()
259  
-[<Book: Artificial Intelligence: A Modern Approach>, <Book: Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp>, <Book: Practical Django Projects>, <Book: Python Web Development with Django>, <Book: Sams Teach Yourself Django in 24 Hours>, <Book: The Definitive Guide to Django: Web Development Done Right>]
260  
-
261  
-# Regression for #10248 - Annotations work with DateQuerySets
262  
->>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors=2).dates('pubdate', 'day')
263  
-[datetime.datetime(1995, 1, 15, 0, 0), datetime.datetime(2007, 12, 6, 0, 0)]
264  
-
265  
-# Regression for #10290 - extra selects with parameters can be used for
266  
-# grouping.
267  
->>> qs = Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'sheets' : '(pages + %s) / %s'}, select_params=[1, 2]).order_by('sheets').values('sheets')
268  
->>> [int(x['sheets']) for x in qs]
269  
-[150, 175, 224, 264, 473, 566]
270  
-
271  
-# Regression for 10425 - annotations don't get in the way of a count() clause
272  
->>> Book.objects.values('publisher').annotate(Count('publisher')).count()
273  
-4
274  
-
275  
->>> Book.objects.annotate(Count('publisher')).values('publisher').count()
276  
-6
277  
-
278  
->>> publishers = Publisher.objects.filter(id__in=(1,2))
279  
->>> publishers
280  
-[<Publisher: Apress>, <Publisher: Sams>]
281  
-
282  
->>> publishers = publishers.annotate(n_books=models.Count('book'))
283  
->>> publishers[0].n_books
284  
-2
285  
-
286  
->>> publishers
287  
-[<Publisher: Apress>, <Publisher: Sams>]
288  
-
289  
->>> books = Book.objects.filter(publisher__in=publishers)
290  
->>> books
291  
-[<Book: Practical Django Projects>, <Book: Sams Teach Yourself Django in 24 Hours>, <Book: The Definitive Guide to Django: Web Development Done Right>]
292  
-
293  
->>> publishers
294  
-[<Publisher: Apress>, <Publisher: Sams>]
295  
-
296  
-
297  
-# Regression for 10666 - inherited fields work with annotations and aggregations
298  
->>> HardbackBook.objects.aggregate(n_pages=Sum('book_ptr__pages'))
299  
-{'n_pages': 2078}
300  
-
301  
->>> HardbackBook.objects.aggregate(n_pages=Sum('pages'))
302  
-{'n_pages': 2078}
303  
-
304  
->>> HardbackBook.objects.annotate(n_authors=Count('book_ptr__authors')).values('name','n_authors')
305  
-[{'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}]
306  
-
307  
->>> HardbackBook.objects.annotate(n_authors=Count('authors')).values('name','n_authors')
308  
-[{'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}]
309  
-
310  
-# Regression for #10766 - Shouldn't be able to reference an aggregate fields in an an aggregate() call.
311  
->>> Book.objects.all().annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age'))
312  
-Traceback (most recent call last):
313  
-...
314  
-FieldError: Cannot compute Avg('mean_age'): 'mean_age' is an aggregate
315  
-
316  
-"""
317  
-}
318  
-
319  
-def run_stddev_tests():
320  
-    """Check to see if StdDev/Variance tests should be run.
321  
-
322  
-    Stddev and Variance are not guaranteed to be available for SQLite, and
323  
-    are not available for PostgreSQL before 8.2.
324  
-    """
325  
-    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3':
326  
-        return False
327  
-
328  
-    class StdDevPop(object):
329  
-        sql_function = 'STDDEV_POP'
330  
-
331  
-    try:
332  
-        connection.ops.check_aggregate_support(StdDevPop())
333  
-    except:
334  
-        return False
335  
-    return True
336  
-
337  
-if run_stddev_tests():
338  
-    __test__['API_TESTS'] += """
339  
->>> Book.objects.aggregate(StdDev('pages'))
340  
-{'pages__stddev': 311.46...}
341  
-
342  
->>> Book.objects.aggregate(StdDev('rating'))
343  
-{'rating__stddev': 0.60...}
344  
-
345  
->>> Book.objects.aggregate(StdDev('price'))
346  
-{'price__stddev': 24.16...}
347  
-
348  
-
349  
->>> Book.objects.aggregate(StdDev('pages', sample=True))
350  
-{'pages__stddev': 341.19...}
351  
-
352  
->>> Book.objects.aggregate(StdDev('rating', sample=True))
353  
-{'rating__stddev': 0.66...}
354  
-
355  
->>> Book.objects.aggregate(StdDev('price', sample=True))
356  
-{'price__stddev': 26.46...}
357  
-
358  
-
359  
->>> Book.objects.aggregate(Variance('pages'))
360  
-{'pages__variance': 97010.80...}
361  
-
362  
->>> Book.objects.aggregate(Variance('rating'))
363  
-{'rating__variance': 0.36...}
364  
-
365  
->>> Book.objects.aggregate(Variance('price'))
366  
-{'price__variance': 583.77...}
367  
-
368  
-
369  
->>> Book.objects.aggregate(Variance('pages', sample=True))
370  
-{'pages__variance': 116412.96...}
371  
-
372  
->>> Book.objects.aggregate(Variance('rating', sample=True))
373  
-{'rating__variance': 0.44...}
374  
-
375  
->>> Book.objects.aggregate(Variance('price', sample=True))
376  
-{'price__variance': 700.53...}
377  
-
378  
-"""
614  tests/regressiontests/aggregation_regress/tests.py
... ...
@@ -1,12 +1,38 @@
  1
+import datetime
  2
+from decimal import Decimal
  3
+
  4
+from django.core.exceptions import FieldError
1 5
 from django.conf import settings
2  
-from django.test import TestCase
  6
+from django.test import TestCase, Approximate
3 7
 from django.db import DEFAULT_DB_ALIAS
4  
-from django.db.models import Count, Max
  8
+from django.db.models import Count, Max, Avg, Sum, F
5 9
 
6 10
 from regressiontests.aggregation_regress.models import *
7 11
 
8 12
 
  13
+def run_stddev_tests():
  14
+    """Check to see if StdDev/Variance tests should be run.
  15
+
  16
+    Stddev and Variance are not guaranteed to be available for SQLite, and
  17
+    are not available for PostgreSQL before 8.2.
  18
+    """
  19
+    if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3':
  20
+        return False
  21
+
  22
+    class StdDevPop(object):
  23
+        sql_function = 'STDDEV_POP'
  24
+
  25
+    try:
  26
+        connection.ops.check_aggregate_support(StdDevPop())
  27
+    except:
  28
+        return False
  29
+    return True
  30
+
  31
+
9 32
 class AggregationTests(TestCase):
  33
+    def assertObjectAttrs(self, obj, **kwargs):
  34
+        for attr, value in kwargs.iteritems():
  35
+            self.assertEqual(getattr(obj, attr), value)
10 36
 
11 37
     def test_aggregates_in_where_clause(self):
12 38
         """
@@ -70,3 +96,587 @@ def test_annotate_with_extra(self):
70 96
             }).annotate(total_books=Count('book'))
71 97
             # force execution of the query
72 98
             list(qs)
  99
+
  100
+    def test_aggregate(self):
  101
+        # Ordering requests are ignored
  102
+        self.assertEqual(
  103
+            Author.objects.order_by("name").aggregate(Avg("age")),
  104
+            {"age__avg": Approximate(37.444, places=1)}
  105
+        )
  106
+
  107
+        # Implicit ordering is also ignored
  108
+        self.assertEqual(
  109
+            Book.objects.aggregate(Sum("pages")),
  110
+            {"pages__sum": 3703},
  111
+        )
  112
+
  113
+        # Baseline results
  114
+        self.assertEqual(
  115
+            Book.objects.aggregate(Sum('pages'), Avg('pages')),
  116
+            {'pages__sum': 3703, 'pages__avg': Approximate(617.166, places=2)}
  117
+        )
  118
+
  119
+        # Empty values query doesn't affect grouping or results
  120
+        self.assertEqual(
  121
+            Book.objects.values().aggregate(Sum('pages'), Avg('pages')),
  122
+            {'pages__sum': 3703, 'pages__avg': Approximate(617.166, places=2)}
  123
+        )
  124
+
  125
+        # Aggregate overrides extra selected column
  126
+        self.assertEqual(
  127
+            Book.objects.extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages')),
  128
+            {'pages__sum': 3703}
  129
+        )
  130
+
  131
+    def test_annotation(self):
  132
+        # Annotations get combined with extra select clauses
  133
+        obj = Book.objects.annotate(mean_auth_age=Avg("authors__age")).extra(select={"manufacture_cost": "price * .5"}).get(pk=2)
  134
+        self.assertObjectAttrs(obj,
  135
+            contact_id=3,
  136
+            id=2,
  137
+            isbn=u'067232959',
  138
+            manufacture_cost=11.545,
  139
+            mean_auth_age=45.0,
  140
+            name='Sams Teach Yourself Django in 24 Hours',
  141
+            pages=528,
  142
+            price=Decimal("23.09"),
  143
+            pubdate=datetime.date(2008, 3, 3),
  144
+            publisher_id=2,
  145
+            rating=3.0
  146
+        )
  147
+
  148
+        # Order of the annotate/extra in the query doesn't matter
  149
+        obj = Book.objects.extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2)
  150
+        self.assertObjectAttrs(obj,
  151
+            contact_id=3,
  152
+            id=2,
  153
+            isbn=u'067232959',
  154
+            manufacture_cost=11.545,
  155
+            mean_auth_age=45.0,
  156
+            name=u'Sams Teach Yourself Django in 24 Hours',
  157
+            pages=528,
  158
+            price=Decimal("23.09"),
  159
+            pubdate=datetime.date(2008, 3, 3),
  160
+            publisher_id=2,
  161
+            rating=3.0
  162
+        )
  163
+
  164
+        # Values queries can be combined with annotate and extra
  165
+        obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2)
  166
+        self.assertEqual(obj, {
  167
+            "contact_id": 3,
  168
+            "id": 2,
  169
+            "isbn": u"067232959",
  170
+            "manufacture_cost": 11.545,
  171
+            "mean_auth_age": 45.0,
  172
+            "name": u"Sams Teach Yourself Django in 24 Hours",
  173
+            "pages": 528,
  174
+            "price": Decimal("23.09"),
  175
+            "pubdate": datetime.date(2008, 3, 3),
  176
+            "publisher_id": 2,
  177
+            "rating": 3.0,
  178
+        })
  179
+
  180
+        # The order of the (empty) values, annotate and extra clauses doesn't
  181
+        # matter
  182
+        obj = Book.objects.values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2)
  183
+        self.assertEqual(obj, {
  184
+            'contact_id': 3,
  185
+            'id': 2,
  186
+            'isbn': u'067232959',
  187
+            'manufacture_cost': 11.545,
  188
+            'mean_auth_age': 45.0,
  189
+            'name': u'Sams Teach Yourself Django in 24 Hours',
  190
+            'pages': 528,
  191
+            'price': Decimal("23.09"),
  192
+            'pubdate': datetime.date(2008, 3, 3),
  193
+            'publisher_id': 2,
  194
+            'rating': 3.0
  195
+        })
  196
+
  197
+        # If the annotation precedes the values clause, it won't be included
  198
+        # unless it is explicitly named
  199
+        obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1)
  200
+        self.assertEqual(obj, {
  201
+            "name": u'The Definitive Guide to Django: Web Development Done Right',
  202
+        })
  203
+
  204
+        obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name','mean_auth_age').get(pk=1)
  205
+        self.assertEqual(obj, {
  206
+            'mean_auth_age': 34.5,
  207
+            'name': u'The Definitive Guide to Django: Web Development Done Right',
  208
+        })
  209
+
  210
+        # If an annotation isn't included in the values, it can still be used
  211
+        # in a filter
  212
+        qs = Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2)
  213
+        self.assertQuerysetEqual(
  214
+            qs, [
  215
+                {"name": u'Python Web Development with Django'}
  216
+            ],
  217
+            lambda b: b,
  218
+        )
  219
+
  220
+        # The annotations are added to values output if values() precedes
  221
+        # annotate()
  222
+        obj = Book.objects.values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1)
  223
+        self.assertEqual(obj, {
  224
+            'mean_auth_age': 34.5,
  225
+            'name': u'The Definitive Guide to Django: Web Development Done Right',
  226
+        })
  227
+
  228
+        # Check that all of the objects are getting counted (allow_nulls) and
  229
+        # that values respects the amount of objects
  230
+        self.assertEqual(
  231
+            len(Author.objects.annotate(Avg('friends__age')).values()),
  232
+            9
  233
+        )
  234
+
  235
+        # Check that consecutive calls to annotate accumulate in the query
  236
+        qs = Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest', 'price').annotate(Max('publisher__num_awards'))
  237
+        self.assertQuerysetEqual(
  238
+            qs, [
  239
+                {'price': Decimal("30"), 'oldest': 35, 'publisher__num_awards__max': 3},
  240
+                {'price': Decimal("29.69"), 'oldest': 37, 'publisher__num_awards__max': 7},
  241
+                {'price': Decimal("23.09"), 'oldest': 45, 'publisher__num_awards__max': 1},
  242
+                {'price': Decimal("75"), 'oldest': 57, 'publisher__num_awards__max': 9},
  243
+                {'price': Decimal("82.8"), 'oldest': 57, 'publisher__num_awards__max': 7}
  244
+            ],
  245
+            lambda b: b,
  246
+        )
  247
+
  248
+    def test_aggrate_annotation(self):
  249
+        # Aggregates can be composed over annotations.
  250
+        # The return type is derived from the composed aggregate
  251
+        vals = Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('pages'), Max('price'), Sum('num_authors'), Avg('num_authors'))
  252
+        self.assertEqual(vals, {
  253
+            'num_authors__sum': 10,
  254
+            'num_authors__avg': Approximate(1.666, places=2),
  255
+            'pages__max': 1132,
  256
+            'price__max': Decimal("82.80")
  257
+        })
  258
+
  259
+    def test_field_error(self):
  260
+        # Bad field requests in aggregates are caught and reported
  261
+        self.assertRaises(
  262
+            FieldError,
  263
+            lambda: Book.objects.all().aggregate(num_authors=Count('foo'))
  264
+        )
  265
+
  266
+        self.assertRaises(
  267
+            FieldError,
  268
+            lambda: Book.objects.all().annotate(num_authors=Count('foo'))
  269
+        )
  270
+
  271
+        self.assertRaises(
  272
+            FieldError,
  273
+            lambda: Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo'))
  274
+        )
  275
+
  276
+    def test_more(self):
  277
+        # Old-style count aggregations can be mixed with new-style
  278
+        self.assertEqual(
  279
+            Book.objects.annotate(num_authors=Count('authors')).count(),
  280
+            6
  281
+        )
  282
+
  283
+        # Non-ordinal, non-computed Aggregates over annotations correctly
  284
+        # inherit the annotation's internal type if the annotation is ordinal
  285
+        # or computed
  286
+        vals = Book.objects.annotate(num_authors=Count('authors')).aggregate(Max('num_authors'))
  287
+        self.assertEqual(
  288
+            vals,
  289
+            {'num_authors__max': 3}
  290
+        )
  291
+
  292
+        vals = Publisher.objects.annotate(avg_price=Avg('book__price')).aggregate(Max('avg_price'))
  293
+        self.assertEqual(
  294
+            vals,
  295
+            {'avg_price__max': 75.0}
  296
+        )
  297
+
  298
+        # Aliases are quoted to protected aliases that might be reserved names
  299
+        vals = Book.objects.aggregate(number=Max('pages'), select=Max('pages'))
  300
+        self.assertEqual(
  301
+            vals,
  302
+            {'number': 1132, 'select': 1132}
  303
+        )
  304
+
  305
+        # Regression for #10064: select_related() plays nice with aggregates
  306
+        obj = Book.objects.select_related('publisher').annotate(num_authors=Count('authors')).values()[0]
  307
+        self.assertEqual(obj, {
  308
+            'contact_id': 8,
  309
+            'id': 5,
  310
+            'isbn': u'013790395',
  311
+            'name': u'Artificial Intelligence: A Modern Approach',
  312
+            'num_authors': 2,
  313
+            'pages': 1132,
  314
+            'price': Decimal("82.8"),
  315
+            'pubdate': datetime.date(1995, 1, 15),
  316
+            'publisher_id': 3,
  317
+            'rating': 4.0,
  318
+        })
  319
+
  320
+        # Regression for #10010: exclude on an aggregate field is correctly
  321
+        # negated
  322
+        self.assertEqual(
  323
+            len(Book.objects.annotate(num_authors=Count('authors'))),
  324
+            6
  325
+        )
  326
+        self.assertEqual(
  327
+            len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=2)),
  328
+            1
  329
+        )
  330
+        self.assertEqual(
  331
+            len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__gt=2)),
  332
+            5
  333
+        )
  334
+
  335
+        self.assertEqual(
  336
+            len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__lt=3).exclude(num_authors__lt=2)),
  337
+            2
  338
+        )
  339
+        self.assertEqual(
  340
+            len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3)),
  341
+            2
  342
+        )
  343
+
  344
+    def test_aggregate_fexpr(self):
  345
+        # Aggregates can be used with F() expressions
  346
+        # ... where the F() is pushed into the HAVING clause
  347
+        qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
  348
+        self.assertQuerysetEqual(
  349
+            qs, [
  350
+                {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9},
  351
+                {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}
  352
+            ],
  353
+            lambda p: p,
  354
+        )
  355
+
  356
+        qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
  357
+        self.assertQuerysetEqual(
  358
+            qs, [
  359
+                {'num_books': 2, 'name': u'Apress', 'num_awards': 3},
  360
+                {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0},
  361
+                {'num_books': 1, 'name': u'Sams', 'num_awards': 1}
  362
+            ],
  363
+            lambda p: p,
  364
+        )
  365
+
  366
+        # ... and where the F() references an aggregate
  367
+        qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards')
  368
+        self.assertQuerysetEqual(
  369
+            qs, [
  370
+                {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9},
  371
+                {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}
  372
+            ],
  373
+            lambda p: p,
  374
+        )
  375
+
  376
+        qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
  377
+        self.assertQuerysetEqual(
  378
+            qs, [
  379
+                {'num_books': 2, 'name': u'Apress', 'num_awards': 3},
  380
+                {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0},
  381
+                {'num_books': 1, 'name': u'Sams', 'num_awards': 1}
  382
+            ],
  383
+            lambda p: p,
  384
+        )
  385
+
  386
+    def test_db_col_table(self):
  387
+        # Tests on fields with non-default table and column names.
  388
+        qs = Clues.objects.values('EntryID__Entry').annotate(Appearances=Count('EntryID'), Distinct_Clues=Count('Clue', distinct=True))
  389
+        self.assertQuerysetEqual(qs, [])
  390
+
  391
+        qs = Entries.objects.annotate(clue_count=Count('clues__ID'))
  392
+        self.assertQuerysetEqual(qs, [])
  393
+
  394
+    def test_empty(self):
  395
+        # Regression for #10089: Check handling of empty result sets with
  396
+        # aggregates
  397
+        self.assertEqual(
  398
+            Book.objects.filter(id__in=[]).count(),
  399
+            0
  400
+        )
  401
+
  402
+        vals = Book.objects.filter(id__in=[]).aggregate(num_authors=Count('authors'), avg_authors=Avg('authors'), max_authors=Max('authors'), max_price=Max('price'), max_rating=Max('rating'))
  403
+        self.assertEqual(
  404
+            vals,
  405
+            {'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None}
  406
+        )
  407
+
  408
+        qs = Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values()
  409
+        self.assertQuerysetEqual(
  410
+            qs, [
  411
+                {'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None}
  412
+            ],
  413
+            lambda p: p
  414
+        )
  415
+
  416
+    def test_more_more(self):
  417
+        # Regression for #10113 - Fields mentioned in order_by() must be
  418
+        # included in the GROUP BY. This only becomes a problem when the
  419
+        # order_by introduces a new join.
  420
+        self.assertQuerysetEqual(
  421
+            Book.objects.annotate(num_authors=Count('authors')).order_by('publisher__name', 'name'), [
  422
+                "Practical Django Projects",
  423
+                "The Definitive Guide to Django: Web Development Done Right",
  424
+                "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp",
  425
+                "Artificial Intelligence: A Modern Approach",
  426
+                "Python Web Development with Django",
  427
+                "Sams Teach Yourself Django in 24 Hours",
  428
+            ],
  429
+            lambda b: b.name
  430
+        )
  431
+
  432
+        # Regression for #10127 - Empty select_related() works with annotate
  433
+        qs = Book.objects.filter(rating__lt=4.5).select_related().annotate(Avg('authors__age'))
  434
+        self.assertQuerysetEqual(
  435
+            qs, [
  436
+                (u'Artificial Intelligence: A Modern Approach', 51.5, u'Prentice Hall', u'Peter Norvig'),
  437
+                (u'Practical Django Projects', 29.0, u'Apress', u'James Bennett'),
  438
+                (u'Python Web Development with Django', Approximate(30.333, places=2), u'Prentice Hall', u'Jeffrey Forcier'),
  439
+                (u'Sams Teach Yourself Django in 24 Hours', 45.0, u'Sams', u'Brad Dayley')
  440
+            ],
  441
+            lambda b: (b.name, b.authors__age__avg, b.publisher.name, b.contact.name)
  442
+        )
  443
+
  444
+        # Regression for #10132 - If the values() clause only mentioned extra
  445
+        # (select=) columns, those columns are used for grouping
  446
+        qs = Book.objects.extra(select={'pub':'publisher_id'}).values('pub').annotate(Count('id')).order_by('pub')
  447
+        self.assertQuerysetEqual(
  448
+            qs, [
  449
+                {'pub': 1, 'id__count': 2},
  450
+                {'pub': 2, 'id__count': 1},
  451
+                {'pub': 3, 'id__count': 2},
  452
+                {'pub': 4, 'id__count': 1}
  453
+            ],
  454
+            lambda b: b
  455
+        )
  456
+
  457
+        qs = Book.objects.extra(select={'pub':'publisher_id', 'foo':'pages'}).values('pub').annotate(Count('id')).order_by('pub')
  458
+        self.assertQuerysetEqual(
  459
+            qs, [
  460
+                {'pub': 1, 'id__count': 2},
  461
+                {'pub': 2, 'id__count': 1},
  462
+                {'pub': 3, 'id__count': 2},
  463
+                {'pub': 4, 'id__count': 1}
  464
+            ],
  465
+            lambda b: b
  466
+        )
  467
+
  468
+        # Regression for #10182 - Queries with aggregate calls are correctly
  469
+        # realiased when used in a subquery
  470
+        ids = Book.objects.filter(pages__gt=100).annotate(n_authors=Count('authors')).filter(n_authors__gt=2).order_by('n_authors')
  471
+        self.assertQuerysetEqual(
  472
+            Book.objects.filter(id__in=ids), [
  473
+                "Python Web Development with Django",
  474
+            ],
  475
+            lambda b: b.name
  476
+        )
  477
+
  478
+    def test_pickle(self):
  479
+        # Regression for #10197 -- Queries with aggregates can be pickled.
  480
+        # First check that pickling is possible at all. No crash = success
  481
+        qs = Book.objects.annotate(num_authors=Count('authors'))
  482
+        out = pickle.dumps(qs)
  483
+
  484
+        # Then check that the round trip works.
  485
+        query = qs.query.get_compiler(qs.db).as_sql()[0]
  486
+        qs2 = pickle.loads(pickle.dumps(qs))
  487
+        self.assertEqual(
  488
+            qs2.query.get_compiler(qs2.db).as_sql()[0],
  489
+            query,
  490
+        )
  491
+
  492
+    def test_more_more_more(self):
  493
+        # Regression for #10199 - Aggregate calls clone the original query so
  494
+        # the original query can still be used
  495
+        books = Book.objects.all()
  496
+        books.aggregate(Avg("authors__age"))
  497
+        self.assertQuerysetEqual(
  498
+            books.all(), [
  499
+                u'Artificial Intelligence: A Modern Approach',
  500
+                u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp',
  501
+                u'Practical Django Projects',
  502
+                u'Python Web Development with Django',
  503
+                u'Sams Teach Yourself Django in 24 Hours',
  504
+                u'The Definitive Guide to Django: Web Development Done Right'
  505
+            ],
  506
+            lambda b: b.name
  507
+        )
  508
+
  509
+        # Regression for #10248 - Annotations work with DateQuerySets
  510
+        qs = Book.objects.annotate(num_authors=Count('authors')).filter(num_authors=2).dates('pubdate', 'day')
  511
+        self.assertQuerysetEqual(
  512
+            qs, [
  513
+                datetime.datetime(1995, 1, 15, 0, 0),
  514
+                datetime.datetime(2007, 12, 6, 0, 0)
  515
+            ],
  516
+            lambda b: b
  517
+        )
  518
+
  519
+        # Regression for #10290 - extra selects with parameters can be used for
  520
+        # grouping.
  521
+        qs = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'sheets' : '(pages + %s) / %s'}, select_params=[1, 2]).order_by('sheets').values('sheets')
  522
+        self.assertQuerysetEqual(
  523
+            qs, [
  524
+                150,
  525
+                175,
  526
+                224,
  527
+                264,
  528
+                473,
  529
+                566
  530
+            ],
  531
+            lambda b: int(b["sheets"])
  532
+        )
  533
+
  534
+        # Regression for 10425 - annotations don't get in the way of a count()
  535
+        # clause
  536
+        self.assertEqual(
  537
+            Book.objects.values('publisher').annotate(Count('publisher')).count(),
  538
+            4
  539
+        )
  540
+        self.assertEqual(
  541
+            Book.objects.annotate(Count('publisher')).values('publisher').count(),
  542
+            6
  543
+        )
  544
+
  545
+        publishers = Publisher.objects.filter(id__in=[1, 2])
  546
+        self.assertQuerysetEqual(
  547
+            publishers, [
  548
+                "Apress",
  549
+                "Sams"
  550
+            ],
  551
+            lambda p: p.name
  552
+        )
  553
+
  554
+        publishers = publishers.annotate(n_books=Count("book"))
  555
+        self.assertEqual(
  556
+            publishers[0].n_books,
  557
+            2
  558
+        )
  559
+
  560
+        self.assertQuerysetEqual(
  561
+            publishers, [
  562
+                "Apress",
  563
+                "Sams",
  564
+            ],
  565
+            lambda p: p.name
  566
+        )
  567
+
  568
+        books = Book.objects.filter(publisher__in=publishers)
  569
+        self.assertQuerysetEqual(
  570
+            books, [
  571
+                "Practical Django Projects",
  572
+                "Sams Teach Yourself Django in 24 Hours",
  573
+                "The Definitive Guide to Django: Web Development Done Right",
  574
+            ],
  575
+            lambda b: b.name
  576
+        )
  577
+        self.assertQuerysetEqual(
  578
+            publishers, [
  579
+                "Apress",
  580
+                "Sams",
  581
+            ],
  582
+            lambda p: p.name
  583
+        )
  584
+
  585
+        # Regression for 10666 - inherited fields work with annotations and
  586
+        # aggregations
  587
+        self.assertEqual(
  588
+            HardbackBook.objects.aggregate(n_pages=Sum('book_ptr__pages')),
  589
+            {'n_pages': 2078}
  590
+        )
  591
+
  592
+        self.assertEqual(
  593
+            HardbackBook.objects.aggregate(n_pages=Sum('pages')),
  594
+            {'n_pages': 2078},
  595
+        )
  596
+
  597
+        qs = HardbackBook.objects.annotate(n_authors=Count('book_ptr__authors')).values('name', 'n_authors')
  598
+        self.assertQuerysetEqual(
  599
+            qs, [
  600
+                {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'},
  601
+                {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}
  602
+            ],
  603
+            lambda h: h
  604
+        )
  605
+
  606
+        qs = HardbackBook.objects.annotate(n_authors=Count('authors')).values('name', 'n_authors')
  607
+        self.assertQuerysetEqual(
  608
+            qs, [
  609
+                {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'},
  610
+                {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}
  611
+            ],
  612
+            lambda h: h,
  613
+        )
  614
+
  615
+        # Regression for #10766 - Shouldn't be able to reference an aggregate
  616
+        # fields in an an aggregate() call.
  617
+        self.assertRaises(
  618
+            FieldError,
  619
+            lambda: Book.objects.annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age'))
  620
+        )
  621
+
  622
+    if run_stddev_tests():
  623
+        def test_stddev(self):
  624
+            self.assertEqual(
  625
+                Book.objects.aggregate(StdDev('pages')),
  626
+                {'pages__stddev': 311.46}
  627
+            )
  628
+
  629
+            self.assertEqual(
  630
+                Book.objects.aggregate(StdDev('rating')),
  631
+                {'rating__stddev': 0.60}
  632
+            )
  633
+
  634
+            self.assertEqual(
  635
+                Book.objects.aggregate(StdDev('price')),
  636
+                {'price__stddev': 24.16}
  637
+            )
  638
+
  639