models.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. # coding: utf-8
  2. from django.db import models
  3. from django.conf import settings
  4. try:
  5. sorted
  6. except NameError:
  7. from django.utils.itercompat import sorted # For Python 2.3
  8. class Author(models.Model):
  9. name = models.CharField(max_length=100)
  10. age = models.IntegerField()
  11. friends = models.ManyToManyField('self', blank=True)
  12. def __unicode__(self):
  13. return self.name
  14. class Publisher(models.Model):
  15. name = models.CharField(max_length=300)
  16. num_awards = models.IntegerField()
  17. def __unicode__(self):
  18. return self.name
  19. class Book(models.Model):
  20. isbn = models.CharField(max_length=9)
  21. name = models.CharField(max_length=300)
  22. pages = models.IntegerField()
  23. rating = models.FloatField()
  24. price = models.DecimalField(decimal_places=2, max_digits=6)
  25. authors = models.ManyToManyField(Author)
  26. contact = models.ForeignKey(Author, related_name='book_contact_set')
  27. publisher = models.ForeignKey(Publisher)
  28. pubdate = models.DateField()
  29. class Meta:
  30. ordering = ('name',)
  31. def __unicode__(self):
  32. return self.name
  33. class Store(models.Model):
  34. name = models.CharField(max_length=300)
  35. books = models.ManyToManyField(Book)
  36. original_opening = models.DateTimeField()
  37. friday_night_closing = models.TimeField()
  38. def __unicode__(self):
  39. return self.name
  40. #Extra does not play well with values. Modify the tests if/when this is fixed.
  41. __test__ = {'API_TESTS': """
  42. >>> from django.core import management
  43. >>> from django.db.models import get_app, F
  44. # Reset the database representation of this app.
  45. # This will return the database to a clean initial state.
  46. >>> management.call_command('flush', verbosity=0, interactive=False)
  47. >>> from django.db.models import Avg, Sum, Count, Max, Min, StdDev, Variance
  48. # Ordering requests are ignored
  49. >>> Author.objects.all().order_by('name').aggregate(Avg('age'))
  50. {'age__avg': 37.4...}
  51. # Implicit ordering is also ignored
  52. >>> Book.objects.all().aggregate(Sum('pages'))
  53. {'pages__sum': 3703}
  54. # Baseline results
  55. >>> Book.objects.all().aggregate(Sum('pages'), Avg('pages'))
  56. {'pages__sum': 3703, 'pages__avg': 617.1...}
  57. # Empty values query doesn't affect grouping or results
  58. >>> Book.objects.all().values().aggregate(Sum('pages'), Avg('pages'))
  59. {'pages__sum': 3703, 'pages__avg': 617.1...}
  60. # Aggregate overrides extra selected column
  61. >>> Book.objects.all().extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages'))
  62. {'pages__sum': 3703}
  63. # Annotations get combined with extra select clauses
  64. >>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).__dict__.items())
  65. [('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)]
  66. # Order of the annotate/extra in the query doesn't matter
  67. >>> sorted(Book.objects.all().extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2).__dict__.items())
  68. [('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)]
  69. # Values queries can be combined with annotate and extra
  70. >>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2).items())
  71. [('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)]
  72. # The order of the (empty) values, annotate and extra clauses doesn't matter
  73. >>> sorted(Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).items())
  74. [('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)]
  75. # If the annotation precedes the values clause, it won't be included
  76. # unless it is explicitly named
  77. >>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1).items())
  78. [('name', u'The Definitive Guide to Django: Web Development Done Right')]
  79. >>> 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())
  80. [('mean_auth_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right')]
  81. # If an annotation isn't included in the values, it can still be used in a filter
  82. >>> Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2)
  83. [{'name': u'Python Web Development with Django'}]
  84. # The annotations are added to values output if values() precedes annotate()
  85. >>> sorted(Book.objects.all().values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1).items())
  86. [('mean_auth_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right')]
  87. # Check that all of the objects are getting counted (allow_nulls) and that values respects the amount of objects
  88. >>> len(Author.objects.all().annotate(Avg('friends__age')).values())
  89. 9
  90. # Check that consecutive calls to annotate accumulate in the query
  91. >>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest', 'price').annotate(Max('publisher__num_awards'))
  92. [{'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}]
  93. # Aggregates can be composed over annotations.
  94. # The return type is derived from the composed aggregate
  95. >>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('pages'), Max('price'), Sum('num_authors'), Avg('num_authors'))
  96. {'num_authors__sum': 10, 'num_authors__avg': 1.66..., 'pages__max': 1132, 'price__max': Decimal("82.80")}
  97. # Bad field requests in aggregates are caught and reported
  98. >>> Book.objects.all().aggregate(num_authors=Count('foo'))
  99. Traceback (most recent call last):
  100. ...
  101. FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, id, isbn, name, pages, price, pubdate, publisher, rating, store
  102. >>> Book.objects.all().annotate(num_authors=Count('foo'))
  103. Traceback (most recent call last):
  104. ...
  105. FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, id, isbn, name, pages, price, pubdate, publisher, rating, store
  106. >>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo'))
  107. Traceback (most recent call last):
  108. ...
  109. FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, id, isbn, name, pages, price, pubdate, publisher, rating, store, num_authors
  110. # Old-style count aggregations can be mixed with new-style
  111. >>> Book.objects.annotate(num_authors=Count('authors')).count()
  112. 6
  113. # Non-ordinal, non-computed Aggregates over annotations correctly inherit
  114. # the annotation's internal type if the annotation is ordinal or computed
  115. >>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Max('num_authors'))
  116. {'num_authors__max': 3}
  117. >>> Publisher.objects.annotate(avg_price=Avg('book__price')).aggregate(Max('avg_price'))
  118. {'avg_price__max': 75.0...}
  119. # Aliases are quoted to protected aliases that might be reserved names
  120. >>> Book.objects.aggregate(number=Max('pages'), select=Max('pages'))
  121. {'number': 1132, 'select': 1132}
  122. # Regression for #10064: select_related() plays nice with aggregates
  123. >>> Book.objects.select_related('publisher').annotate(num_authors=Count('authors')).values()[0]
  124. {'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}
  125. # Regression for #10010: exclude on an aggregate field is correctly negated
  126. >>> len(Book.objects.annotate(num_authors=Count('authors')))
  127. 6
  128. >>> len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=2))
  129. 1
  130. >>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__gt=2))
  131. 5
  132. >>> len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__lt=3).exclude(num_authors__lt=2))
  133. 2
  134. >>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3))
  135. 2
  136. # Aggregates can be used with F() expressions
  137. # ... where the F() is pushed into the HAVING clause
  138. >>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
  139. [{'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}]
  140. >>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
  141. [{'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}]
  142. # ... and where the F() references an aggregate
  143. >>> Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards')
  144. [{'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}]
  145. >>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards')
  146. [{'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}]
  147. # Regression for #10089: Check handling of empty result sets with aggregates
  148. >>> Book.objects.filter(id__in=[]).count()
  149. 0
  150. >>> 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'))
  151. {'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None}
  152. >>> 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()
  153. [{'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}]
  154. # Regression for #10113 - Fields mentioned in order_by() must be included in the GROUP BY.
  155. # This only becomes a problem when the order_by introduces a new join.
  156. >>> Book.objects.annotate(num_authors=Count('authors')).order_by('publisher__name', 'name')
  157. [<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>]
  158. # Regression for #10127 - Empty select_related() works with annotate
  159. >>> books = Book.objects.all().filter(rating__lt=4.5).select_related().annotate(Avg('authors__age'))
  160. >>> sorted([(b.name, b.authors__age__avg, b.publisher.name, b.contact.name) for b in books])
  161. [(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')]
  162. # Regression for #10132 - If the values() clause only mentioned extra(select=) columns, those columns are used for grouping
  163. >>> Book.objects.extra(select={'pub':'publisher_id'}).values('pub').annotate(Count('id')).order_by('pub')
  164. [{'pub': 1, 'id__count': 2}, {'pub': 2, 'id__count': 1}, {'pub': 3, 'id__count': 2}, {'pub': 4, 'id__count': 1}]
  165. >>> Book.objects.extra(select={'pub':'publisher_id','foo':'pages'}).values('pub').annotate(Count('id')).order_by('pub')
  166. [{'pub': 1, 'id__count': 2}, {'pub': 2, 'id__count': 1}, {'pub': 3, 'id__count': 2}, {'pub': 4, 'id__count': 1}]
  167. # Regression for #10182 - Queries with aggregate calls are correctly realiased when used in a subquery
  168. >>> ids = Book.objects.filter(pages__gt=100).annotate(n_authors=Count('authors')).filter(n_authors__gt=2).order_by('n_authors')
  169. >>> Book.objects.filter(id__in=ids)
  170. [<Book: Python Web Development with Django>]
  171. # Regression for #10199 - Aggregate calls clone the original query so the original query can still be used
  172. >>> books = Book.objects.all()
  173. >>> _ = books.aggregate(Avg('authors__age'))
  174. >>> books.all()
  175. [<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>]
  176. # Regression for #10248 - Annotations work with DateQuerySets
  177. >>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors=2).dates('pubdate', 'day')
  178. [datetime.datetime(1995, 1, 15, 0, 0), datetime.datetime(2007, 12, 6, 0, 0)]
  179. """
  180. }
  181. if settings.DATABASE_ENGINE != 'sqlite3':
  182. __test__['API_TESTS'] += """
  183. # Stddev and Variance are not guaranteed to be available for SQLite.
  184. >>> Book.objects.aggregate(StdDev('pages'))
  185. {'pages__stddev': 311.46...}
  186. >>> Book.objects.aggregate(StdDev('rating'))
  187. {'rating__stddev': 0.60...}
  188. >>> Book.objects.aggregate(StdDev('price'))
  189. {'price__stddev': 24.16...}
  190. >>> Book.objects.aggregate(StdDev('pages', sample=True))
  191. {'pages__stddev': 341.19...}
  192. >>> Book.objects.aggregate(StdDev('rating', sample=True))
  193. {'rating__stddev': 0.66...}
  194. >>> Book.objects.aggregate(StdDev('price', sample=True))
  195. {'price__stddev': 26.46...}
  196. >>> Book.objects.aggregate(Variance('pages'))
  197. {'pages__variance': 97010.80...}
  198. >>> Book.objects.aggregate(Variance('rating'))
  199. {'rating__variance': 0.36...}
  200. >>> Book.objects.aggregate(Variance('price'))
  201. {'price__variance': 583.77...}
  202. >>> Book.objects.aggregate(Variance('pages', sample=True))
  203. {'pages__variance': 116412.96...}
  204. >>> Book.objects.aggregate(Variance('rating', sample=True))
  205. {'rating__variance': 0.44...}
  206. >>> Book.objects.aggregate(Variance('price', sample=True))
  207. {'price__variance': 700.53...}
  208. """