tests.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. import datetime
  2. from decimal import Decimal
  3. from unittest import skipIf
  4. from django.core.exceptions import FieldDoesNotExist, FieldError
  5. from django.db import connection
  6. from django.db.models import (
  7. BooleanField, Case, Count, DateTimeField, Exists, ExpressionWrapper, F,
  8. FloatField, Func, IntegerField, Max, NullBooleanField, OuterRef, Q,
  9. Subquery, Sum, Value, When,
  10. )
  11. from django.db.models.expressions import RawSQL
  12. from django.db.models.functions import Length, Lower
  13. from django.test import TestCase, skipUnlessDBFeature
  14. from .models import (
  15. Author, Book, Company, DepartmentStore, Employee, Publisher, Store, Ticket,
  16. )
  17. def cxOracle_py3_bug(func):
  18. """
  19. There's a bug in Django/cx_Oracle with respect to string handling under
  20. Python 3 (essentially, they treat Python 3 strings as Python 2 strings
  21. rather than unicode). This makes some tests here fail under Python 3, so
  22. we mark them as expected failures until someone fixes them in #23843.
  23. """
  24. from unittest import expectedFailure
  25. from django.db import connection
  26. return expectedFailure(func) if connection.vendor == 'oracle' else func
  27. class NonAggregateAnnotationTestCase(TestCase):
  28. @classmethod
  29. def setUpTestData(cls):
  30. cls.a1 = Author.objects.create(name='Adrian Holovaty', age=34)
  31. cls.a2 = Author.objects.create(name='Jacob Kaplan-Moss', age=35)
  32. cls.a3 = Author.objects.create(name='Brad Dayley', age=45)
  33. cls.a4 = Author.objects.create(name='James Bennett', age=29)
  34. cls.a5 = Author.objects.create(name='Jeffrey Forcier', age=37)
  35. cls.a6 = Author.objects.create(name='Paul Bissex', age=29)
  36. cls.a7 = Author.objects.create(name='Wesley J. Chun', age=25)
  37. cls.a8 = Author.objects.create(name='Peter Norvig', age=57)
  38. cls.a9 = Author.objects.create(name='Stuart Russell', age=46)
  39. cls.a1.friends.add(cls.a2, cls.a4)
  40. cls.a2.friends.add(cls.a1, cls.a7)
  41. cls.a4.friends.add(cls.a1)
  42. cls.a5.friends.add(cls.a6, cls.a7)
  43. cls.a6.friends.add(cls.a5, cls.a7)
  44. cls.a7.friends.add(cls.a2, cls.a5, cls.a6)
  45. cls.a8.friends.add(cls.a9)
  46. cls.a9.friends.add(cls.a8)
  47. cls.p1 = Publisher.objects.create(name='Apress', num_awards=3)
  48. cls.p2 = Publisher.objects.create(name='Sams', num_awards=1)
  49. cls.p3 = Publisher.objects.create(name='Prentice Hall', num_awards=7)
  50. cls.p4 = Publisher.objects.create(name='Morgan Kaufmann', num_awards=9)
  51. cls.p5 = Publisher.objects.create(name="Jonno's House of Books", num_awards=0)
  52. cls.b1 = Book.objects.create(
  53. isbn='159059725', name='The Definitive Guide to Django: Web Development Done Right',
  54. pages=447, rating=4.5, price=Decimal('30.00'), contact=cls.a1, publisher=cls.p1,
  55. pubdate=datetime.date(2007, 12, 6)
  56. )
  57. cls.b2 = Book.objects.create(
  58. isbn='067232959', name='Sams Teach Yourself Django in 24 Hours',
  59. pages=528, rating=3.0, price=Decimal('23.09'), contact=cls.a3, publisher=cls.p2,
  60. pubdate=datetime.date(2008, 3, 3)
  61. )
  62. cls.b3 = Book.objects.create(
  63. isbn='159059996', name='Practical Django Projects',
  64. pages=300, rating=4.0, price=Decimal('29.69'), contact=cls.a4, publisher=cls.p1,
  65. pubdate=datetime.date(2008, 6, 23)
  66. )
  67. cls.b4 = Book.objects.create(
  68. isbn='013235613', name='Python Web Development with Django',
  69. pages=350, rating=4.0, price=Decimal('29.69'), contact=cls.a5, publisher=cls.p3,
  70. pubdate=datetime.date(2008, 11, 3)
  71. )
  72. cls.b5 = Book.objects.create(
  73. isbn='013790395', name='Artificial Intelligence: A Modern Approach',
  74. pages=1132, rating=4.0, price=Decimal('82.80'), contact=cls.a8, publisher=cls.p3,
  75. pubdate=datetime.date(1995, 1, 15)
  76. )
  77. cls.b6 = Book.objects.create(
  78. isbn='155860191', name='Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp',
  79. pages=946, rating=5.0, price=Decimal('75.00'), contact=cls.a8, publisher=cls.p4,
  80. pubdate=datetime.date(1991, 10, 15)
  81. )
  82. cls.b1.authors.add(cls.a1, cls.a2)
  83. cls.b2.authors.add(cls.a3)
  84. cls.b3.authors.add(cls.a4)
  85. cls.b4.authors.add(cls.a5, cls.a6, cls.a7)
  86. cls.b5.authors.add(cls.a8, cls.a9)
  87. cls.b6.authors.add(cls.a8)
  88. s1 = Store.objects.create(
  89. name='Amazon.com',
  90. original_opening=datetime.datetime(1994, 4, 23, 9, 17, 42),
  91. friday_night_closing=datetime.time(23, 59, 59)
  92. )
  93. s2 = Store.objects.create(
  94. name='Books.com',
  95. original_opening=datetime.datetime(2001, 3, 15, 11, 23, 37),
  96. friday_night_closing=datetime.time(23, 59, 59)
  97. )
  98. s3 = Store.objects.create(
  99. name="Mamma and Pappa's Books",
  100. original_opening=datetime.datetime(1945, 4, 25, 16, 24, 14),
  101. friday_night_closing=datetime.time(21, 30)
  102. )
  103. s1.books.add(cls.b1, cls.b2, cls.b3, cls.b4, cls.b5, cls.b6)
  104. s2.books.add(cls.b1, cls.b3, cls.b5, cls.b6)
  105. s3.books.add(cls.b3, cls.b4, cls.b6)
  106. def test_basic_annotation(self):
  107. books = Book.objects.annotate(is_book=Value(1))
  108. for book in books:
  109. self.assertEqual(book.is_book, 1)
  110. def test_basic_f_annotation(self):
  111. books = Book.objects.annotate(another_rating=F('rating'))
  112. for book in books:
  113. self.assertEqual(book.another_rating, book.rating)
  114. def test_joined_annotation(self):
  115. books = Book.objects.select_related('publisher').annotate(
  116. num_awards=F('publisher__num_awards'))
  117. for book in books:
  118. self.assertEqual(book.num_awards, book.publisher.num_awards)
  119. def test_mixed_type_annotation_date_interval(self):
  120. active = datetime.datetime(2015, 3, 20, 14, 0, 0)
  121. duration = datetime.timedelta(hours=1)
  122. expires = datetime.datetime(2015, 3, 20, 14, 0, 0) + duration
  123. Ticket.objects.create(active_at=active, duration=duration)
  124. t = Ticket.objects.annotate(
  125. expires=ExpressionWrapper(F('active_at') + F('duration'), output_field=DateTimeField())
  126. ).first()
  127. self.assertEqual(t.expires, expires)
  128. def test_mixed_type_annotation_numbers(self):
  129. test = self.b1
  130. b = Book.objects.annotate(
  131. combined=ExpressionWrapper(F('pages') + F('rating'), output_field=IntegerField())
  132. ).get(isbn=test.isbn)
  133. combined = int(test.pages + test.rating)
  134. self.assertEqual(b.combined, combined)
  135. def test_empty_expression_annotation(self):
  136. books = Book.objects.annotate(
  137. selected=ExpressionWrapper(Q(pk__in=[]), output_field=BooleanField())
  138. )
  139. self.assertEqual(len(books), Book.objects.count())
  140. self.assertTrue(all(not book.selected for book in books))
  141. books = Book.objects.annotate(
  142. selected=ExpressionWrapper(Q(pk__in=Book.objects.none()), output_field=BooleanField())
  143. )
  144. self.assertEqual(len(books), Book.objects.count())
  145. self.assertTrue(all(not book.selected for book in books))
  146. def test_annotate_with_aggregation(self):
  147. books = Book.objects.annotate(is_book=Value(1), rating_count=Count('rating'))
  148. for book in books:
  149. self.assertEqual(book.is_book, 1)
  150. self.assertEqual(book.rating_count, 1)
  151. def test_combined_expression_annotation_with_aggregation(self):
  152. book = Book.objects.annotate(
  153. combined=ExpressionWrapper(Value(3) * Value(4), output_field=IntegerField()),
  154. rating_count=Count('rating'),
  155. ).first()
  156. self.assertEqual(book.combined, 12)
  157. self.assertEqual(book.rating_count, 1)
  158. def test_combined_f_expression_annotation_with_aggregation(self):
  159. book = Book.objects.filter(isbn='159059725').annotate(
  160. combined=ExpressionWrapper(F('price') * F('pages'), output_field=FloatField()),
  161. rating_count=Count('rating'),
  162. ).first()
  163. self.assertEqual(book.combined, 13410.0)
  164. self.assertEqual(book.rating_count, 1)
  165. def test_aggregate_over_annotation(self):
  166. agg = Author.objects.annotate(other_age=F('age')).aggregate(otherage_sum=Sum('other_age'))
  167. other_agg = Author.objects.aggregate(age_sum=Sum('age'))
  168. self.assertEqual(agg['otherage_sum'], other_agg['age_sum'])
  169. @skipUnlessDBFeature('can_distinct_on_fields')
  170. def test_distinct_on_with_annotation(self):
  171. store = Store.objects.create(
  172. name='test store',
  173. original_opening=datetime.datetime.now(),
  174. friday_night_closing=datetime.time(21, 00, 00),
  175. )
  176. names = [
  177. 'Theodore Roosevelt',
  178. 'Eleanor Roosevelt',
  179. 'Franklin Roosevelt',
  180. 'Ned Stark',
  181. 'Catelyn Stark',
  182. ]
  183. for name in names:
  184. Employee.objects.create(
  185. store=store,
  186. first_name=name.split()[0],
  187. last_name=name.split()[1],
  188. age=30, salary=2000,
  189. )
  190. people = Employee.objects.annotate(
  191. name_lower=Lower('last_name'),
  192. ).distinct('name_lower')
  193. self.assertEqual({p.last_name for p in people}, {'Stark', 'Roosevelt'})
  194. self.assertEqual(len(people), 2)
  195. people2 = Employee.objects.annotate(
  196. test_alias=F('store__name'),
  197. ).distinct('test_alias')
  198. self.assertEqual(len(people2), 1)
  199. lengths = Employee.objects.annotate(
  200. name_len=Length('first_name'),
  201. ).distinct('name_len').values_list('name_len', flat=True)
  202. self.assertCountEqual(lengths, [3, 7, 8])
  203. def test_filter_annotation(self):
  204. books = Book.objects.annotate(is_book=Value(1)).filter(is_book=1)
  205. for book in books:
  206. self.assertEqual(book.is_book, 1)
  207. def test_filter_annotation_with_f(self):
  208. books = Book.objects.annotate(
  209. other_rating=F('rating')
  210. ).filter(other_rating=3.5)
  211. for book in books:
  212. self.assertEqual(book.other_rating, 3.5)
  213. def test_filter_annotation_with_double_f(self):
  214. books = Book.objects.annotate(
  215. other_rating=F('rating')
  216. ).filter(other_rating=F('rating'))
  217. for book in books:
  218. self.assertEqual(book.other_rating, book.rating)
  219. def test_filter_agg_with_double_f(self):
  220. books = Book.objects.annotate(
  221. sum_rating=Sum('rating')
  222. ).filter(sum_rating=F('sum_rating'))
  223. for book in books:
  224. self.assertEqual(book.sum_rating, book.rating)
  225. def test_filter_wrong_annotation(self):
  226. with self.assertRaisesMessage(FieldError, "Cannot resolve keyword 'nope' into field."):
  227. list(Book.objects.annotate(
  228. sum_rating=Sum('rating')
  229. ).filter(sum_rating=F('nope')))
  230. def test_decimal_annotation(self):
  231. salary = Decimal(10) ** -Employee._meta.get_field('salary').decimal_places
  232. Employee.objects.create(
  233. first_name='Max',
  234. last_name='Paine',
  235. store=Store.objects.first(),
  236. age=23,
  237. salary=salary,
  238. )
  239. self.assertEqual(
  240. Employee.objects.annotate(new_salary=F('salary') / 10).get().new_salary,
  241. salary / 10,
  242. )
  243. def test_filter_decimal_annotation(self):
  244. qs = Book.objects.annotate(new_price=F('price') + 1).filter(new_price=Decimal(31)).values_list('new_price')
  245. self.assertEqual(qs.get(), (Decimal(31),))
  246. def test_combined_annotation_commutative(self):
  247. book1 = Book.objects.annotate(adjusted_rating=F('rating') + 2).get(pk=self.b1.pk)
  248. book2 = Book.objects.annotate(adjusted_rating=2 + F('rating')).get(pk=self.b1.pk)
  249. self.assertEqual(book1.adjusted_rating, book2.adjusted_rating)
  250. book1 = Book.objects.annotate(adjusted_rating=F('rating') + None).get(pk=self.b1.pk)
  251. book2 = Book.objects.annotate(adjusted_rating=None + F('rating')).get(pk=self.b1.pk)
  252. self.assertEqual(book1.adjusted_rating, book2.adjusted_rating)
  253. def test_update_with_annotation(self):
  254. book_preupdate = Book.objects.get(pk=self.b2.pk)
  255. Book.objects.annotate(other_rating=F('rating') - 1).update(rating=F('other_rating'))
  256. book_postupdate = Book.objects.get(pk=self.b2.pk)
  257. self.assertEqual(book_preupdate.rating - 1, book_postupdate.rating)
  258. def test_annotation_with_m2m(self):
  259. books = Book.objects.annotate(author_age=F('authors__age')).filter(pk=self.b1.pk).order_by('author_age')
  260. self.assertEqual(books[0].author_age, 34)
  261. self.assertEqual(books[1].author_age, 35)
  262. def test_annotation_reverse_m2m(self):
  263. books = Book.objects.annotate(
  264. store_name=F('store__name'),
  265. ).filter(
  266. name='Practical Django Projects',
  267. ).order_by('store_name')
  268. self.assertQuerysetEqual(
  269. books, [
  270. 'Amazon.com',
  271. 'Books.com',
  272. 'Mamma and Pappa\'s Books'
  273. ],
  274. lambda b: b.store_name
  275. )
  276. def test_values_annotation(self):
  277. """
  278. Annotations can reference fields in a values clause,
  279. and contribute to an existing values clause.
  280. """
  281. # annotate references a field in values()
  282. qs = Book.objects.values('rating').annotate(other_rating=F('rating') - 1)
  283. book = qs.get(pk=self.b1.pk)
  284. self.assertEqual(book['rating'] - 1, book['other_rating'])
  285. # filter refs the annotated value
  286. book = qs.get(other_rating=4)
  287. self.assertEqual(book['other_rating'], 4)
  288. # can annotate an existing values with a new field
  289. book = qs.annotate(other_isbn=F('isbn')).get(other_rating=4)
  290. self.assertEqual(book['other_rating'], 4)
  291. self.assertEqual(book['other_isbn'], '155860191')
  292. def test_values_with_pk_annotation(self):
  293. # annotate references a field in values() with pk
  294. publishers = Publisher.objects.values('id', 'book__rating').annotate(total=Sum('book__rating'))
  295. for publisher in publishers.filter(pk=self.p1.pk):
  296. self.assertEqual(publisher['book__rating'], publisher['total'])
  297. @skipUnlessDBFeature('allows_group_by_pk')
  298. def test_rawsql_group_by_collapse(self):
  299. raw = RawSQL('SELECT MIN(id) FROM annotations_book', [])
  300. qs = Author.objects.values('id').annotate(
  301. min_book_id=raw,
  302. count_friends=Count('friends'),
  303. ).order_by()
  304. _, _, group_by = qs.query.get_compiler(using='default').pre_sql_setup()
  305. self.assertEqual(len(group_by), 1)
  306. self.assertNotEqual(raw, group_by[0])
  307. def test_defer_annotation(self):
  308. """
  309. Deferred attributes can be referenced by an annotation,
  310. but they are not themselves deferred, and cannot be deferred.
  311. """
  312. qs = Book.objects.defer('rating').annotate(other_rating=F('rating') - 1)
  313. with self.assertNumQueries(2):
  314. book = qs.get(other_rating=4)
  315. self.assertEqual(book.rating, 5)
  316. self.assertEqual(book.other_rating, 4)
  317. with self.assertRaisesMessage(FieldDoesNotExist, "Book has no field named 'other_rating'"):
  318. book = qs.defer('other_rating').get(other_rating=4)
  319. def test_mti_annotations(self):
  320. """
  321. Fields on an inherited model can be referenced by an
  322. annotated field.
  323. """
  324. d = DepartmentStore.objects.create(
  325. name='Angus & Robinson',
  326. original_opening=datetime.date(2014, 3, 8),
  327. friday_night_closing=datetime.time(21, 00, 00),
  328. chain='Westfield'
  329. )
  330. books = Book.objects.filter(rating__gt=4)
  331. for b in books:
  332. d.books.add(b)
  333. qs = DepartmentStore.objects.annotate(
  334. other_name=F('name'),
  335. other_chain=F('chain'),
  336. is_open=Value(True, BooleanField()),
  337. book_isbn=F('books__isbn')
  338. ).order_by('book_isbn').filter(chain='Westfield')
  339. self.assertQuerysetEqual(
  340. qs, [
  341. ('Angus & Robinson', 'Westfield', True, '155860191'),
  342. ('Angus & Robinson', 'Westfield', True, '159059725')
  343. ],
  344. lambda d: (d.other_name, d.other_chain, d.is_open, d.book_isbn)
  345. )
  346. def test_null_annotation(self):
  347. """
  348. Annotating None onto a model round-trips
  349. """
  350. book = Book.objects.annotate(no_value=Value(None, output_field=IntegerField())).first()
  351. self.assertIsNone(book.no_value)
  352. def test_order_by_annotation(self):
  353. authors = Author.objects.annotate(other_age=F('age')).order_by('other_age')
  354. self.assertQuerysetEqual(
  355. authors, [
  356. 25, 29, 29, 34, 35, 37, 45, 46, 57,
  357. ],
  358. lambda a: a.other_age
  359. )
  360. def test_order_by_aggregate(self):
  361. authors = Author.objects.values('age').annotate(age_count=Count('age')).order_by('age_count', 'age')
  362. self.assertQuerysetEqual(
  363. authors, [
  364. (25, 1), (34, 1), (35, 1), (37, 1), (45, 1), (46, 1), (57, 1), (29, 2),
  365. ],
  366. lambda a: (a['age'], a['age_count'])
  367. )
  368. def test_raw_sql_with_inherited_field(self):
  369. DepartmentStore.objects.create(
  370. name='Angus & Robinson',
  371. original_opening=datetime.date(2014, 3, 8),
  372. friday_night_closing=datetime.time(21),
  373. chain='Westfield',
  374. area=123,
  375. )
  376. tests = (
  377. ('name', 'Angus & Robinson'),
  378. ('surface', 123),
  379. ("case when name='Angus & Robinson' then chain else name end", 'Westfield'),
  380. )
  381. for sql, expected_result in tests:
  382. with self.subTest(sql=sql):
  383. self.assertSequenceEqual(
  384. DepartmentStore.objects.annotate(
  385. annotation=RawSQL(sql, ()),
  386. ).values_list('annotation', flat=True),
  387. [expected_result],
  388. )
  389. def test_annotate_exists(self):
  390. authors = Author.objects.annotate(c=Count('id')).filter(c__gt=1)
  391. self.assertFalse(authors.exists())
  392. def test_column_field_ordering(self):
  393. """
  394. Columns are aligned in the correct order for resolve_columns. This test
  395. will fail on MySQL if column ordering is out. Column fields should be
  396. aligned as:
  397. 1. extra_select
  398. 2. model_fields
  399. 3. annotation_fields
  400. 4. model_related_fields
  401. """
  402. store = Store.objects.first()
  403. Employee.objects.create(id=1, first_name='Max', manager=True, last_name='Paine',
  404. store=store, age=23, salary=Decimal(50000.00))
  405. Employee.objects.create(id=2, first_name='Buffy', manager=False, last_name='Summers',
  406. store=store, age=18, salary=Decimal(40000.00))
  407. qs = Employee.objects.extra(
  408. select={'random_value': '42'}
  409. ).select_related('store').annotate(
  410. annotated_value=Value(17),
  411. )
  412. rows = [
  413. (1, 'Max', True, 42, 'Paine', 23, Decimal(50000.00), store.name, 17),
  414. (2, 'Buffy', False, 42, 'Summers', 18, Decimal(40000.00), store.name, 17)
  415. ]
  416. self.assertQuerysetEqual(
  417. qs.order_by('id'), rows,
  418. lambda e: (
  419. e.id, e.first_name, e.manager, e.random_value, e.last_name, e.age,
  420. e.salary, e.store.name, e.annotated_value))
  421. def test_column_field_ordering_with_deferred(self):
  422. store = Store.objects.first()
  423. Employee.objects.create(id=1, first_name='Max', manager=True, last_name='Paine',
  424. store=store, age=23, salary=Decimal(50000.00))
  425. Employee.objects.create(id=2, first_name='Buffy', manager=False, last_name='Summers',
  426. store=store, age=18, salary=Decimal(40000.00))
  427. qs = Employee.objects.extra(
  428. select={'random_value': '42'}
  429. ).select_related('store').annotate(
  430. annotated_value=Value(17),
  431. )
  432. rows = [
  433. (1, 'Max', True, 42, 'Paine', 23, Decimal(50000.00), store.name, 17),
  434. (2, 'Buffy', False, 42, 'Summers', 18, Decimal(40000.00), store.name, 17)
  435. ]
  436. # and we respect deferred columns!
  437. self.assertQuerysetEqual(
  438. qs.defer('age').order_by('id'), rows,
  439. lambda e: (
  440. e.id, e.first_name, e.manager, e.random_value, e.last_name, e.age,
  441. e.salary, e.store.name, e.annotated_value))
  442. @cxOracle_py3_bug
  443. def test_custom_functions(self):
  444. Company(name='Apple', motto=None, ticker_name='APPL', description='Beautiful Devices').save()
  445. Company(name='Django Software Foundation', motto=None, ticker_name=None, description=None).save()
  446. Company(name='Google', motto='Do No Evil', ticker_name='GOOG', description='Internet Company').save()
  447. Company(name='Yahoo', motto=None, ticker_name=None, description='Internet Company').save()
  448. qs = Company.objects.annotate(
  449. tagline=Func(
  450. F('motto'),
  451. F('ticker_name'),
  452. F('description'),
  453. Value('No Tag'),
  454. function='COALESCE'
  455. )
  456. ).order_by('name')
  457. self.assertQuerysetEqual(
  458. qs, [
  459. ('Apple', 'APPL'),
  460. ('Django Software Foundation', 'No Tag'),
  461. ('Google', 'Do No Evil'),
  462. ('Yahoo', 'Internet Company')
  463. ],
  464. lambda c: (c.name, c.tagline)
  465. )
  466. @cxOracle_py3_bug
  467. def test_custom_functions_can_ref_other_functions(self):
  468. Company(name='Apple', motto=None, ticker_name='APPL', description='Beautiful Devices').save()
  469. Company(name='Django Software Foundation', motto=None, ticker_name=None, description=None).save()
  470. Company(name='Google', motto='Do No Evil', ticker_name='GOOG', description='Internet Company').save()
  471. Company(name='Yahoo', motto=None, ticker_name=None, description='Internet Company').save()
  472. class Lower(Func):
  473. function = 'LOWER'
  474. qs = Company.objects.annotate(
  475. tagline=Func(
  476. F('motto'),
  477. F('ticker_name'),
  478. F('description'),
  479. Value('No Tag'),
  480. function='COALESCE',
  481. )
  482. ).annotate(
  483. tagline_lower=Lower(F('tagline')),
  484. ).order_by('name')
  485. # LOWER function supported by:
  486. # oracle, postgres, mysql, sqlite, sqlserver
  487. self.assertQuerysetEqual(
  488. qs, [
  489. ('Apple', 'APPL'.lower()),
  490. ('Django Software Foundation', 'No Tag'.lower()),
  491. ('Google', 'Do No Evil'.lower()),
  492. ('Yahoo', 'Internet Company'.lower())
  493. ],
  494. lambda c: (c.name, c.tagline_lower)
  495. )
  496. def test_boolean_value_annotation(self):
  497. books = Book.objects.annotate(
  498. is_book=Value(True, output_field=BooleanField()),
  499. is_pony=Value(False, output_field=BooleanField()),
  500. is_none=Value(None, output_field=BooleanField(null=True)),
  501. is_none_old=Value(None, output_field=NullBooleanField()),
  502. )
  503. self.assertGreater(len(books), 0)
  504. for book in books:
  505. self.assertIs(book.is_book, True)
  506. self.assertIs(book.is_pony, False)
  507. self.assertIsNone(book.is_none)
  508. self.assertIsNone(book.is_none_old)
  509. def test_annotation_in_f_grouped_by_annotation(self):
  510. qs = (
  511. Publisher.objects.annotate(multiplier=Value(3))
  512. # group by option => sum of value * multiplier
  513. .values('name')
  514. .annotate(multiplied_value_sum=Sum(F('multiplier') * F('num_awards')))
  515. .order_by()
  516. )
  517. self.assertCountEqual(
  518. qs, [
  519. {'multiplied_value_sum': 9, 'name': 'Apress'},
  520. {'multiplied_value_sum': 0, 'name': "Jonno's House of Books"},
  521. {'multiplied_value_sum': 27, 'name': 'Morgan Kaufmann'},
  522. {'multiplied_value_sum': 21, 'name': 'Prentice Hall'},
  523. {'multiplied_value_sum': 3, 'name': 'Sams'},
  524. ]
  525. )
  526. def test_arguments_must_be_expressions(self):
  527. msg = 'QuerySet.annotate() received non-expression(s): %s.'
  528. with self.assertRaisesMessage(TypeError, msg % BooleanField()):
  529. Book.objects.annotate(BooleanField())
  530. with self.assertRaisesMessage(TypeError, msg % True):
  531. Book.objects.annotate(is_book=True)
  532. with self.assertRaisesMessage(TypeError, msg % ', '.join([str(BooleanField()), 'True'])):
  533. Book.objects.annotate(BooleanField(), Value(False), is_book=True)
  534. def test_chaining_annotation_filter_with_m2m(self):
  535. qs = Author.objects.filter(
  536. name='Adrian Holovaty',
  537. friends__age=35,
  538. ).annotate(
  539. jacob_name=F('friends__name'),
  540. ).filter(
  541. friends__age=29,
  542. ).annotate(
  543. james_name=F('friends__name'),
  544. ).values('jacob_name', 'james_name')
  545. self.assertCountEqual(
  546. qs,
  547. [{'jacob_name': 'Jacob Kaplan-Moss', 'james_name': 'James Bennett'}],
  548. )
  549. def test_annotation_filter_with_subquery(self):
  550. long_books_qs = Book.objects.filter(
  551. publisher=OuterRef('pk'),
  552. pages__gt=400,
  553. ).values('publisher').annotate(count=Count('pk')).values('count')
  554. publisher_books_qs = Publisher.objects.annotate(
  555. total_books=Count('book'),
  556. ).filter(
  557. total_books=Subquery(long_books_qs, output_field=IntegerField()),
  558. ).values('name')
  559. self.assertCountEqual(publisher_books_qs, [{'name': 'Sams'}, {'name': 'Morgan Kaufmann'}])
  560. def test_annotation_exists_aggregate_values_chaining(self):
  561. qs = Book.objects.values('publisher').annotate(
  562. has_authors=Exists(Book.authors.through.objects.filter(book=OuterRef('pk'))),
  563. max_pubdate=Max('pubdate'),
  564. ).values_list('max_pubdate', flat=True).order_by('max_pubdate')
  565. self.assertCountEqual(qs, [
  566. datetime.date(1991, 10, 15),
  567. datetime.date(2008, 3, 3),
  568. datetime.date(2008, 6, 23),
  569. datetime.date(2008, 11, 3),
  570. ])
  571. @skipIf(
  572. connection.vendor == 'mysql' and 'ONLY_FULL_GROUP_BY' in connection.sql_mode,
  573. 'GROUP BY optimization does not work properly when ONLY_FULL_GROUP_BY '
  574. 'mode is enabled on MySQL, see #31331.',
  575. )
  576. def test_annotation_aggregate_with_m2o(self):
  577. qs = Author.objects.filter(age__lt=30).annotate(
  578. max_pages=Case(
  579. When(book_contact_set__isnull=True, then=Value(0)),
  580. default=Max(F('book__pages')),
  581. ),
  582. ).values('name', 'max_pages')
  583. self.assertCountEqual(qs, [
  584. {'name': 'James Bennett', 'max_pages': 300},
  585. {'name': 'Paul Bissex', 'max_pages': 0},
  586. {'name': 'Wesley J. Chun', 'max_pages': 0},
  587. ])