tests.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. from __future__ import unicode_literals
  2. import datetime
  3. from decimal import Decimal
  4. from django.core.exceptions import FieldError
  5. from django.db.models import (
  6. Sum, Count,
  7. F, Value, Func,
  8. IntegerField, BooleanField, CharField)
  9. from django.db.models.fields import FieldDoesNotExist
  10. from django.test import TestCase
  11. from .models import Author, Book, Store, DepartmentStore, Company, Employee
  12. class NonAggregateAnnotationTestCase(TestCase):
  13. fixtures = ["annotations.json"]
  14. def test_basic_annotation(self):
  15. books = Book.objects.annotate(
  16. is_book=Value(1, output_field=IntegerField()))
  17. for book in books:
  18. self.assertEqual(book.is_book, 1)
  19. def test_basic_f_annotation(self):
  20. books = Book.objects.annotate(another_rating=F('rating'))
  21. for book in books:
  22. self.assertEqual(book.another_rating, book.rating)
  23. def test_joined_annotation(self):
  24. books = Book.objects.select_related('publisher').annotate(
  25. num_awards=F('publisher__num_awards'))
  26. for book in books:
  27. self.assertEqual(book.num_awards, book.publisher.num_awards)
  28. def test_annotate_with_aggregation(self):
  29. books = Book.objects.annotate(
  30. is_book=Value(1, output_field=IntegerField()),
  31. rating_count=Count('rating'))
  32. for book in books:
  33. self.assertEqual(book.is_book, 1)
  34. self.assertEqual(book.rating_count, 1)
  35. def test_aggregate_over_annotation(self):
  36. agg = Author.objects.annotate(other_age=F('age')).aggregate(otherage_sum=Sum('other_age'))
  37. other_agg = Author.objects.aggregate(age_sum=Sum('age'))
  38. self.assertEqual(agg['otherage_sum'], other_agg['age_sum'])
  39. def test_filter_annotation(self):
  40. books = Book.objects.annotate(
  41. is_book=Value(1, output_field=IntegerField())
  42. ).filter(is_book=1)
  43. for book in books:
  44. self.assertEqual(book.is_book, 1)
  45. def test_filter_annotation_with_f(self):
  46. books = Book.objects.annotate(
  47. other_rating=F('rating')
  48. ).filter(other_rating=3.5)
  49. for book in books:
  50. self.assertEqual(book.other_rating, 3.5)
  51. def test_filter_annotation_with_double_f(self):
  52. books = Book.objects.annotate(
  53. other_rating=F('rating')
  54. ).filter(other_rating=F('rating'))
  55. for book in books:
  56. self.assertEqual(book.other_rating, book.rating)
  57. def test_filter_agg_with_double_f(self):
  58. books = Book.objects.annotate(
  59. sum_rating=Sum('rating')
  60. ).filter(sum_rating=F('sum_rating'))
  61. for book in books:
  62. self.assertEqual(book.sum_rating, book.rating)
  63. def test_filter_wrong_annotation(self):
  64. with self.assertRaisesRegexp(FieldError, "Cannot resolve keyword .*"):
  65. list(Book.objects.annotate(
  66. sum_rating=Sum('rating')
  67. ).filter(sum_rating=F('nope')))
  68. def test_update_with_annotation(self):
  69. book_preupdate = Book.objects.get(pk=2)
  70. Book.objects.annotate(other_rating=F('rating') - 1).update(rating=F('other_rating'))
  71. book_postupdate = Book.objects.get(pk=2)
  72. self.assertEqual(book_preupdate.rating - 1, book_postupdate.rating)
  73. def test_annotation_with_m2m(self):
  74. books = Book.objects.annotate(author_age=F('authors__age')).filter(pk=1).order_by('author_age')
  75. self.assertEqual(books[0].author_age, 34)
  76. self.assertEqual(books[1].author_age, 35)
  77. def test_annotation_reverse_m2m(self):
  78. books = Book.objects.annotate(
  79. store_name=F('store__name')).filter(
  80. name='Practical Django Projects').order_by(
  81. 'store_name')
  82. self.assertQuerysetEqual(
  83. books, [
  84. 'Amazon.com',
  85. 'Books.com',
  86. 'Mamma and Pappa\'s Books'
  87. ],
  88. lambda b: b.store_name
  89. )
  90. def test_values_annotation(self):
  91. """
  92. Annotations can reference fields in a values clause,
  93. and contribute to an existing values clause.
  94. """
  95. # annotate references a field in values()
  96. qs = Book.objects.values('rating').annotate(other_rating=F('rating') - 1)
  97. book = qs.get(pk=1)
  98. self.assertEqual(book['rating'] - 1, book['other_rating'])
  99. # filter refs the annotated value
  100. book = qs.get(other_rating=4)
  101. self.assertEqual(book['other_rating'], 4)
  102. # can annotate an existing values with a new field
  103. book = qs.annotate(other_isbn=F('isbn')).get(other_rating=4)
  104. self.assertEqual(book['other_rating'], 4)
  105. self.assertEqual(book['other_isbn'], '155860191')
  106. def test_defer_annotation(self):
  107. """
  108. Deferred attributes can be referenced by an annotation,
  109. but they are not themselves deferred, and cannot be deferred.
  110. """
  111. qs = Book.objects.defer('rating').annotate(other_rating=F('rating') - 1)
  112. with self.assertNumQueries(2):
  113. book = qs.get(other_rating=4)
  114. self.assertEqual(book.rating, 5)
  115. self.assertEqual(book.other_rating, 4)
  116. with self.assertRaisesRegexp(FieldDoesNotExist, "\w has no field named u?'other_rating'"):
  117. book = qs.defer('other_rating').get(other_rating=4)
  118. def test_mti_annotations(self):
  119. """
  120. Fields on an inherited model can be referenced by an
  121. annotated field.
  122. """
  123. d = DepartmentStore.objects.create(
  124. name='Angus & Robinson',
  125. original_opening=datetime.date(2014, 3, 8),
  126. friday_night_closing=datetime.time(21, 00, 00),
  127. chain='Westfield'
  128. )
  129. books = Book.objects.filter(rating__gt=4)
  130. for b in books:
  131. d.books.add(b)
  132. qs = DepartmentStore.objects.annotate(
  133. other_name=F('name'),
  134. other_chain=F('chain'),
  135. is_open=Value(True, BooleanField()),
  136. book_isbn=F('books__isbn')
  137. ).select_related('store').order_by('book_isbn').filter(chain='Westfield')
  138. self.assertQuerysetEqual(
  139. qs, [
  140. ('Angus & Robinson', 'Westfield', True, '155860191'),
  141. ('Angus & Robinson', 'Westfield', True, '159059725')
  142. ],
  143. lambda d: (d.other_name, d.other_chain, d.is_open, d.book_isbn)
  144. )
  145. def test_column_field_ordering(self):
  146. """
  147. Test that columns are aligned in the correct order for
  148. resolve_columns. This test will fail on mysql if column
  149. ordering is out. Column fields should be aligned as:
  150. 1. extra_select
  151. 2. model_fields
  152. 3. annotation_fields
  153. 4. model_related_fields
  154. """
  155. store = Store.objects.first()
  156. Employee.objects.create(id=1, first_name='Max', manager=True, last_name='Paine',
  157. store=store, age=23, salary=Decimal(50000.00))
  158. Employee.objects.create(id=2, first_name='Buffy', manager=False, last_name='Summers',
  159. store=store, age=18, salary=Decimal(40000.00))
  160. qs = Employee.objects.extra(
  161. select={'random_value': '42'}
  162. ).select_related('store').annotate(
  163. annotated_value=Value(17, output_field=IntegerField())
  164. )
  165. rows = [
  166. (1, 'Max', True, 42, 'Paine', 23, Decimal(50000.00), store.name, 17),
  167. (2, 'Buffy', False, 42, 'Summers', 18, Decimal(40000.00), store.name, 17)
  168. ]
  169. self.assertQuerysetEqual(
  170. qs.order_by('id'), rows,
  171. lambda e: (
  172. e.id, e.first_name, e.manager, e.random_value, e.last_name, e.age,
  173. e.salary, e.store.name, e.annotated_value))
  174. def test_column_field_ordering_with_deferred(self):
  175. store = Store.objects.first()
  176. Employee.objects.create(id=1, first_name='Max', manager=True, last_name='Paine',
  177. store=store, age=23, salary=Decimal(50000.00))
  178. Employee.objects.create(id=2, first_name='Buffy', manager=False, last_name='Summers',
  179. store=store, age=18, salary=Decimal(40000.00))
  180. qs = Employee.objects.extra(
  181. select={'random_value': '42'}
  182. ).select_related('store').annotate(
  183. annotated_value=Value(17, output_field=IntegerField())
  184. )
  185. rows = [
  186. (1, 'Max', True, 42, 'Paine', 23, Decimal(50000.00), store.name, 17),
  187. (2, 'Buffy', False, 42, 'Summers', 18, Decimal(40000.00), store.name, 17)
  188. ]
  189. # and we respect deferred columns!
  190. self.assertQuerysetEqual(
  191. qs.defer('age').order_by('id'), rows,
  192. lambda e: (
  193. e.id, e.first_name, e.manager, e.random_value, e.last_name, e.age,
  194. e.salary, e.store.name, e.annotated_value))
  195. def test_custom_functions(self):
  196. Company(name='Apple', motto=None, ticker_name='APPL', description='Beautiful Devices').save()
  197. Company(name='Django Software Foundation', motto=None, ticker_name=None, description=None).save()
  198. Company(name='Google', motto='Do No Evil', ticker_name='GOOG', description='Internet Company').save()
  199. Company(name='Yahoo', motto=None, ticker_name=None, description='Internet Company').save()
  200. qs = Company.objects.annotate(
  201. tagline=Func(
  202. F('motto'),
  203. F('ticker_name'),
  204. F('description'),
  205. Value('No Tag'),
  206. function='COALESCE')
  207. ).order_by('name')
  208. self.assertQuerysetEqual(
  209. qs, [
  210. ('Apple', 'APPL'),
  211. ('Django Software Foundation', 'No Tag'),
  212. ('Google', 'Do No Evil'),
  213. ('Yahoo', 'Internet Company')
  214. ],
  215. lambda c: (c.name, c.tagline)
  216. )
  217. def test_custom_functions_can_ref_other_functions(self):
  218. Company(name='Apple', motto=None, ticker_name='APPL', description='Beautiful Devices').save()
  219. Company(name='Django Software Foundation', motto=None, ticker_name=None, description=None).save()
  220. Company(name='Google', motto='Do No Evil', ticker_name='GOOG', description='Internet Company').save()
  221. Company(name='Yahoo', motto=None, ticker_name=None, description='Internet Company').save()
  222. class Lower(Func):
  223. function = 'LOWER'
  224. qs = Company.objects.annotate(
  225. tagline=Func(
  226. F('motto'),
  227. F('ticker_name'),
  228. F('description'),
  229. Value('No Tag'),
  230. function='COALESCE')
  231. ).annotate(
  232. tagline_lower=Lower(F('tagline'), output_field=CharField())
  233. ).order_by('name')
  234. # LOWER function supported by:
  235. # oracle, postgres, mysql, sqlite, sqlserver
  236. self.assertQuerysetEqual(
  237. qs, [
  238. ('Apple', 'APPL'.lower()),
  239. ('Django Software Foundation', 'No Tag'.lower()),
  240. ('Google', 'Do No Evil'.lower()),
  241. ('Yahoo', 'Internet Company'.lower())
  242. ],
  243. lambda c: (c.name, c.tagline_lower)
  244. )