Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #13634 -- Migrated aggregation tests to use unittest. This remo…

…ves a flush, which speeds up the tests overall.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13309 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 526a4410d790f9b3923f7bac6fc4c1eff3b8fbf6 1 parent eedf16d
Russell Keith-Magee authored
3  django/test/testcases.py
@@ -466,6 +466,9 @@ def assertTemplateNotUsed(self, response, template_name, msg_prefix=''):
466 466
             msg_prefix + "Template '%s' was used unexpectedly in rendering"
467 467
             " the response" % template_name)
468 468
 
  469
+    def assertQuerysetEqual(self, qs, values, transform=repr):
  470
+        return self.assertEqual(map(transform, qs), values)
  471
+
469 472
 def connections_support_transactions():
470 473
     """
471 474
     Returns True if all connections support transactions.  This is messy
13  docs/topics/testing.txt
@@ -1248,6 +1248,19 @@ cause of an failure in your test suite.
1248 1248
     ``target_status_code`` will be the url and status code for the final
1249 1249
     point of the redirect chain.
1250 1250
 
  1251
+.. method:: TestCase.assertQuerysetEqual(response, qs, values, transform=repr)
  1252
+
  1253
+    Asserts that a queryset ``qs`` returns a particular list of values ``values``.
  1254
+
  1255
+    The comparison of the contents of ``qs`` and ``values`` is performed using
  1256
+    the function ``transform``; by default, this means that the ``repr()`` of
  1257
+    each value is compared. Any other callable can be used if ``repr()`` doesn't
  1258
+    provide a unique or helpful comparison.
  1259
+
  1260
+    The comparison is also ordering dependent. If ``qs`` doesn't provide an
  1261
+    implicit ordering, you will need to apply a ``order_by()`` clause to your
  1262
+    queryset to ensure that the test will pass reliably.
  1263
+
1251 1264
 .. _topics-testing-email:
1252 1265
 
1253 1266
 E-mail services
321  tests/modeltests/aggregation/models.py
... ...
@@ -1,6 +1,7 @@
1 1
 # coding: utf-8
2 2
 from django.db import models
3 3
 
  4
+
4 5
 class Author(models.Model):
5 6
     name = models.CharField(max_length=100)
6 7
     age = models.IntegerField()
@@ -39,323 +40,3 @@ class Store(models.Model):
39 40
     def __unicode__(self):
40 41
         return self.name
41 42
 
42  
-# Tests on 'aggregate'
43  
-# Different backends and numbers.
44  
-__test__ = {'API_TESTS': """
45  
->>> from django.core import management
46  
->>> from decimal import Decimal
47  
->>> from datetime import date
48  
-
49  
-# Reset the database representation of this app.
50  
-# This will return the database to a clean initial state.
51  
->>> management.call_command('flush', verbosity=0, interactive=False)
52  
-
53  
-# Empty Call - request nothing, get nothing.
54  
->>> Author.objects.all().aggregate()
55  
-{}
56  
-
57  
->>> from django.db.models import Avg, Sum, Count, Max, Min
58  
-
59  
-# Single model aggregation
60  
-#
61  
-
62  
-# Single aggregate
63  
-# Average age of Authors
64  
->>> Author.objects.all().aggregate(Avg('age'))
65  
-{'age__avg': 37.4...}
66  
-
67  
-# Multiple aggregates
68  
-# Average and Sum of Author ages
69  
->>> Author.objects.all().aggregate(Sum('age'), Avg('age'))
70  
-{'age__sum': 337, 'age__avg': 37.4...}
71  
-
72  
-# Aggreates interact with filters, and only
73  
-# generate aggregate values for the filtered values
74  
-# Sum of the age of those older than 29 years old
75  
->>> Author.objects.all().filter(age__gt=29).aggregate(Sum('age'))
76  
-{'age__sum': 254}
77  
-
78  
-# Depth-1 Joins
79  
-#
80  
-
81  
-# On Relationships with self
82  
-# Average age of the friends of each author
83  
->>> Author.objects.all().aggregate(Avg('friends__age'))
84  
-{'friends__age__avg': 34.07...}
85  
-
86  
-# On ManyToMany Relationships
87  
-#
88  
-
89  
-# Forward
90  
-# Average age of the Authors of Books with a rating of less than 4.5
91  
->>> Book.objects.all().filter(rating__lt=4.5).aggregate(Avg('authors__age'))
92  
-{'authors__age__avg': 38.2...}
93  
-
94  
-# Backward
95  
-# Average rating of the Books whose Author's name contains the letter 'a'
96  
->>> Author.objects.all().filter(name__contains='a').aggregate(Avg('book__rating'))
97  
-{'book__rating__avg': 4.0}
98  
-
99  
-# On OneToMany Relationships
100  
-#
101  
-
102  
-# Forward
103  
-# Sum of the number of awards of each Book's Publisher
104  
->>> Book.objects.all().aggregate(Sum('publisher__num_awards'))
105  
-{'publisher__num_awards__sum': 30}
106  
-
107  
-# Backward
108  
-# Sum of the price of every Book that has a Publisher
109  
->>> Publisher.objects.all().aggregate(Sum('book__price'))
110  
-{'book__price__sum': Decimal("270.27")}
111  
-
112  
-# Multiple Joins
113  
-#
114  
-
115  
-# Forward
116  
->>> Store.objects.all().aggregate(Max('books__authors__age'))
117  
-{'books__authors__age__max': 57}
118  
-
119  
-# Backward
120  
-# Note that the very long default alias may be truncated
121  
->>> Author.objects.all().aggregate(Min('book__publisher__num_awards'))
122  
-{'book__publisher__num_award...': 1}
123  
-
124  
-# Aggregate outputs can also be aliased.
125  
-
126  
-# Average amazon.com Book rating
127  
->>> Store.objects.filter(name='Amazon.com').aggregate(amazon_mean=Avg('books__rating'))
128  
-{'amazon_mean': 4.08...}
129  
-
130  
-# Tests on annotate()
131  
-
132  
-# An empty annotate call does nothing but return the same QuerySet
133  
->>> Book.objects.all().annotate().order_by('pk')
134  
-[<Book: The Definitive Guide to Django: Web Development Done Right>, <Book: Sams Teach Yourself Django in 24 Hours>, <Book: Practical Django Projects>, <Book: Python Web Development with Django>, <Book: Artificial Intelligence: A Modern Approach>, <Book: Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp>]
135  
-
136  
-# Annotate inserts the alias into the model object with the aggregated result
137  
->>> books = Book.objects.all().annotate(mean_age=Avg('authors__age'))
138  
->>> books.get(pk=1).name
139  
-u'The Definitive Guide to Django: Web Development Done Right'
140  
-
141  
->>> books.get(pk=1).mean_age
142  
-34.5
143  
-
144  
-# On ManyToMany Relationships
145  
-
146  
-# Forward
147  
-# Average age of the Authors of each book with a rating less than 4.5
148  
->>> books = Book.objects.all().filter(rating__lt=4.5).annotate(Avg('authors__age'))
149  
->>> sorted([(b.name, b.authors__age__avg) for b in books])
150  
-[(u'Artificial Intelligence: A Modern Approach', 51.5), (u'Practical Django Projects', 29.0), (u'Python Web Development with Django', 30.3...), (u'Sams Teach Yourself Django in 24 Hours', 45.0)]
151  
-
152  
-# Count the number of authors of each book
153  
->>> books = Book.objects.annotate(num_authors=Count('authors'))
154  
->>> sorted([(b.name, b.num_authors) for b in books])
155  
-[(u'Artificial Intelligence: A Modern Approach', 2), (u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 1), (u'Practical Django Projects', 1), (u'Python Web Development with Django', 3), (u'Sams Teach Yourself Django in 24 Hours', 1), (u'The Definitive Guide to Django: Web Development Done Right', 2)]
156  
-
157  
-# Backward
158  
-# Average rating of the Books whose Author's names contains the letter 'a'
159  
->>> authors = Author.objects.all().filter(name__contains='a').annotate(Avg('book__rating'))
160  
->>> sorted([(a.name, a.book__rating__avg) for a in authors])
161  
-[(u'Adrian Holovaty', 4.5), (u'Brad Dayley', 3.0), (u'Jacob Kaplan-Moss', 4.5), (u'James Bennett', 4.0), (u'Paul Bissex', 4.0), (u'Stuart Russell', 4.0)]
162  
-
163  
-# Count the number of books written by each author
164  
->>> authors = Author.objects.annotate(num_books=Count('book'))
165  
->>> sorted([(a.name, a.num_books) for a in authors])
166  
-[(u'Adrian Holovaty', 1), (u'Brad Dayley', 1), (u'Jacob Kaplan-Moss', 1), (u'James Bennett', 1), (u'Jeffrey Forcier', 1), (u'Paul Bissex', 1), (u'Peter Norvig', 2), (u'Stuart Russell', 1), (u'Wesley J. Chun', 1)]
167  
-
168  
-# On OneToMany Relationships
169  
-
170  
-# Forward
171  
-# Annotate each book with the number of awards of each Book's Publisher
172  
->>> books = Book.objects.all().annotate(Sum('publisher__num_awards'))
173  
->>> sorted([(b.name, b.publisher__num_awards__sum) for b in books])
174  
-[(u'Artificial Intelligence: A Modern Approach', 7), (u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 9), (u'Practical Django Projects', 3), (u'Python Web Development with Django', 7), (u'Sams Teach Yourself Django in 24 Hours', 1), (u'The Definitive Guide to Django: Web Development Done Right', 3)]
175  
-
176  
-# Backward
177  
-# Annotate each publisher with the sum of the price of all books sold
178  
->>> publishers = Publisher.objects.all().annotate(Sum('book__price'))
179  
->>> sorted([(p.name, p.book__price__sum) for p in publishers])
180  
-[(u'Apress', Decimal("59.69")), (u"Jonno's House of Books", None), (u'Morgan Kaufmann', Decimal("75.00")), (u'Prentice Hall', Decimal("112.49")), (u'Sams', Decimal("23.09"))]
181  
-
182  
-# Calls to values() are not commutative over annotate().
183  
-
184  
-# Calling values on a queryset that has annotations returns the output
185  
-# as a dictionary
186  
->>> [sorted(o.iteritems()) for o in Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values()]
187  
-[[('contact_id', 1), ('id', 1), ('isbn', u'159059725'), ('mean_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right'), ('pages', 447), ('price', Decimal("30...")), ('pubdate', datetime.date(2007, 12, 6)), ('publisher_id', 1), ('rating', 4.5)]]
188  
-
189  
->>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values('pk', 'isbn', 'mean_age')
190  
-[{'pk': 1, 'isbn': u'159059725', 'mean_age': 34.5}]
191  
-
192  
-# Calling values() with parameters reduces the output
193  
->>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values('name')
194  
-[{'name': u'The Definitive Guide to Django: Web Development Done Right'}]
195  
-
196  
-# An empty values() call before annotating has the same effect as an
197  
-# empty values() call after annotating
198  
->>> [sorted(o.iteritems()) for o in Book.objects.filter(pk=1).values().annotate(mean_age=Avg('authors__age'))]
199  
-[[('contact_id', 1), ('id', 1), ('isbn', u'159059725'), ('mean_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right'), ('pages', 447), ('price', Decimal("30...")), ('pubdate', datetime.date(2007, 12, 6)), ('publisher_id', 1), ('rating', 4.5)]]
200  
-
201  
-# Calling annotate() on a ValuesQuerySet annotates over the groups of
202  
-# fields to be selected by the ValuesQuerySet.
203  
-
204  
-# Note that an extra parameter is added to each dictionary. This
205  
-# parameter is a queryset representing the objects that have been
206  
-# grouped to generate the annotation
207  
-
208  
->>> Book.objects.all().values('rating').annotate(n_authors=Count('authors__id'), mean_age=Avg('authors__age')).order_by('rating')
209  
-[{'rating': 3.0, 'n_authors': 1, 'mean_age': 45.0}, {'rating': 4.0, 'n_authors': 6, 'mean_age': 37.1...}, {'rating': 4.5, 'n_authors': 2, 'mean_age': 34.5}, {'rating': 5.0, 'n_authors': 1, 'mean_age': 57.0}]
210  
-
211  
-# If a join doesn't match any objects, an aggregate returns None
212  
->>> authors = Author.objects.all().annotate(Avg('friends__age')).order_by('id')
213  
->>> len(authors)
214  
-9
215  
->>> sorted([(a.name, a.friends__age__avg) for a in authors])
216  
-[(u'Adrian Holovaty', 32.0), (u'Brad Dayley', None), (u'Jacob Kaplan-Moss', 29.5), (u'James Bennett', 34.0), (u'Jeffrey Forcier', 27.0), (u'Paul Bissex', 31.0), (u'Peter Norvig', 46.0), (u'Stuart Russell', 57.0), (u'Wesley J. Chun', 33.6...)]
217  
-
218  
-
219  
-# The Count aggregation function allows an extra parameter: distinct.
220  
-# This restricts the count results to unique items
221  
->>> Book.objects.all().aggregate(Count('rating'))
222  
-{'rating__count': 6}
223  
-
224  
->>> Book.objects.all().aggregate(Count('rating', distinct=True))
225  
-{'rating__count': 4}
226  
-
227  
-# Retreiving the grouped objects
228  
-
229  
-# When using Count you can also omit the primary key and refer only to
230  
-# the related field name if you want to count all the related objects
231  
-# and not a specific column
232  
->>> explicit = list(Author.objects.annotate(Count('book__id')))
233  
->>> implicit = list(Author.objects.annotate(Count('book')))
234  
->>> explicit == implicit
235  
-True
236  
-
237  
-# Ordering is allowed on aggregates
238  
->>> Book.objects.values('rating').annotate(oldest=Max('authors__age')).order_by('oldest', 'rating')
239  
-[{'rating': 4.5, 'oldest': 35}, {'rating': 3.0, 'oldest': 45}, {'rating': 4.0, 'oldest': 57}, {'rating': 5.0, 'oldest': 57}]
240  
-
241  
->>> Book.objects.values('rating').annotate(oldest=Max('authors__age')).order_by('-oldest', '-rating')
242  
-[{'rating': 5.0, 'oldest': 57}, {'rating': 4.0, 'oldest': 57}, {'rating': 3.0, 'oldest': 45}, {'rating': 4.5, 'oldest': 35}]
243  
-
244  
-# It is possible to aggregate over anotated values
245  
->>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Avg('num_authors'))
246  
-{'num_authors__avg': 1.66...}
247  
-
248  
-# You can filter the results based on the aggregation alias.
249  
-
250  
-# Lets add a publisher to test the different possibilities for filtering
251  
->>> p = Publisher(name='Expensive Publisher', num_awards=0)
252  
->>> p.save()
253  
->>> Book(name='ExpensiveBook1', pages=1, isbn='111', rating=3.5, price=Decimal("1000"), publisher=p, contact_id=1, pubdate=date(2008,12,1)).save()
254  
->>> Book(name='ExpensiveBook2', pages=1, isbn='222', rating=4.0, price=Decimal("1000"), publisher=p, contact_id=1, pubdate=date(2008,12,2)).save()
255  
->>> Book(name='ExpensiveBook3', pages=1, isbn='333', rating=4.5, price=Decimal("35"), publisher=p, contact_id=1, pubdate=date(2008,12,3)).save()
256  
-
257  
-# Publishers that have:
258  
-
259  
-# (i) more than one book
260  
->>> Publisher.objects.annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk')
261  
-[<Publisher: Apress>, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>]
262  
-
263  
-# (ii) a book that cost less than 40
264  
->>> Publisher.objects.filter(book__price__lt=Decimal("40.0")).order_by('pk')
265  
-[<Publisher: Apress>, <Publisher: Apress>, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>]
266  
-
267  
-# (iii) more than one book and (at least) a book that cost less than 40
268  
->>> Publisher.objects.annotate(num_books=Count('book__id')).filter(num_books__gt=1, book__price__lt=Decimal("40.0")).order_by('pk')
269  
-[<Publisher: Apress>, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>]
270  
-
271  
-# (iv) more than one book that costs less than $40
272  
->>> Publisher.objects.filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk')
273  
-[<Publisher: Apress>]
274  
-
275  
-# Now a bit of testing on the different lookup types
276  
-#
277  
-
278  
->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__range=[1, 3]).order_by('pk')
279  
-[<Publisher: Apress>, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Morgan Kaufmann>, <Publisher: Expensive Publisher>]
280  
-
281  
->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__range=[1, 2]).order_by('pk')
282  
-[<Publisher: Apress>, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Morgan Kaufmann>]
283  
-
284  
->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__in=[1, 3]).order_by('pk')
285  
-[<Publisher: Sams>, <Publisher: Morgan Kaufmann>, <Publisher: Expensive Publisher>]
286  
-
287  
->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__isnull=True)
288  
-[]
289  
-
290  
->>> p.delete()
291  
-
292  
-# Does Author X have any friends? (or better, how many friends does author X have)
293  
->> Author.objects.filter(pk=1).aggregate(Count('friends__id'))
294  
-{'friends__id__count': 2.0}
295  
-
296  
-# Give me a list of all Books with more than 1 authors
297  
->>> Book.objects.all().annotate(num_authors=Count('authors__name')).filter(num_authors__ge=2).order_by('pk')
298  
-[<Book: The Definitive Guide to Django: Web Development Done Right>, <Book: Artificial Intelligence: A Modern Approach>]
299  
-
300  
-# Give me a list of all Authors that have no friends
301  
->>> Author.objects.all().annotate(num_friends=Count('friends__id', distinct=True)).filter(num_friends=0).order_by('pk')
302  
-[<Author: Brad Dayley>]
303  
-
304  
-# Give me a list of all publishers that have published more than 1 books
305  
->>> Publisher.objects.all().annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk')
306  
-[<Publisher: Apress>, <Publisher: Prentice Hall>]
307  
-
308  
-# Give me a list of all publishers that have published more than 1 books that cost less than 40
309  
->>> Publisher.objects.all().filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count('book__id')).filter(num_books__gt=1)
310  
-[<Publisher: Apress>]
311  
-
312  
-# Give me a list of all Books that were written by X and one other author.
313  
->>> Book.objects.all().annotate(num_authors=Count('authors__id')).filter(authors__name__contains='Norvig', num_authors__gt=1)
314  
-[<Book: Artificial Intelligence: A Modern Approach>]
315  
-
316  
-# Give me the average rating of all Books that were written by X and one other author.
317  
-#(Aggregate over objects discovered using membership of the m2m set)
318  
-
319  
-# Adding an existing author to another book to test it the right way
320  
->>> a = Author.objects.get(name__contains='Norvig')
321  
->>> b = Book.objects.get(name__contains='Done Right')
322  
->>> b.authors.add(a)
323  
->>> b.save()
324  
-
325  
-# This should do it
326  
->>> Book.objects.all().annotate(num_authors=Count('authors__id')).filter(authors__name__contains='Norvig', num_authors__gt=1).aggregate(Avg('rating'))
327  
-{'rating__avg': 4.25}
328  
->>> b.authors.remove(a)
329  
-
330  
-# Give me a list of all Authors that have published a book with at least one other person
331  
-# (Filters over a count generated on a related object)
332  
-#
333  
-# Cheating: [a for a in Author.objects.all().annotate(num_coleagues=Count('book__authors__id'), num_books=Count('book__id', distinct=True)) if a.num_coleagues - a.num_books > 0]
334  
-# F-Syntax is required. Will be fixed after F objects are available
335  
-
336  
-# Aggregates also work on dates, times and datetimes
337  
->>> Publisher.objects.annotate(earliest_book=Min('book__pubdate')).exclude(earliest_book=None).order_by('earliest_book').values()
338  
-[{'earliest_book': datetime.date(1991, 10, 15), 'num_awards': 9, 'id': 4, 'name': u'Morgan Kaufmann'}, {'earliest_book': datetime.date(1995, 1, 15), 'num_awards': 7, 'id': 3, 'name': u'Prentice Hall'}, {'earliest_book': datetime.date(2007, 12, 6), 'num_awards': 3, 'id': 1, 'name': u'Apress'}, {'earliest_book': datetime.date(2008, 3, 3), 'num_awards': 1, 'id': 2, 'name': u'Sams'}]
339  
-
340  
->>> Store.objects.aggregate(Max('friday_night_closing'), Min("original_opening"))
341  
-{'friday_night_closing__max': datetime.time(23, 59, 59), 'original_opening__min': datetime.datetime(1945, 4, 25, 16, 24, 14)}
342  
-
343  
-# values_list() can also be used
344  
-
345  
->>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values_list('pk', 'isbn', 'mean_age')
346  
-[(1, u'159059725', 34.5)]
347  
-
348  
->>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values_list('isbn')
349  
-[(u'159059725',)]
350  
-
351  
->>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values_list('mean_age')
352  
-[(34.5,)]
353  
-
354  
->>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values_list('mean_age', flat=True)
355  
-[34.5]
356  
-
357  
->>> qs = Book.objects.values_list('price').annotate(count=Count('price')).order_by('-count', 'price')
358  
->>> list(qs) == [(Decimal('29.69'), 2), (Decimal('23.09'), 1), (Decimal('30'), 1), (Decimal('75'), 1), (Decimal('82.8'), 1)]
359  
-True
360  
-
361  
-"""}
578  tests/modeltests/aggregation/tests.py
... ...
@@ -0,0 +1,578 @@
  1
+import datetime
  2
+from decimal import Decimal
  3
+
  4
+from django.db.models import Avg, Sum, Count, Max, Min
  5
+from django.test import TestCase
  6
+
  7
+from models import Author, Publisher, Book, Store
  8
+
  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
+class BaseAggregateTestCase(TestCase):
  24
+    fixtures = ["initial_data.json"]
  25
+
  26
+    def test_empty_aggregate(self):
  27
+        self.assertEqual(Author.objects.all().aggregate(), {})
  28
+
  29
+    def test_single_aggregate(self):
  30
+        vals = Author.objects.aggregate(Avg("age"))
  31
+        self.assertEqual(vals, {"age__avg": Approximate(37.4, places=1)})
  32
+
  33
+    def test_multiple_aggregates(self):
  34
+        vals = Author.objects.aggregate(Sum("age"), Avg("age"))
  35
+        self.assertEqual(vals, {"age__sum": 337, "age__avg": Approximate(37.4, places=1)})
  36
+
  37
+    def test_filter_aggregate(self):
  38
+        vals = Author.objects.filter(age__gt=29).aggregate(Sum("age"))
  39
+        self.assertEqual(len(vals), 1)
  40
+        self.assertEqual(vals["age__sum"], 254)
  41
+
  42
+    def test_related_aggregate(self):
  43
+        vals = Author.objects.aggregate(Avg("friends__age"))
  44
+        self.assertEqual(len(vals), 1)
  45
+        self.assertAlmostEqual(vals["friends__age__avg"], 34.07, places=2)
  46
+
  47
+        vals = Book.objects.filter(rating__lt=4.5).aggregate(Avg("authors__age"))
  48
+        self.assertEqual(len(vals), 1)
  49
+        self.assertAlmostEqual(vals["authors__age__avg"], 38.2857, places=2)
  50
+
  51
+        vals = Author.objects.all().filter(name__contains="a").aggregate(Avg("book__rating"))
  52
+        self.assertEqual(len(vals), 1)
  53
+        self.assertEqual(vals["book__rating__avg"], 4.0)
  54
+
  55
+        vals = Book.objects.aggregate(Sum("publisher__num_awards"))
  56
+        self.assertEqual(len(vals), 1)
  57
+        self.assertEquals(vals["publisher__num_awards__sum"], 30)
  58
+
  59
+        vals = Publisher.objects.aggregate(Sum("book__price"))
  60
+        self.assertEqual(len(vals), 1)
  61
+        self.assertEqual(vals["book__price__sum"], Decimal("270.27"))
  62
+
  63
+    def test_aggregate_multi_join(self):
  64
+        vals = Store.objects.aggregate(Max("books__authors__age"))
  65
+        self.assertEqual(len(vals), 1)
  66
+        self.assertEqual(vals["books__authors__age__max"], 57)
  67
+
  68
+        vals = Author.objects.aggregate(Min("book__publisher__num_awards"))
  69
+        self.assertEqual(len(vals), 1)
  70
+        self.assertEqual(vals["book__publisher__num_awards__min"], 1)
  71
+
  72
+    def test_aggregate_alias(self):
  73
+        vals = Store.objects.filter(name="Amazon.com").aggregate(amazon_mean=Avg("books__rating"))
  74
+        self.assertEqual(len(vals), 1)
  75
+        self.assertAlmostEqual(vals["amazon_mean"], 4.08, places=2)
  76
+
  77
+    def test_annotate_basic(self):
  78
+        self.assertQuerysetEqual(
  79
+            Book.objects.annotate().order_by('pk'), [
  80
+                "The Definitive Guide to Django: Web Development Done Right",
  81
+                "Sams Teach Yourself Django in 24 Hours",
  82
+                "Practical Django Projects",
  83
+                "Python Web Development with Django",
  84
+                "Artificial Intelligence: A Modern Approach",
  85
+                "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp"
  86
+            ],
  87
+            lambda b: b.name
  88
+        )
  89
+
  90
+        books = Book.objects.annotate(mean_age=Avg("authors__age"))
  91
+        b = books.get(pk=1)
  92
+        self.assertEqual(
  93
+            b.name,
  94
+            u'The Definitive Guide to Django: Web Development Done Right'
  95
+        )
  96
+        self.assertEqual(b.mean_age, 34.5)
  97
+
  98
+    def test_annotate_m2m(self):
  99
+        books = Book.objects.filter(rating__lt=4.5).annotate(Avg("authors__age")).order_by("name")
  100
+        self.assertQuerysetEqual(
  101
+            books, [
  102
+                (u'Artificial Intelligence: A Modern Approach', 51.5),
  103
+                (u'Practical Django Projects', 29.0),
  104
+                (u'Python Web Development with Django', Approximate(30.3, places=1)),
  105
+                (u'Sams Teach Yourself Django in 24 Hours', 45.0)
  106
+            ],
  107
+            lambda b: (b.name, b.authors__age__avg),
  108
+        )
  109
+
  110
+        books = Book.objects.annotate(num_authors=Count("authors")).order_by("name")
  111
+        self.assertQuerysetEqual(
  112
+            books, [
  113
+                (u'Artificial Intelligence: A Modern Approach', 2),
  114
+                (u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 1),
  115
+                (u'Practical Django Projects', 1),
  116
+                (u'Python Web Development with Django', 3),
  117
+                (u'Sams Teach Yourself Django in 24 Hours', 1),
  118
+                (u'The Definitive Guide to Django: Web Development Done Right', 2)
  119
+            ],
  120
+            lambda b: (b.name, b.num_authors)
  121
+        )
  122
+
  123
+    def test_backwards_m2m_annotate(self):
  124
+        authors = Author.objects.filter(name__contains="a").annotate(Avg("book__rating")).order_by("name")
  125
+        self.assertQuerysetEqual(
  126
+            authors, [
  127
+                (u'Adrian Holovaty', 4.5),
  128
+                (u'Brad Dayley', 3.0),
  129
+                (u'Jacob Kaplan-Moss', 4.5),
  130
+                (u'James Bennett', 4.0),
  131
+                (u'Paul Bissex', 4.0),
  132
+                (u'Stuart Russell', 4.0)
  133
+            ],
  134
+            lambda a: (a.name, a.book__rating__avg)
  135
+        )
  136
+
  137
+        authors = Author.objects.annotate(num_books=Count("book")).order_by("name")
  138
+        self.assertQuerysetEqual(
  139
+            authors, [
  140
+                (u'Adrian Holovaty', 1),
  141
+                (u'Brad Dayley', 1),
  142
+                (u'Jacob Kaplan-Moss', 1),
  143
+                (u'James Bennett', 1),
  144
+                (u'Jeffrey Forcier', 1),
  145
+                (u'Paul Bissex', 1),
  146
+                (u'Peter Norvig', 2),
  147
+                (u'Stuart Russell', 1),
  148
+                (u'Wesley J. Chun', 1)
  149
+            ],
  150
+            lambda a: (a.name, a.num_books)
  151
+        )
  152
+
  153
+    def test_reverse_fkey_annotate(self):
  154
+        books = Book.objects.annotate(Sum("publisher__num_awards")).order_by("name")
  155
+        self.assertQuerysetEqual(
  156
+            books, [
  157
+                (u'Artificial Intelligence: A Modern Approach', 7),
  158
+                (u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 9),
  159
+                (u'Practical Django Projects', 3),
  160
+                (u'Python Web Development with Django', 7),
  161
+                (u'Sams Teach Yourself Django in 24 Hours', 1),
  162
+                (u'The Definitive Guide to Django: Web Development Done Right', 3)
  163
+            ],
  164
+            lambda b: (b.name, b.publisher__num_awards__sum)
  165
+        )
  166
+
  167
+        publishers = Publisher.objects.annotate(Sum("book__price")).order_by("name")
  168
+        self.assertQuerysetEqual(
  169
+            publishers, [
  170
+                (u'Apress', Decimal("59.69")),
  171
+                (u"Jonno's House of Books", None),
  172
+                (u'Morgan Kaufmann', Decimal("75.00")),
  173
+                (u'Prentice Hall', Decimal("112.49")),
  174
+                (u'Sams', Decimal("23.09"))
  175
+            ],
  176
+            lambda p: (p.name, p.book__price__sum)
  177
+        )
  178
+
  179
+    def test_annotate_values(self):
  180
+        books = list(Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values())
  181
+        self.assertEqual(
  182
+            books, [
  183
+                {
  184
+                    "contact_id": 1,
  185
+                    "id": 1,
  186
+                    "isbn": "159059725",
  187
+                    "mean_age": 34.5,
  188
+                    "name": "The Definitive Guide to Django: Web Development Done Right",
  189
+                    "pages": 447,
  190
+                    "price": Approximate(Decimal("30")),
  191
+                    "pubdate": datetime.date(2007, 12, 6),
  192
+                    "publisher_id": 1,
  193
+                    "rating": 4.5,
  194
+                }
  195
+            ]
  196
+        )
  197
+
  198
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values('pk', 'isbn', 'mean_age')
  199
+        self.assertEqual(
  200
+            list(books), [
  201
+                {
  202
+                    "pk": 1,
  203
+                    "isbn": "159059725",
  204
+                    "mean_age": 34.5,
  205
+                }
  206
+            ]
  207
+        )
  208
+
  209
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values("name")
  210
+        self.assertEqual(
  211
+            list(books), [
  212
+                {
  213
+                    "name": "The Definitive Guide to Django: Web Development Done Right"
  214
+                }
  215
+            ]
  216
+        )
  217
+
  218
+        books = Book.objects.filter(pk=1).values().annotate(mean_age=Avg('authors__age'))
  219
+        self.assertEqual(
  220
+            list(books), [
  221
+                {
  222
+                    "contact_id": 1,
  223
+                    "id": 1,
  224
+                    "isbn": "159059725",
  225
+                    "mean_age": 34.5,
  226
+                    "name": "The Definitive Guide to Django: Web Development Done Right",
  227
+                    "pages": 447,
  228
+                    "price": Approximate(Decimal("30")),
  229
+                    "pubdate": datetime.date(2007, 12, 6),
  230
+                    "publisher_id": 1,
  231
+                    "rating": 4.5,
  232
+                }
  233
+            ]
  234
+        )
  235
+
  236
+        books = Book.objects.values("rating").annotate(n_authors=Count("authors__id"), mean_age=Avg("authors__age")).order_by("rating")
  237
+        self.assertEqual(
  238
+            list(books), [
  239
+                {
  240
+                    "rating": 3.0,
  241
+                    "n_authors": 1,
  242
+                    "mean_age": 45.0,
  243
+                },
  244
+                {
  245
+                    "rating": 4.0,
  246
+                    "n_authors": 6,
  247
+                    "mean_age": Approximate(37.16, places=1)
  248
+                },
  249
+                {
  250
+                    "rating": 4.5,
  251
+                    "n_authors": 2,
  252
+                    "mean_age": 34.5,
  253
+                },
  254
+                {
  255
+                    "rating": 5.0,
  256
+                    "n_authors": 1,
  257
+                    "mean_age": 57.0,
  258
+                }
  259
+            ]
  260
+        )
  261
+
  262
+        authors = Author.objects.annotate(Avg("friends__age")).order_by("name")
  263
+        self.assertEqual(len(authors), 9)
  264
+        self.assertQuerysetEqual(
  265
+            authors, [
  266
+                (u'Adrian Holovaty', 32.0),
  267
+                (u'Brad Dayley', None),
  268
+                (u'Jacob Kaplan-Moss', 29.5),
  269
+                (u'James Bennett', 34.0),
  270
+                (u'Jeffrey Forcier', 27.0),
  271
+                (u'Paul Bissex', 31.0),
  272
+                (u'Peter Norvig', 46.0),
  273
+                (u'Stuart Russell', 57.0),
  274
+                (u'Wesley J. Chun', Approximate(33.66, places=1))
  275
+            ],
  276
+            lambda a: (a.name, a.friends__age__avg)
  277
+        )
  278
+
  279
+    def test_count(self):
  280
+        vals = Book.objects.aggregate(Count("rating"))
  281
+        self.assertEqual(vals, {"rating__count": 6})
  282
+
  283
+        vals = Book.objects.aggregate(Count("rating", distinct=True))
  284
+        self.assertEqual(vals, {"rating__count": 4})
  285
+
  286
+    def test_fkey_aggregate(self):
  287
+        explicit = list(Author.objects.annotate(Count('book__id')))
  288
+        implicit = list(Author.objects.annotate(Count('book')))
  289
+        self.assertEqual(explicit, implicit)
  290
+
  291
+    def test_annotate_ordering(self):
  292
+        books = Book.objects.values('rating').annotate(oldest=Max('authors__age')).order_by('oldest', 'rating')
  293
+        self.assertEqual(
  294
+            list(books), [
  295
+                {
  296
+                    "rating": 4.5,
  297
+                    "oldest": 35,
  298
+                },
  299
+                {
  300
+                    "rating": 3.0,
  301
+                    "oldest": 45
  302
+                },
  303
+                {
  304
+                    "rating": 4.0,
  305
+                    "oldest": 57,
  306
+                },
  307
+                {
  308
+                    "rating": 5.0,
  309
+                    "oldest": 57,
  310
+                }
  311
+            ]
  312
+        )
  313
+
  314
+        books = Book.objects.values("rating").annotate(oldest=Max("authors__age")).order_by("-oldest", "-rating")
  315
+        self.assertEqual(
  316
+            list(books), [
  317
+                {
  318
+                    "rating": 5.0,
  319
+                    "oldest": 57,
  320
+                },
  321
+                {
  322
+                    "rating": 4.0,
  323
+                    "oldest": 57,
  324
+                },
  325
+                {
  326
+                    "rating": 3.0,
  327
+                    "oldest": 45,
  328
+                },
  329
+                {
  330
+                    "rating": 4.5,
  331
+                    "oldest": 35,
  332
+                }
  333
+            ]
  334
+        )
  335
+
  336
+    def test_aggregate_annotation(self):
  337
+        vals = Book.objects.annotate(num_authors=Count("authors__id")).aggregate(Avg("num_authors"))
  338
+        self.assertEqual(vals, {"num_authors__avg": Approximate(1.66, places=1)})
  339
+
  340
+    def test_filtering(self):
  341
+        p = Publisher.objects.create(name='Expensive Publisher', num_awards=0)
  342
+        Book.objects.create(
  343
+            name='ExpensiveBook1',
  344
+            pages=1,
  345
+            isbn='111',
  346
+            rating=3.5,
  347
+            price=Decimal("1000"),
  348
+            publisher=p,
  349
+            contact_id=1,
  350
+            pubdate=datetime.date(2008,12,1)
  351
+        )
  352
+        Book.objects.create(
  353
+            name='ExpensiveBook2',
  354
+            pages=1,
  355
+            isbn='222',
  356
+            rating=4.0,
  357
+            price=Decimal("1000"),
  358
+            publisher=p,
  359
+            contact_id=1,
  360
+            pubdate=datetime.date(2008,12,2)
  361
+        )
  362
+        Book.objects.create(
  363
+            name='ExpensiveBook3',
  364
+            pages=1,
  365
+            isbn='333',
  366
+            rating=4.5,
  367
+            price=Decimal("35"),
  368
+            publisher=p,
  369
+            contact_id=1,
  370
+            pubdate=datetime.date(2008,12,3)
  371
+        )
  372
+
  373
+        publishers = Publisher.objects.annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk")
  374
+        self.assertQuerysetEqual(
  375
+            publishers, [
  376
+                "Apress",
  377
+                "Prentice Hall",
  378
+                "Expensive Publisher",
  379
+            ],
  380
+            lambda p: p.name,
  381
+        )
  382
+
  383
+        publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).order_by("pk")
  384
+        self.assertQuerysetEqual(
  385
+            publishers, [
  386
+                "Apress",
  387
+                "Apress",
  388
+                "Sams",
  389
+                "Prentice Hall",
  390
+                "Expensive Publisher",
  391
+            ],
  392
+            lambda p: p.name
  393
+        )
  394
+
  395
+        publishers = Publisher.objects.annotate(num_books=Count("book__id")).filter(num_books__gt=1, book__price__lt=Decimal("40.0")).order_by("pk")
  396
+        self.assertQuerysetEqual(
  397
+            publishers, [
  398
+                "Apress",
  399
+                "Prentice Hall",
  400
+                "Expensive Publisher",
  401
+            ],
  402
+            lambda p: p.name,
  403
+        )
  404
+
  405
+        publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk")
  406
+        self.assertQuerysetEqual(
  407
+            publishers, [
  408
+                "Apress",
  409
+            ],
  410
+            lambda p: p.name
  411
+        )
  412
+
  413
+        publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__range=[1, 3]).order_by("pk")
  414
+        self.assertQuerysetEqual(
  415
+            publishers, [
  416
+                "Apress",
  417
+                "Sams",
  418
+                "Prentice Hall",
  419
+                "Morgan Kaufmann",
  420
+                "Expensive Publisher",
  421
+            ],
  422
+            lambda p: p.name
  423
+        )
  424
+
  425
+        publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__range=[1, 2]).order_by("pk")
  426
+        self.assertQuerysetEqual(
  427
+            publishers, [
  428
+                "Apress",
  429
+                "Sams",
  430
+                "Prentice Hall",
  431
+                "Morgan Kaufmann",
  432
+            ],
  433
+            lambda p: p.name
  434
+        )
  435
+
  436
+        publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__in=[1, 3]).order_by("pk")
  437
+        self.assertQuerysetEqual(
  438
+            publishers, [
  439
+                "Sams",
  440
+                "Morgan Kaufmann",
  441
+                "Expensive Publisher",
  442
+            ],
  443
+            lambda p: p.name,
  444
+        )
  445
+
  446
+        publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__isnull=True)
  447
+        self.assertEqual(len(publishers), 0)
  448
+
  449
+    def test_annotation(self):
  450
+        vals = Author.objects.filter(pk=1).aggregate(Count("friends__id"))
  451
+        self.assertEqual(vals, {"friends__id__count": 2})
  452
+
  453
+        books = Book.objects.annotate(num_authors=Count("authors__name")).filter(num_authors__ge=2).order_by("pk")
  454
+        self.assertQuerysetEqual(
  455
+            books, [
  456
+                "The Definitive Guide to Django: Web Development Done Right",
  457
+                "Artificial Intelligence: A Modern Approach",
  458
+            ],
  459
+            lambda b: b.name
  460
+        )
  461
+
  462
+        authors = Author.objects.annotate(num_friends=Count("friends__id", distinct=True)).filter(num_friends=0).order_by("pk")
  463
+        self.assertQuerysetEqual(
  464
+            authors, [
  465
+                "Brad Dayley",
  466
+            ],
  467
+            lambda a: a.name
  468
+        )
  469
+
  470
+        publishers = Publisher.objects.annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk")
  471
+        self.assertQuerysetEqual(
  472
+            publishers, [
  473
+                "Apress",
  474
+                "Prentice Hall",
  475
+            ],
  476
+            lambda p: p.name
  477
+        )
  478
+
  479
+        publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count("book__id")).filter(num_books__gt=1)
  480
+        self.assertQuerysetEqual(
  481
+            publishers, [
  482
+                "Apress",
  483
+            ],
  484
+            lambda p: p.name
  485
+        )
  486
+
  487
+        books = Book.objects.annotate(num_authors=Count("authors__id")).filter(authors__name__contains="Norvig", num_authors__gt=1)
  488
+        self.assertQuerysetEqual(
  489
+            books, [
  490
+                "Artificial Intelligence: A Modern Approach",
  491
+            ],
  492
+            lambda b: b.name
  493
+        )
  494
+
  495
+    def test_more_aggregation(self):
  496
+        a = Author.objects.get(name__contains='Norvig')
  497
+        b = Book.objects.get(name__contains='Done Right')
  498
+        b.authors.add(a)
  499
+        b.save()
  500
+
  501
+        vals = Book.objects.annotate(num_authors=Count("authors__id")).filter(authors__name__contains="Norvig", num_authors__gt=1).aggregate(Avg("rating"))
  502
+        self.assertEqual(vals, {"rating__avg": 4.25})
  503
+
  504
+    def test_even_more_aggregate(self):
  505
+        publishers = Publisher.objects.annotate(earliest_book=Min("book__pubdate")).exclude(earliest_book=None).order_by("earliest_book").values()
  506
+        self.assertEqual(
  507
+            list(publishers), [
  508
+                {
  509
+                    'earliest_book': datetime.date(1991, 10, 15),
  510
+                    'num_awards': 9,
  511
+                    'id': 4,
  512
+                    'name': u'Morgan Kaufmann'
  513
+                },
  514
+                {
  515
+                    'earliest_book': datetime.date(1995, 1, 15),
  516
+                    'num_awards': 7,
  517
+                    'id': 3,
  518
+                    'name': u'Prentice Hall'
  519
+                },
  520
+                {
  521
+                    'earliest_book': datetime.date(2007, 12, 6),
  522
+                    'num_awards': 3,
  523
+                    'id': 1,
  524
+                    'name': u'Apress'
  525
+                },
  526
+                {
  527
+                    'earliest_book': datetime.date(2008, 3, 3),
  528
+                    'num_awards': 1,
  529
+                    'id': 2,
  530
+                    'name': u'Sams'
  531
+                }
  532
+            ]
  533
+        )
  534
+
  535
+        vals = Store.objects.aggregate(Max("friday_night_closing"), Min("original_opening"))
  536
+        self.assertEqual(
  537
+            vals,
  538
+            {
  539
+                "friday_night_closing__max": datetime.time(23, 59, 59),
  540
+                "original_opening__min": datetime.datetime(1945, 4, 25, 16, 24, 14),
  541
+            }
  542
+        )
  543
+
  544
+    def test_annotate_values_list(self):
  545
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("pk", "isbn", "mean_age")
  546
+        self.assertEqual(
  547
+            list(books), [
  548
+                (1, "159059725", 34.5),
  549
+            ]
  550
+        )
  551
+
  552
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("isbn")
  553
+        self.assertEqual(
  554
+            list(books), [
  555
+                ('159059725',)
  556
+            ]
  557
+        )
  558
+
  559
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("mean_age")
  560
+        self.assertEqual(
  561
+            list(books), [
  562
+                (34.5,)
  563
+            ]
  564
+        )
  565
+
  566
+        books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("mean_age", flat=True)
  567
+        self.assertEqual(list(books), [34.5])
  568
+
  569
+        books = Book.objects.values_list("price").annotate(count=Count("price")).order_by("-count", "price")
  570
+        self.assertEqual(
  571
+            list(books), [
  572
+                (Decimal("29.69"), 2),
  573
+                (Decimal('23.09'), 1),
  574
+                (Decimal('30'), 1),
  575
+                (Decimal('75'), 1),
  576
+                (Decimal('82.8'), 1),
  577
+            ]
  578
+        )

0 notes on commit 526a441

Please sign in to comment.
Something went wrong with that request. Please try again.