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