Ticket #13634: aggregation-tests.diff
File aggregation-tests.diff, 39.5 KB (added by , 14 years ago) |
---|
-
django/test/testcases.py
diff --git a/django/test/testcases.py b/django/test/testcases.py index 2f8acad..769eaa8 100644
a b class TransactionTestCase(unittest.TestCase): 465 465 self.failIf(template_name in template_names, 466 466 msg_prefix + "Template '%s' was used unexpectedly in rendering" 467 467 " the response" % template_name) 468 469 def assertQuerysetEqual(self, qs, vals, transform=repr): 470 return self.assertEqual(map(transform, qs), vals) 468 471 469 472 def connections_support_transactions(): 470 473 """ -
tests/modeltests/aggregation/models.py
diff --git a/tests/modeltests/aggregation/models.py b/tests/modeltests/aggregation/models.py index f50abe6..ccc1289 100644
a b 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() … … 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 management46 >>> from decimal import Decimal47 >>> from datetime import date48 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, Min58 59 # Single model aggregation60 #61 62 # Single aggregate63 # Average age of Authors64 >>> Author.objects.all().aggregate(Avg('age'))65 {'age__avg': 37.4...}66 67 # Multiple aggregates68 # Average and Sum of Author ages69 >>> Author.objects.all().aggregate(Sum('age'), Avg('age'))70 {'age__sum': 337, 'age__avg': 37.4...}71 72 # Aggreates interact with filters, and only73 # generate aggregate values for the filtered values74 # Sum of the age of those older than 29 years old75 >>> Author.objects.all().filter(age__gt=29).aggregate(Sum('age'))76 {'age__sum': 254}77 78 # Depth-1 Joins79 #80 81 # On Relationships with self82 # Average age of the friends of each author83 >>> Author.objects.all().aggregate(Avg('friends__age'))84 {'friends__age__avg': 34.07...}85 86 # On ManyToMany Relationships87 #88 89 # Forward90 # Average age of the Authors of Books with a rating of less than 4.591 >>> Book.objects.all().filter(rating__lt=4.5).aggregate(Avg('authors__age'))92 {'authors__age__avg': 38.2...}93 94 # Backward95 # 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 Relationships100 #101 102 # Forward103 # Sum of the number of awards of each Book's Publisher104 >>> Book.objects.all().aggregate(Sum('publisher__num_awards'))105 {'publisher__num_awards__sum': 30}106 107 # Backward108 # Sum of the price of every Book that has a Publisher109 >>> Publisher.objects.all().aggregate(Sum('book__price'))110 {'book__price__sum': Decimal("270.27")}111 112 # Multiple Joins113 #114 115 # Forward116 >>> Store.objects.all().aggregate(Max('books__authors__age'))117 {'books__authors__age__max': 57}118 119 # Backward120 # Note that the very long default alias may be truncated121 >>> 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 rating127 >>> 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 QuerySet133 >>> 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 result137 >>> books = Book.objects.all().annotate(mean_age=Avg('authors__age'))138 >>> books.get(pk=1).name139 u'The Definitive Guide to Django: Web Development Done Right'140 141 >>> books.get(pk=1).mean_age142 34.5143 144 # On ManyToMany Relationships145 146 # Forward147 # Average age of the Authors of each book with a rating less than 4.5148 >>> 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 book153 >>> 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 # Backward158 # 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 author164 >>> 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 Relationships169 170 # Forward171 # Annotate each book with the number of awards of each Book's Publisher172 >>> 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 # Backward177 # Annotate each publisher with the sum of the price of all books sold178 >>> 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 output185 # as a dictionary186 >>> [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 output193 >>> 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 an197 # empty values() call after annotating198 >>> [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 of202 # fields to be selected by the ValuesQuerySet.203 204 # Note that an extra parameter is added to each dictionary. This205 # parameter is a queryset representing the objects that have been206 # grouped to generate the annotation207 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 None212 >>> authors = Author.objects.all().annotate(Avg('friends__age')).order_by('id')213 >>> len(authors)214 9215 >>> 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 items221 >>> 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 objects228 229 # When using Count you can also omit the primary key and refer only to230 # the related field name if you want to count all the related objects231 # and not a specific column232 >>> explicit = list(Author.objects.annotate(Count('book__id')))233 >>> implicit = list(Author.objects.annotate(Count('book')))234 >>> explicit == implicit235 True236 237 # Ordering is allowed on aggregates238 >>> 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 values245 >>> 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 filtering251 >>> 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 book260 >>> 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 40264 >>> 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 40268 >>> 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 $40272 >>> 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 types276 #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 authors297 >>> 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 friends301 >>> 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 books305 >>> 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 40309 >>> 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 way320 >>> 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 it326 >>> 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 person331 # (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 available335 336 # Aggregates also work on dates, times and datetimes337 >>> 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 used344 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 True360 361 """} -
new file tests/modeltests/aggregation/tests.py
diff --git a/tests/modeltests/aggregation/tests.py b/tests/modeltests/aggregation/tests.py new file mode 100644 index 0000000..d80b478
- + 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 tests_single_aggregates(self): 30 vals = Author.objects.aggregate(Avg("age")) 31 self.assertEqual(vals, {"age__avg": Approximate(37.4, places=1)}) 32 33 vals = Author.objects.aggregate(Sum("age"), Avg("age")) 34 self.assertEqual(vals, {"age__sum": 337, "age__avg": Approximate(37.4, places=1)}) 35 36 def test_filter_aggregate(self): 37 vals = Author.objects.filter(age__gt=29).aggregate(Sum("age")) 38 self.assertEqual(len(vals), 1) 39 self.assertEqual(vals["age__sum"], 254) 40 41 def test_related_aggregate(self): 42 vals = Author.objects.aggregate(Avg("friends__age")) 43 self.assertEqual(len(vals), 1) 44 self.assertAlmostEqual(vals["friends__age__avg"], 34.07, places=2) 45 46 vals = Book.objects.filter(rating__lt=4.5).aggregate(Avg("authors__age")) 47 self.assertEqual(len(vals), 1) 48 self.assertAlmostEqual(vals["authors__age__avg"], 38.2857, places=2) 49 50 vals = Author.objects.all().filter(name__contains="a").aggregate(Avg("book__rating")) 51 self.assertEqual(len(vals), 1) 52 self.assertEqual(vals["book__rating__avg"], 4.0) 53 54 vals = Book.objects.aggregate(Sum("publisher__num_awards")) 55 self.assertEqual(len(vals), 1) 56 self.assertEquals(vals["publisher__num_awards__sum"], 30) 57 58 vals = Publisher.objects.aggregate(Sum("book__price")) 59 self.assertEqual(len(vals), 1) 60 self.assertEqual(vals["book__price__sum"], Decimal("270.27")) 61 62 def test_aggregate_multi_join(self): 63 vals = Store.objects.aggregate(Max("books__authors__age")) 64 self.assertEqual(len(vals), 1) 65 self.assertEqual(vals["books__authors__age__max"], 57) 66 67 vals = Author.objects.aggregate(Min("book__publisher__num_awards")) 68 self.assertEqual(len(vals), 1) 69 self.assertEqual(vals["book__publisher__num_awards__min"], 1) 70 71 def test_aggregate_alias(self): 72 vals = Store.objects.filter(name="Amazon.com").aggregate(amazon_mean=Avg("books__rating")) 73 self.assertEqual(len(vals), 1) 74 self.assertAlmostEqual(vals["amazon_mean"], 4.08, places=2) 75 76 def test_annotate_basic(self): 77 self.assertQuerysetEqual( 78 Book.objects.annotate().order_by('pk'), [ 79 "The Definitive Guide to Django: Web Development Done Right", 80 "Sams Teach Yourself Django in 24 Hours", 81 "Practical Django Projects", 82 "Python Web Development with Django", 83 "Artificial Intelligence: A Modern Approach", 84 "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp" 85 ], 86 lambda b: b.name 87 ) 88 89 books = Book.objects.annotate(mean_age=Avg("authors__age")) 90 b = books.get(pk=1) 91 self.assertEqual( 92 b.name, 93 u'The Definitive Guide to Django: Web Development Done Right' 94 ) 95 self.assertEqual(b.mean_age, 34.5) 96 97 def test_annotate_m2m(self): 98 books = Book.objects.filter(rating__lt=4.5).annotate(Avg("authors__age")).order_by("name") 99 self.assertQuerysetEqual( 100 books, [ 101 (u'Artificial Intelligence: A Modern Approach', 51.5), 102 (u'Practical Django Projects', 29.0), 103 (u'Python Web Development with Django', Approximate(30.3, places=1)), 104 (u'Sams Teach Yourself Django in 24 Hours', 45.0) 105 ], 106 lambda b: (b.name, b.authors__age__avg), 107 ) 108 109 books = Book.objects.annotate(num_authors=Count("authors")).order_by("name") 110 self.assertQuerysetEqual( 111 books, [ 112 (u'Artificial Intelligence: A Modern Approach', 2), 113 (u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 1), 114 (u'Practical Django Projects', 1), 115 (u'Python Web Development with Django', 3), 116 (u'Sams Teach Yourself Django in 24 Hours', 1), 117 (u'The Definitive Guide to Django: Web Development Done Right', 2) 118 ], 119 lambda b: (b.name, b.num_authors) 120 ) 121 122 def test_backwards_m2m_annotate(self): 123 authors = Author.objects.filter(name__contains="a").annotate(Avg("book__rating")).order_by("name") 124 self.assertQuerysetEqual( 125 authors, [ 126 (u'Adrian Holovaty', 4.5), 127 (u'Brad Dayley', 3.0), 128 (u'Jacob Kaplan-Moss', 4.5), 129 (u'James Bennett', 4.0), 130 (u'Paul Bissex', 4.0), 131 (u'Stuart Russell', 4.0) 132 ], 133 lambda a: (a.name, a.book__rating__avg) 134 ) 135 136 authors = Author.objects.annotate(num_books=Count("book")).order_by("name") 137 self.assertQuerysetEqual( 138 authors, [ 139 (u'Adrian Holovaty', 1), 140 (u'Brad Dayley', 1), 141 (u'Jacob Kaplan-Moss', 1), 142 (u'James Bennett', 1), 143 (u'Jeffrey Forcier', 1), 144 (u'Paul Bissex', 1), 145 (u'Peter Norvig', 2), 146 (u'Stuart Russell', 1), 147 (u'Wesley J. Chun', 1) 148 ], 149 lambda a: (a.name, a.num_books) 150 ) 151 152 def test_reverse_fkey_annotate(self): 153 books = Book.objects.annotate(Sum("publisher__num_awards")).order_by("name") 154 self.assertQuerysetEqual( 155 books, [ 156 (u'Artificial Intelligence: A Modern Approach', 7), 157 (u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 9), 158 (u'Practical Django Projects', 3), 159 (u'Python Web Development with Django', 7), 160 (u'Sams Teach Yourself Django in 24 Hours', 1), 161 (u'The Definitive Guide to Django: Web Development Done Right', 3) 162 ], 163 lambda b: (b.name, b.publisher__num_awards__sum) 164 ) 165 166 publishers = Publisher.objects.annotate(Sum("book__price")).order_by("name") 167 self.assertQuerysetEqual( 168 publishers, [ 169 (u'Apress', Decimal("59.69")), 170 (u"Jonno's House of Books", None), 171 (u'Morgan Kaufmann', Decimal("75.00")), 172 (u'Prentice Hall', Decimal("112.49")), 173 (u'Sams', Decimal("23.09")) 174 ], 175 lambda p: (p.name, p.book__price__sum) 176 ) 177 178 def test_annotate_values(self): 179 books = list(Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values()) 180 self.assertEqual( 181 books, [ 182 { 183 "contact_id": 1, 184 "id": 1, 185 "isbn": "159059725", 186 "mean_age": 34.5, 187 "name": "The Definitive Guide to Django: Web Development Done Right", 188 "pages": 447, 189 "price": Approximate(Decimal("30")), 190 "pubdate": datetime.date(2007, 12, 6), 191 "publisher_id": 1, 192 "rating": 4.5, 193 } 194 ] 195 ) 196 197 books = Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values('pk', 'isbn', 'mean_age') 198 self.assertEqual( 199 list(books), [ 200 { 201 "pk": 1, 202 "isbn": "159059725", 203 "mean_age": 34.5, 204 } 205 ] 206 ) 207 208 books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values("name") 209 self.assertEqual( 210 list(books), [ 211 { 212 "name": "The Definitive Guide to Django: Web Development Done Right" 213 } 214 ] 215 ) 216 217 books = Book.objects.filter(pk=1).values().annotate(mean_age=Avg('authors__age')) 218 self.assertEqual( 219 list(books), [ 220 { 221 "contact_id": 1, 222 "id": 1, 223 "isbn": "159059725", 224 "mean_age": 34.5, 225 "name": "The Definitive Guide to Django: Web Development Done Right", 226 "pages": 447, 227 "price": Approximate(Decimal("30")), 228 "pubdate": datetime.date(2007, 12, 6), 229 "publisher_id": 1, 230 "rating": 4.5, 231 } 232 ] 233 ) 234 235 books = Book.objects.values("rating").annotate(n_authors=Count("authors__id"), mean_age=Avg("authors__age")).order_by("rating") 236 self.assertEqual( 237 list(books), [ 238 { 239 "rating": 3.0, 240 "n_authors": 1, 241 "mean_age": 45.0, 242 }, 243 { 244 "rating": 4.0, 245 "n_authors": 6, 246 "mean_age": Approximate(37.16, places=1) 247 }, 248 { 249 "rating": 4.5, 250 "n_authors": 2, 251 "mean_age": 34.5, 252 }, 253 { 254 "rating": 5.0, 255 "n_authors": 1, 256 "mean_age": 57.0, 257 } 258 ] 259 ) 260 261 authors = Author.objects.annotate(Avg("friends__age")).order_by("name") 262 self.assertEqual(len(authors), 9) 263 self.assertQuerysetEqual( 264 authors, [ 265 (u'Adrian Holovaty', 32.0), 266 (u'Brad Dayley', None), 267 (u'Jacob Kaplan-Moss', 29.5), 268 (u'James Bennett', 34.0), 269 (u'Jeffrey Forcier', 27.0), 270 (u'Paul Bissex', 31.0), 271 (u'Peter Norvig', 46.0), 272 (u'Stuart Russell', 57.0), 273 (u'Wesley J. Chun', Approximate(33.66, places=1)) 274 ], 275 lambda a: (a.name, a.friends__age__avg) 276 ) 277 278 def test_count(self): 279 vals = Book.objects.aggregate(Count("rating")) 280 self.assertEqual(vals, {"rating__count": 6}) 281 282 vals = Book.objects.aggregate(Count("rating", distinct=True)) 283 self.assertEqual(vals, {"rating__count": 4}) 284 285 def test_fkey_aggregate(self): 286 explicit = list(Author.objects.annotate(Count('book__id'))) 287 implicit = list(Author.objects.annotate(Count('book'))) 288 self.assertEqual(explicit, implicit) 289 290 def test_annotate_ordering(self): 291 books = Book.objects.values('rating').annotate(oldest=Max('authors__age')).order_by('oldest', 'rating') 292 self.assertEqual( 293 list(books), [ 294 { 295 "rating": 4.5, 296 "oldest": 35, 297 }, 298 { 299 "rating": 3.0, 300 "oldest": 45 301 }, 302 { 303 "rating": 4.0, 304 "oldest": 57, 305 }, 306 { 307 "rating": 5.0, 308 "oldest": 57, 309 } 310 ] 311 ) 312 313 books = Book.objects.values("rating").annotate(oldest=Max("authors__age")).order_by("-oldest", "-rating") 314 self.assertEqual( 315 list(books), [ 316 { 317 "rating": 5.0, 318 "oldest": 57, 319 }, 320 { 321 "rating": 4.0, 322 "oldest": 57, 323 }, 324 { 325 "rating": 3.0, 326 "oldest": 45, 327 }, 328 { 329 "rating": 4.5, 330 "oldest": 35, 331 } 332 ] 333 ) 334 335 def test_aggregate_annotation(self): 336 vals = Book.objects.annotate(num_authors=Count("authors__id")).aggregate(Avg("num_authors")) 337 self.assertEqual(vals, {"num_authors__avg": Approximate(1.66, places=1)}) 338 339 def test_filtering(self): 340 p = Publisher.objects.create(name='Expensive Publisher', num_awards=0) 341 Book.objects.create( 342 name='ExpensiveBook1', 343 pages=1, 344 isbn='111', 345 rating=3.5, 346 price=Decimal("1000"), 347 publisher=p, 348 contact_id=1, 349 pubdate=datetime.date(2008,12,1) 350 ) 351 Book.objects.create( 352 name='ExpensiveBook2', 353 pages=1, 354 isbn='222', 355 rating=4.0, 356 price=Decimal("1000"), 357 publisher=p, 358 contact_id=1, 359 pubdate=datetime.date(2008,12,2) 360 ) 361 Book.objects.create( 362 name='ExpensiveBook3', 363 pages=1, 364 isbn='333', 365 rating=4.5, 366 price=Decimal("35"), 367 publisher=p, 368 contact_id=1, 369 pubdate=datetime.date(2008,12,3) 370 ) 371 372 publishers = Publisher.objects.annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk") 373 self.assertQuerysetEqual( 374 publishers, [ 375 "Apress", 376 "Prentice Hall", 377 "Expensive Publisher", 378 ], 379 lambda p: p.name, 380 ) 381 382 publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).order_by("pk") 383 self.assertQuerysetEqual( 384 publishers, [ 385 "Apress", 386 "Apress", 387 "Sams", 388 "Prentice Hall", 389 "Expensive Publisher", 390 ], 391 lambda p: p.name 392 ) 393 394 publishers = Publisher.objects.annotate(num_books=Count("book__id")).filter(num_books__gt=1, book__price__lt=Decimal("40.0")).order_by("pk") 395 self.assertQuerysetEqual( 396 publishers, [ 397 "Apress", 398 "Prentice Hall", 399 "Expensive Publisher", 400 ], 401 lambda p: p.name, 402 ) 403 404 publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk") 405 self.assertQuerysetEqual( 406 publishers, [ 407 "Apress", 408 ], 409 lambda p: p.name 410 ) 411 412 publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__range=[1, 3]).order_by("pk") 413 self.assertQuerysetEqual( 414 publishers, [ 415 "Apress", 416 "Sams", 417 "Prentice Hall", 418 "Morgan Kaufmann", 419 "Expensive Publisher", 420 ], 421 lambda p: p.name 422 ) 423 424 publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__range=[1, 2]).order_by("pk") 425 self.assertQuerysetEqual( 426 publishers, [ 427 "Apress", 428 "Sams", 429 "Prentice Hall", 430 "Morgan Kaufmann", 431 ], 432 lambda p: p.name 433 ) 434 435 publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__in=[1, 3]).order_by("pk") 436 self.assertQuerysetEqual( 437 publishers, [ 438 "Sams", 439 "Morgan Kaufmann", 440 "Expensive Publisher", 441 ], 442 lambda p: p.name, 443 ) 444 445 publishers = Publisher.objects.annotate(num_books=Count("book")).filter(num_books__isnull=True) 446 self.assertEqual(len(publishers), 0) 447 448 def test_annotation(self): 449 vals = Author.objects.filter(pk=1).aggregate(Count("friends__id")) 450 self.assertEqual(vals, {"friends__id__count": 2}) 451 452 books = Book.objects.annotate(num_authors=Count("authors__name")).filter(num_authors__ge=2).order_by("pk") 453 self.assertQuerysetEqual( 454 books, [ 455 "The Definitive Guide to Django: Web Development Done Right", 456 "Artificial Intelligence: A Modern Approach", 457 ], 458 lambda b: b.name 459 ) 460 461 authors = Author.objects.annotate(num_friends=Count("friends__id", distinct=True)).filter(num_friends=0).order_by("pk") 462 self.assertQuerysetEqual( 463 authors, [ 464 "Brad Dayley", 465 ], 466 lambda a: a.name 467 ) 468 469 publishers = Publisher.objects.annotate(num_books=Count("book__id")).filter(num_books__gt=1).order_by("pk") 470 self.assertQuerysetEqual( 471 publishers, [ 472 "Apress", 473 "Prentice Hall", 474 ], 475 lambda p: p.name 476 ) 477 478 publishers = Publisher.objects.filter(book__price__lt=Decimal("40.0")).annotate(num_books=Count("book__id")).filter(num_books__gt=1) 479 self.assertQuerysetEqual( 480 publishers, [ 481 "Apress", 482 ], 483 lambda p: p.name 484 ) 485 486 books = Book.objects.annotate(num_authors=Count("authors__id")).filter(authors__name__contains="Norvig", num_authors__gt=1) 487 self.assertQuerysetEqual( 488 books, [ 489 "Artificial Intelligence: A Modern Approach", 490 ], 491 lambda b: b.name 492 ) 493 494 def test_more_aggregation(self): 495 a = Author.objects.get(name__contains='Norvig') 496 b = Book.objects.get(name__contains='Done Right') 497 b.authors.add(a) 498 b.save() 499 500 vals = Book.objects.annotate(num_authors=Count("authors__id")).filter(authors__name__contains="Norvig", num_authors__gt=1).aggregate(Avg("rating")) 501 self.assertEqual(vals, {"rating__avg": 4.25}) 502 503 def test_even_more_aggregate(self): 504 publishers = Publisher.objects.annotate(earliest_book=Min("book__pubdate")).exclude(earliest_book=None).order_by("earliest_book").values() 505 self.assertEqual( 506 list(publishers), [ 507 { 508 'earliest_book': datetime.date(1991, 10, 15), 509 'num_awards': 9, 510 'id': 4, 511 'name': u'Morgan Kaufmann' 512 }, 513 { 514 'earliest_book': datetime.date(1995, 1, 15), 515 'num_awards': 7, 516 'id': 3, 517 'name': u'Prentice Hall' 518 }, 519 { 520 'earliest_book': datetime.date(2007, 12, 6), 521 'num_awards': 3, 522 'id': 1, 523 'name': u'Apress' 524 }, 525 { 526 'earliest_book': datetime.date(2008, 3, 3), 527 'num_awards': 1, 528 'id': 2, 529 'name': u'Sams' 530 } 531 ] 532 ) 533 534 publishers = Publisher.objects.annotate(earliest_book=Min("book__pubdate")).exclude(earliest_book=None).order_by("earliest_book").values() 535 self.assertEqual( 536 list(publishers), [ 537 { 538 'earliest_book': datetime.date(1991, 10, 15), 539 'num_awards': 9, 540 'id': 4, 541 'name': u'Morgan Kaufmann' 542 }, 543 { 544 'earliest_book': datetime.date(1995, 1, 15), 545 'num_awards': 7, 546 'id': 3, 547 'name': u'Prentice Hall' 548 }, 549 { 550 'earliest_book': datetime.date(2007, 12, 6), 551 'num_awards': 3, 552 'id': 1, 553 'name': u'Apress', 554 }, 555 { 556 'earliest_book': datetime.date(2008, 3, 3), 557 'num_awards': 1, 558 'id': 2, 559 'name': u'Sams' 560 } 561 ] 562 ) 563 564 vals = Store.objects.aggregate(Max("friday_night_closing"), Min("original_opening")) 565 self.assertEqual( 566 vals, 567 { 568 "friday_night_closing__max": datetime.time(23, 59, 59), 569 "original_opening__min": datetime.datetime(1945, 4, 25, 16, 24, 14), 570 } 571 ) 572 573 def test_annotate_values_list(self): 574 books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("pk", "isbn", "mean_age") 575 self.assertEqual( 576 list(books), [ 577 (1, "159059725", 34.5), 578 ] 579 ) 580 581 books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("isbn") 582 self.assertEqual( 583 list(books), [ 584 ('159059725',) 585 ] 586 ) 587 588 books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("mean_age") 589 self.assertEqual( 590 list(books), [ 591 (34.5,) 592 ] 593 ) 594 595 books = Book.objects.filter(pk=1).annotate(mean_age=Avg("authors__age")).values_list("mean_age", flat=True) 596 self.assertEqual(list(books), [34.5]) 597 598 books = Book.objects.values_list("price").annotate(count=Count("price")).order_by("-count", "price") 599 self.assertEqual( 600 list(books), [ 601 (Decimal("29.69"), 2), 602 (Decimal('23.09'), 1), 603 (Decimal('30'), 1), 604 (Decimal('75'), 1), 605 (Decimal('82.8'), 1), 606 ] 607 )