2009-01-15 19:06:34 +08:00
# coding: utf-8
from django . db import models
from django . conf import settings
try :
sorted
except NameError :
from django . utils . itercompat import sorted # For Python 2.3
class Author ( models . Model ) :
name = models . CharField ( max_length = 100 )
age = models . IntegerField ( )
friends = models . ManyToManyField ( ' self ' , blank = True )
def __unicode__ ( self ) :
return self . name
class Publisher ( models . Model ) :
name = models . CharField ( max_length = 300 )
num_awards = models . IntegerField ( )
def __unicode__ ( self ) :
return self . name
class Book ( models . Model ) :
isbn = models . CharField ( max_length = 9 )
name = models . CharField ( max_length = 300 )
pages = models . IntegerField ( )
rating = models . FloatField ( )
price = models . DecimalField ( decimal_places = 2 , max_digits = 6 )
authors = models . ManyToManyField ( Author )
2009-02-03 19:07:21 +08:00
contact = models . ForeignKey ( Author , related_name = ' book_contact_set ' )
2009-01-15 19:06:34 +08:00
publisher = models . ForeignKey ( Publisher )
pubdate = models . DateField ( )
class Meta :
ordering = ( ' name ' , )
def __unicode__ ( self ) :
return self . name
class Store ( models . Model ) :
name = models . CharField ( max_length = 300 )
books = models . ManyToManyField ( Book )
original_opening = models . DateTimeField ( )
friday_night_closing = models . TimeField ( )
def __unicode__ ( self ) :
return self . name
#Extra does not play well with values. Modify the tests if/when this is fixed.
__test__ = { ' API_TESTS ' : """
>> > from django . core import management
2009-01-29 18:46:36 +08:00
>> > from django . db . models import get_app , F
2009-01-15 19:06:34 +08:00
# Reset the database representation of this app.
# This will return the database to a clean initial state.
>> > management . call_command ( ' flush ' , verbosity = 0 , interactive = False )
>> > from django . db . models import Avg , Sum , Count , Max , Min , StdDev , Variance
# Ordering requests are ignored
>> > Author . objects . all ( ) . order_by ( ' name ' ) . aggregate ( Avg ( ' age ' ) )
{ ' age__avg ' : 37.4 . . . }
# Implicit ordering is also ignored
>> > Book . objects . all ( ) . aggregate ( Sum ( ' pages ' ) )
{ ' pages__sum ' : 3703 }
# Baseline results
>> > Book . objects . all ( ) . aggregate ( Sum ( ' pages ' ) , Avg ( ' pages ' ) )
{ ' pages__sum ' : 3703 , ' pages__avg ' : 617.1 . . . }
# Empty values query doesn't affect grouping or results
>> > Book . objects . all ( ) . values ( ) . aggregate ( Sum ( ' pages ' ) , Avg ( ' pages ' ) )
{ ' pages__sum ' : 3703 , ' pages__avg ' : 617.1 . . . }
# Aggregate overrides extra selected column
>> > Book . objects . all ( ) . extra ( select = { ' price_per_page ' : ' price / pages ' } ) . aggregate ( Sum ( ' pages ' ) )
{ ' pages__sum ' : 3703 }
# Annotations get combined with extra select clauses
>> > sorted ( Book . objects . all ( ) . annotate ( mean_auth_age = Avg ( ' authors__age ' ) ) . extra ( select = { ' manufacture_cost ' : ' price * .5 ' } ) . get ( pk = 2 ) . __dict__ . items ( ) )
2009-02-03 19:07:21 +08:00
[ ( ' 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 ) ]
2009-01-15 19:06:34 +08:00
# Order of the annotate/extra in the query doesn't matter
>> > sorted ( Book . objects . all ( ) . extra ( select = { ' manufacture_cost ' : ' price * .5 ' } ) . annotate ( mean_auth_age = Avg ( ' authors__age ' ) ) . get ( pk = 2 ) . __dict__ . items ( ) )
2009-02-03 19:07:21 +08:00
[ ( ' 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 ) ]
2009-01-15 19:06:34 +08:00
# Values queries can be combined with annotate and extra
>> > sorted ( Book . objects . all ( ) . annotate ( mean_auth_age = Avg ( ' authors__age ' ) ) . extra ( select = { ' manufacture_cost ' : ' price * .5 ' } ) . values ( ) . get ( pk = 2 ) . items ( ) )
2009-02-03 19:07:21 +08:00
[ ( ' 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 ) ]
2009-01-15 19:06:34 +08:00
# The order of the values, annotate and extra clauses doesn't matter
>> > sorted ( Book . objects . all ( ) . values ( ) . annotate ( mean_auth_age = Avg ( ' authors__age ' ) ) . extra ( select = { ' manufacture_cost ' : ' price * .5 ' } ) . get ( pk = 2 ) . items ( ) )
2009-02-03 19:07:21 +08:00
[ ( ' 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 ) ]
2009-01-15 19:06:34 +08:00
# A values query that selects specific columns reduces the output
>> > sorted ( Book . objects . all ( ) . annotate ( mean_auth_age = Avg ( ' authors__age ' ) ) . extra ( select = { ' price_per_page ' : ' price / pages ' } ) . values ( ' name ' ) . get ( pk = 1 ) . items ( ) )
[ ( ' mean_auth_age ' , 34.5 ) , ( ' name ' , u ' The Definitive Guide to Django: Web Development Done Right ' ) ]
# The annotations are added to values output if values() precedes annotate()
>> > sorted ( Book . objects . all ( ) . values ( ' name ' ) . annotate ( mean_auth_age = Avg ( ' authors__age ' ) ) . extra ( select = { ' price_per_page ' : ' price / pages ' } ) . get ( pk = 1 ) . items ( ) )
[ ( ' mean_auth_age ' , 34.5 ) , ( ' name ' , u ' The Definitive Guide to Django: Web Development Done Right ' ) ]
# Check that all of the objects are getting counted (allow_nulls) and that values respects the amount of objects
>> > len ( Author . objects . all ( ) . annotate ( Avg ( ' friends__age ' ) ) . values ( ) )
9
# Check that consecutive calls to annotate accumulate in the query
>> > Book . objects . values ( ' price ' ) . annotate ( oldest = Max ( ' authors__age ' ) ) . order_by ( ' oldest ' , ' price ' ) . annotate ( Max ( ' publisher__num_awards ' ) )
[ { ' 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 } ]
# Aggregates can be composed over annotations.
# The return type is derived from the composed aggregate
>> > Book . objects . all ( ) . annotate ( num_authors = Count ( ' authors__id ' ) ) . aggregate ( Max ( ' pages ' ) , Max ( ' price ' ) , Sum ( ' num_authors ' ) , Avg ( ' num_authors ' ) )
{ ' num_authors__sum ' : 10 , ' num_authors__avg ' : 1.66 . . . , ' pages__max ' : 1132 , ' price__max ' : Decimal ( " 82.80 " ) }
# Bad field requests in aggregates are caught and reported
>> > Book . objects . all ( ) . aggregate ( num_authors = Count ( ' foo ' ) )
Traceback ( most recent call last ) :
. . .
2009-02-03 19:07:21 +08:00
FieldError : Cannot resolve keyword ' foo ' into field . Choices are : authors , contact , id , isbn , name , pages , price , pubdate , publisher , rating , store
2009-01-15 19:06:34 +08:00
>> > Book . objects . all ( ) . annotate ( num_authors = Count ( ' foo ' ) )
Traceback ( most recent call last ) :
. . .
2009-02-03 19:07:21 +08:00
FieldError : Cannot resolve keyword ' foo ' into field . Choices are : authors , contact , id , isbn , name , pages , price , pubdate , publisher , rating , store
2009-01-15 19:06:34 +08:00
>> > Book . objects . all ( ) . annotate ( num_authors = Count ( ' authors__id ' ) ) . aggregate ( Max ( ' foo ' ) )
Traceback ( most recent call last ) :
. . .
2009-02-03 19:07:21 +08:00
FieldError : Cannot resolve keyword ' foo ' into field . Choices are : authors , contact , id , isbn , name , pages , price , pubdate , publisher , rating , store , num_authors
2009-01-15 19:06:34 +08:00
# Old-style count aggregations can be mixed with new-style
>> > Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . count ( )
6
# Non-ordinal, non-computed Aggregates over annotations correctly inherit
# the annotation's internal type if the annotation is ordinal or computed
>> > Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . aggregate ( Max ( ' num_authors ' ) )
{ ' num_authors__max ' : 3 }
>> > Publisher . objects . annotate ( avg_price = Avg ( ' book__price ' ) ) . aggregate ( Max ( ' avg_price ' ) )
{ ' avg_price__max ' : 75.0 . . . }
# Aliases are quoted to protected aliases that might be reserved names
>> > Book . objects . aggregate ( number = Max ( ' pages ' ) , select = Max ( ' pages ' ) )
{ ' number ' : 1132 , ' select ' : 1132 }
2009-01-20 20:23:48 +08:00
# Regression for #10064: select_related() plays nice with aggregates
>> > Book . objects . select_related ( ' publisher ' ) . annotate ( num_authors = Count ( ' authors ' ) ) . values ( ) [ 0 ]
2009-02-03 19:07:21 +08:00
{ ' rating ' : 4.0 , ' isbn ' : u ' 013790395 ' , ' name ' : u ' Artificial Intelligence: A Modern Approach ' , ' pubdate ' : datetime . date ( 1995 , 1 , 15 ) , ' price ' : Decimal ( " 82.8... " ) , ' contact_id ' : 8 , ' id ' : 5 , ' num_authors ' : 2 , ' publisher_id ' : 3 , ' pages ' : 1132 }
2009-01-15 19:06:34 +08:00
2009-01-23 15:07:51 +08:00
# Regression for #10010: exclude on an aggregate field is correctly negated
>> > len ( Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) )
6
>> > len ( Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . filter ( num_authors__gt = 2 ) )
1
>> > len ( Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . exclude ( num_authors__gt = 2 ) )
5
>> > len ( Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . filter ( num_authors__lt = 3 ) . exclude ( num_authors__lt = 2 ) )
2
>> > len ( Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . exclude ( num_authors__lt = 2 ) . filter ( num_authors__lt = 3 ) )
2
2009-01-29 18:46:36 +08:00
# Aggregates can be used with F() expressions
# ... where the F() is pushed into the HAVING clause
2009-01-31 04:15:41 +08:00
>> > Publisher . objects . annotate ( num_books = Count ( ' book ' ) ) . filter ( num_books__lt = F ( ' num_awards ' ) / 2 ) . order_by ( ' name ' ) . values ( ' name ' , ' num_books ' , ' num_awards ' )
[ { ' num_books ' : 1 , ' name ' : u ' Morgan Kaufmann ' , ' num_awards ' : 9 } , { ' num_books ' : 2 , ' name ' : u ' Prentice Hall ' , ' num_awards ' : 7 } ]
2009-01-29 18:46:36 +08:00
2009-01-31 04:15:41 +08:00
>> > Publisher . objects . annotate ( num_books = Count ( ' book ' ) ) . exclude ( num_books__lt = F ( ' num_awards ' ) / 2 ) . order_by ( ' name ' ) . values ( ' name ' , ' num_books ' , ' num_awards ' )
[ { ' 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 } ]
2009-01-29 18:46:36 +08:00
# ... and where the F() references an aggregate
2009-01-31 04:15:41 +08:00
>> > Publisher . objects . annotate ( num_books = Count ( ' book ' ) ) . filter ( num_awards__gt = 2 * F ( ' num_books ' ) ) . order_by ( ' name ' ) . values ( ' name ' , ' num_books ' , ' num_awards ' )
[ { ' num_books ' : 1 , ' name ' : u ' Morgan Kaufmann ' , ' num_awards ' : 9 } , { ' num_books ' : 2 , ' name ' : u ' Prentice Hall ' , ' num_awards ' : 7 } ]
2009-01-29 18:46:36 +08:00
2009-01-31 04:15:41 +08:00
>> > Publisher . objects . annotate ( num_books = Count ( ' book ' ) ) . exclude ( num_books__lt = F ( ' num_awards ' ) / 2 ) . order_by ( ' name ' ) . values ( ' name ' , ' num_books ' , ' num_awards ' )
[ { ' 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 } ]
2009-01-29 18:46:36 +08:00
2009-01-23 19:03:48 +08:00
# Regression for #10089: Check handling of empty result sets with aggregates
>> > Book . objects . filter ( id__in = [ ] ) . count ( )
0
>> > 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 ' ) )
{ ' max_authors ' : None , ' max_rating ' : None , ' num_authors ' : 0 , ' avg_authors ' : None , ' max_price ' : None }
>> > 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 } ]
2009-01-24 14:53:54 +08:00
# Regression for #10113 - Fields mentioned in order_by() must be included in the GROUP BY.
# This only becomes a problem when the order_by introduces a new join.
2009-01-24 22:16:25 +08:00
>> > Book . objects . annotate ( num_authors = Count ( ' authors ' ) ) . order_by ( ' publisher__name ' , ' name ' )
[ < 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 > ]
2009-01-24 14:53:54 +08:00
2009-02-02 21:43:18 +08:00
# Regression for #10127 - Empty select_related() works with annotate
>> > books = Book . objects . all ( ) . filter ( rating__lt = 4.5 ) . select_related ( ) . annotate ( Avg ( ' authors__age ' ) )
2009-02-03 19:07:21 +08:00
>> > sorted ( [ ( b . name , b . authors__age__avg , b . publisher . name , b . contact . name ) for b in books ] )
[ ( 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 ' ) ]
2009-02-02 21:43:18 +08:00
2009-02-08 19:14:07 +08:00
# Regression for #10199 - Aggregate calls clone the original query so the original query can still be used
>> > books = Book . objects . all ( )
>> > _ = books . aggregate ( Avg ( ' authors__age ' ) )
>> > books . all ( )
[ < 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 > ]
2009-01-15 19:06:34 +08:00
"""
}
if settings . DATABASE_ENGINE != ' sqlite3 ' :
__test__ [ ' API_TESTS ' ] + = """
# Stddev and Variance are not guaranteed to be available for SQLite.
>> > Book . objects . aggregate ( StdDev ( ' pages ' ) )
{ ' pages__stddev ' : 311.46 . . . }
>> > Book . objects . aggregate ( StdDev ( ' rating ' ) )
{ ' rating__stddev ' : 0.60 . . . }
>> > Book . objects . aggregate ( StdDev ( ' price ' ) )
{ ' price__stddev ' : 24.16 . . . }
>> > Book . objects . aggregate ( StdDev ( ' pages ' , sample = True ) )
{ ' pages__stddev ' : 341.19 . . . }
>> > Book . objects . aggregate ( StdDev ( ' rating ' , sample = True ) )
{ ' rating__stddev ' : 0.66 . . . }
>> > Book . objects . aggregate ( StdDev ( ' price ' , sample = True ) )
{ ' price__stddev ' : 26.46 . . . }
>> > Book . objects . aggregate ( Variance ( ' pages ' ) )
{ ' pages__variance ' : 97010.80 . . . }
>> > Book . objects . aggregate ( Variance ( ' rating ' ) )
{ ' rating__variance ' : 0.36 . . . }
>> > Book . objects . aggregate ( Variance ( ' price ' ) )
{ ' price__variance ' : 583.77 . . . }
>> > Book . objects . aggregate ( Variance ( ' pages ' , sample = True ) )
{ ' pages__variance ' : 116412.96 . . . }
>> > Book . objects . aggregate ( Variance ( ' rating ' , sample = True ) )
{ ' rating__variance ' : 0.44 . . . }
>> > Book . objects . aggregate ( Variance ( ' price ' , sample = True ) )
{ ' price__variance ' : 700.53 . . . }
"""