Ticket #13634: aggregation-tests.diff

File aggregation-tests.diff, 39.5 KB (added by Alex Gaynor, 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):  
    465465        self.failIf(template_name in template_names,
    466466            msg_prefix + "Template '%s' was used unexpectedly in rendering"
    467467            " the response" % template_name)
     468   
     469    def assertQuerysetEqual(self, qs, vals, transform=repr):
     470        return self.assertEqual(map(transform, qs), vals)
    468471
    469472def connections_support_transactions():
    470473    """
  • 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  
    11# coding: utf-8
    22from django.db import models
    33
     4
    45class Author(models.Model):
    56    name = models.CharField(max_length=100)
    67    age = models.IntegerField()
    class Store(models.Model):  
    3940    def __unicode__(self):
    4041        return self.name
    4142
    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 """}
  • 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
    - +  
     1import datetime
     2from decimal import Decimal
     3
     4from django.db.models import Avg, Sum, Count, Max, Min
     5from django.test import TestCase
     6
     7from models import Author, Publisher, Book, Store
     8
     9
     10class 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
     23class 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        )
Back to Top