tests.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. from __future__ import unicode_literals
  2. from datetime import datetime, timedelta
  3. from unittest import skipIf, skipUnless
  4. from django.db import connection
  5. from django.db.models import CharField, TextField, Value as V
  6. from django.db.models.expressions import RawSQL
  7. from django.db.models.functions import (
  8. Coalesce, Concat, Greatest, Least, Length, Lower, Now, Substr, Upper,
  9. )
  10. from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
  11. from django.utils import six, timezone
  12. from .models import Article, Author, Fan
  13. lorem_ipsum = """
  14. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
  15. tempor incididunt ut labore et dolore magna aliqua."""
  16. def truncate_microseconds(value):
  17. return value if connection.features.supports_microsecond_precision else value.replace(microsecond=0)
  18. class FunctionTests(TestCase):
  19. def test_coalesce(self):
  20. Author.objects.create(name='John Smith', alias='smithj')
  21. Author.objects.create(name='Rhonda')
  22. authors = Author.objects.annotate(display_name=Coalesce('alias', 'name'))
  23. self.assertQuerysetEqual(
  24. authors.order_by('name'), [
  25. 'smithj',
  26. 'Rhonda',
  27. ],
  28. lambda a: a.display_name
  29. )
  30. with self.assertRaisesMessage(ValueError, 'Coalesce must take at least two expressions'):
  31. Author.objects.annotate(display_name=Coalesce('alias'))
  32. def test_coalesce_mixed_values(self):
  33. a1 = Author.objects.create(name='John Smith', alias='smithj')
  34. a2 = Author.objects.create(name='Rhonda')
  35. ar1 = Article.objects.create(
  36. title="How to Django",
  37. text=lorem_ipsum,
  38. written=timezone.now(),
  39. )
  40. ar1.authors.add(a1)
  41. ar1.authors.add(a2)
  42. # mixed Text and Char
  43. article = Article.objects.annotate(
  44. headline=Coalesce('summary', 'text', output_field=TextField()),
  45. )
  46. self.assertQuerysetEqual(
  47. article.order_by('title'), [
  48. lorem_ipsum,
  49. ],
  50. lambda a: a.headline
  51. )
  52. # mixed Text and Char wrapped
  53. article = Article.objects.annotate(
  54. headline=Coalesce(Lower('summary'), Lower('text'), output_field=TextField()),
  55. )
  56. self.assertQuerysetEqual(
  57. article.order_by('title'), [
  58. lorem_ipsum.lower(),
  59. ],
  60. lambda a: a.headline
  61. )
  62. def test_coalesce_ordering(self):
  63. Author.objects.create(name='John Smith', alias='smithj')
  64. Author.objects.create(name='Rhonda')
  65. authors = Author.objects.order_by(Coalesce('alias', 'name'))
  66. self.assertQuerysetEqual(
  67. authors, [
  68. 'Rhonda',
  69. 'John Smith',
  70. ],
  71. lambda a: a.name
  72. )
  73. authors = Author.objects.order_by(Coalesce('alias', 'name').asc())
  74. self.assertQuerysetEqual(
  75. authors, [
  76. 'Rhonda',
  77. 'John Smith',
  78. ],
  79. lambda a: a.name
  80. )
  81. authors = Author.objects.order_by(Coalesce('alias', 'name').desc())
  82. self.assertQuerysetEqual(
  83. authors, [
  84. 'John Smith',
  85. 'Rhonda',
  86. ],
  87. lambda a: a.name
  88. )
  89. def test_greatest(self):
  90. now = timezone.now()
  91. before = now - timedelta(hours=1)
  92. Article.objects.create(
  93. title="Testing with Django",
  94. written=before,
  95. published=now,
  96. )
  97. articles = Article.objects.annotate(
  98. last_updated=Greatest('written', 'published'),
  99. )
  100. self.assertEqual(articles.first().last_updated, truncate_microseconds(now))
  101. @skipUnlessDBFeature('greatest_least_ignores_nulls')
  102. def test_greatest_ignores_null(self):
  103. now = timezone.now()
  104. Article.objects.create(title="Testing with Django", written=now)
  105. articles = Article.objects.annotate(
  106. last_updated=Greatest('written', 'published'),
  107. )
  108. self.assertEqual(articles.first().last_updated, now)
  109. @skipIfDBFeature('greatest_least_ignores_nulls')
  110. def test_greatest_propogates_null(self):
  111. now = timezone.now()
  112. Article.objects.create(title="Testing with Django", written=now)
  113. articles = Article.objects.annotate(
  114. last_updated=Greatest('written', 'published'),
  115. )
  116. self.assertIsNone(articles.first().last_updated)
  117. @skipIf(connection.vendor == 'mysql', "This doesn't work on MySQL")
  118. def test_greatest_coalesce_workaround(self):
  119. past = datetime(1900, 1, 1)
  120. now = timezone.now()
  121. Article.objects.create(title="Testing with Django", written=now)
  122. articles = Article.objects.annotate(
  123. last_updated=Greatest(
  124. Coalesce('written', past),
  125. Coalesce('published', past),
  126. ),
  127. )
  128. self.assertEqual(articles.first().last_updated, now)
  129. @skipUnless(connection.vendor == 'mysql', "MySQL-specific workaround")
  130. def test_greatest_coalesce_workaround_mysql(self):
  131. past = datetime(1900, 1, 1)
  132. now = timezone.now()
  133. Article.objects.create(title="Testing with Django", written=now)
  134. past_sql = RawSQL("cast(%s as datetime)", (past,))
  135. articles = Article.objects.annotate(
  136. last_updated=Greatest(
  137. Coalesce('written', past_sql),
  138. Coalesce('published', past_sql),
  139. ),
  140. )
  141. self.assertEqual(articles.first().last_updated, truncate_microseconds(now))
  142. def test_greatest_all_null(self):
  143. Article.objects.create(title="Testing with Django", written=timezone.now())
  144. articles = Article.objects.annotate(last_updated=Greatest('published', 'updated'))
  145. self.assertIsNone(articles.first().last_updated)
  146. def test_greatest_one_expressions(self):
  147. with self.assertRaisesMessage(ValueError, 'Greatest must take at least two expressions'):
  148. Greatest('written')
  149. def test_greatest_related_field(self):
  150. author = Author.objects.create(name='John Smith', age=45)
  151. Fan.objects.create(name='Margaret', age=50, author=author)
  152. authors = Author.objects.annotate(
  153. highest_age=Greatest('age', 'fans__age'),
  154. )
  155. self.assertEqual(authors.first().highest_age, 50)
  156. def test_greatest_update(self):
  157. author = Author.objects.create(name='James Smith', goes_by='Jim')
  158. Author.objects.update(alias=Greatest('name', 'goes_by'))
  159. author.refresh_from_db()
  160. self.assertEqual(author.alias, 'Jim')
  161. def test_least(self):
  162. now = timezone.now()
  163. before = now - timedelta(hours=1)
  164. Article.objects.create(
  165. title="Testing with Django",
  166. written=before,
  167. published=now,
  168. )
  169. articles = Article.objects.annotate(
  170. first_updated=Least('written', 'published'),
  171. )
  172. self.assertEqual(articles.first().first_updated, truncate_microseconds(before))
  173. @skipUnlessDBFeature('greatest_least_ignores_nulls')
  174. def test_least_ignores_null(self):
  175. now = timezone.now()
  176. Article.objects.create(title="Testing with Django", written=now)
  177. articles = Article.objects.annotate(
  178. first_updated=Least('written', 'published'),
  179. )
  180. self.assertEqual(articles.first().first_updated, now)
  181. @skipIfDBFeature('greatest_least_ignores_nulls')
  182. def test_least_propogates_null(self):
  183. now = timezone.now()
  184. Article.objects.create(title="Testing with Django", written=now)
  185. articles = Article.objects.annotate(
  186. first_updated=Least('written', 'published'),
  187. )
  188. self.assertIsNone(articles.first().first_updated)
  189. @skipIf(connection.vendor == 'mysql', "This doesn't work on MySQL")
  190. def test_least_coalesce_workaround(self):
  191. future = datetime(2100, 1, 1)
  192. now = timezone.now()
  193. Article.objects.create(title="Testing with Django", written=now)
  194. articles = Article.objects.annotate(
  195. last_updated=Least(
  196. Coalesce('written', future),
  197. Coalesce('published', future),
  198. ),
  199. )
  200. self.assertEqual(articles.first().last_updated, now)
  201. @skipUnless(connection.vendor == 'mysql', "MySQL-specific workaround")
  202. def test_least_coalesce_workaround_mysql(self):
  203. future = datetime(2100, 1, 1)
  204. now = timezone.now()
  205. Article.objects.create(title="Testing with Django", written=now)
  206. future_sql = RawSQL("cast(%s as datetime)", (future,))
  207. articles = Article.objects.annotate(
  208. last_updated=Least(
  209. Coalesce('written', future_sql),
  210. Coalesce('published', future_sql),
  211. ),
  212. )
  213. self.assertEqual(articles.first().last_updated, truncate_microseconds(now))
  214. def test_least_all_null(self):
  215. Article.objects.create(title="Testing with Django", written=timezone.now())
  216. articles = Article.objects.annotate(first_updated=Least('published', 'updated'))
  217. self.assertIsNone(articles.first().first_updated)
  218. def test_least_one_expressions(self):
  219. with self.assertRaisesMessage(ValueError, 'Least must take at least two expressions'):
  220. Least('written')
  221. def test_least_related_field(self):
  222. author = Author.objects.create(name='John Smith', age=45)
  223. Fan.objects.create(name='Margaret', age=50, author=author)
  224. authors = Author.objects.annotate(
  225. lowest_age=Least('age', 'fans__age'),
  226. )
  227. self.assertEqual(authors.first().lowest_age, 45)
  228. def test_least_update(self):
  229. author = Author.objects.create(name='James Smith', goes_by='Jim')
  230. Author.objects.update(alias=Least('name', 'goes_by'))
  231. author.refresh_from_db()
  232. self.assertEqual(author.alias, 'James Smith')
  233. def test_concat(self):
  234. Author.objects.create(name='Jayden')
  235. Author.objects.create(name='John Smith', alias='smithj', goes_by='John')
  236. Author.objects.create(name='Margaret', goes_by='Maggie')
  237. Author.objects.create(name='Rhonda', alias='adnohR')
  238. authors = Author.objects.annotate(joined=Concat('alias', 'goes_by'))
  239. self.assertQuerysetEqual(
  240. authors.order_by('name'), [
  241. '',
  242. 'smithjJohn',
  243. 'Maggie',
  244. 'adnohR',
  245. ],
  246. lambda a: a.joined
  247. )
  248. with self.assertRaisesMessage(ValueError, 'Concat must take at least two expressions'):
  249. Author.objects.annotate(joined=Concat('alias'))
  250. def test_concat_many(self):
  251. Author.objects.create(name='Jayden')
  252. Author.objects.create(name='John Smith', alias='smithj', goes_by='John')
  253. Author.objects.create(name='Margaret', goes_by='Maggie')
  254. Author.objects.create(name='Rhonda', alias='adnohR')
  255. authors = Author.objects.annotate(
  256. joined=Concat('name', V(' ('), 'goes_by', V(')'), output_field=CharField()),
  257. )
  258. self.assertQuerysetEqual(
  259. authors.order_by('name'), [
  260. 'Jayden ()',
  261. 'John Smith (John)',
  262. 'Margaret (Maggie)',
  263. 'Rhonda ()',
  264. ],
  265. lambda a: a.joined
  266. )
  267. def test_concat_mixed_char_text(self):
  268. Article.objects.create(title='The Title', text=lorem_ipsum, written=timezone.now())
  269. article = Article.objects.annotate(
  270. title_text=Concat('title', V(' - '), 'text', output_field=TextField()),
  271. ).get(title='The Title')
  272. self.assertEqual(article.title + ' - ' + article.text, article.title_text)
  273. # wrap the concat in something else to ensure that we're still
  274. # getting text rather than bytes
  275. article = Article.objects.annotate(
  276. title_text=Upper(Concat('title', V(' - '), 'text', output_field=TextField())),
  277. ).get(title='The Title')
  278. expected = article.title + ' - ' + article.text
  279. self.assertEqual(expected.upper(), article.title_text)
  280. def test_lower(self):
  281. Author.objects.create(name='John Smith', alias='smithj')
  282. Author.objects.create(name='Rhonda')
  283. authors = Author.objects.annotate(lower_name=Lower('name'))
  284. self.assertQuerysetEqual(
  285. authors.order_by('name'), [
  286. 'john smith',
  287. 'rhonda',
  288. ],
  289. lambda a: a.lower_name
  290. )
  291. Author.objects.update(name=Lower('name'))
  292. self.assertQuerysetEqual(
  293. authors.order_by('name'), [
  294. ('john smith', 'john smith'),
  295. ('rhonda', 'rhonda'),
  296. ],
  297. lambda a: (a.lower_name, a.name)
  298. )
  299. def test_upper(self):
  300. Author.objects.create(name='John Smith', alias='smithj')
  301. Author.objects.create(name='Rhonda')
  302. authors = Author.objects.annotate(upper_name=Upper('name'))
  303. self.assertQuerysetEqual(
  304. authors.order_by('name'), [
  305. 'JOHN SMITH',
  306. 'RHONDA',
  307. ],
  308. lambda a: a.upper_name
  309. )
  310. Author.objects.update(name=Upper('name'))
  311. self.assertQuerysetEqual(
  312. authors.order_by('name'), [
  313. ('JOHN SMITH', 'JOHN SMITH'),
  314. ('RHONDA', 'RHONDA'),
  315. ],
  316. lambda a: (a.upper_name, a.name)
  317. )
  318. def test_length(self):
  319. Author.objects.create(name='John Smith', alias='smithj')
  320. Author.objects.create(name='Rhonda')
  321. authors = Author.objects.annotate(
  322. name_length=Length('name'),
  323. alias_length=Length('alias'))
  324. self.assertQuerysetEqual(
  325. authors.order_by('name'), [
  326. (10, 6),
  327. (6, None),
  328. ],
  329. lambda a: (a.name_length, a.alias_length)
  330. )
  331. self.assertEqual(authors.filter(alias_length__lte=Length('name')).count(), 1)
  332. def test_length_ordering(self):
  333. Author.objects.create(name='John Smith', alias='smithj')
  334. Author.objects.create(name='John Smith', alias='smithj1')
  335. Author.objects.create(name='Rhonda', alias='ronny')
  336. authors = Author.objects.order_by(Length('name'), Length('alias'))
  337. self.assertQuerysetEqual(
  338. authors, [
  339. ('Rhonda', 'ronny'),
  340. ('John Smith', 'smithj'),
  341. ('John Smith', 'smithj1'),
  342. ],
  343. lambda a: (a.name, a.alias)
  344. )
  345. def test_substr(self):
  346. Author.objects.create(name='John Smith', alias='smithj')
  347. Author.objects.create(name='Rhonda')
  348. authors = Author.objects.annotate(name_part=Substr('name', 5, 3))
  349. self.assertQuerysetEqual(
  350. authors.order_by('name'), [
  351. ' Sm',
  352. 'da',
  353. ],
  354. lambda a: a.name_part
  355. )
  356. authors = Author.objects.annotate(name_part=Substr('name', 2))
  357. self.assertQuerysetEqual(
  358. authors.order_by('name'), [
  359. 'ohn Smith',
  360. 'honda',
  361. ],
  362. lambda a: a.name_part
  363. )
  364. # if alias is null, set to first 5 lower characters of the name
  365. Author.objects.filter(alias__isnull=True).update(
  366. alias=Lower(Substr('name', 1, 5)),
  367. )
  368. self.assertQuerysetEqual(
  369. authors.order_by('name'), [
  370. 'smithj',
  371. 'rhond',
  372. ],
  373. lambda a: a.alias
  374. )
  375. def test_substr_start(self):
  376. Author.objects.create(name='John Smith', alias='smithj')
  377. a = Author.objects.annotate(
  378. name_part_1=Substr('name', 1),
  379. name_part_2=Substr('name', 2),
  380. ).get(alias='smithj')
  381. self.assertEqual(a.name_part_1[1:], a.name_part_2)
  382. with six.assertRaisesRegex(self, ValueError, "'pos' must be greater than 0"):
  383. Author.objects.annotate(raises=Substr('name', 0))
  384. def test_substr_with_expressions(self):
  385. Author.objects.create(name='John Smith', alias='smithj')
  386. Author.objects.create(name='Rhonda')
  387. authors = Author.objects.annotate(name_part=Substr('name', 5, 3))
  388. self.assertQuerysetEqual(
  389. authors.order_by('name'), [
  390. ' Sm',
  391. 'da',
  392. ],
  393. lambda a: a.name_part
  394. )
  395. def test_nested_function_ordering(self):
  396. Author.objects.create(name='John Smith')
  397. Author.objects.create(name='Rhonda Simpson', alias='ronny')
  398. authors = Author.objects.order_by(Length(Coalesce('alias', 'name')))
  399. self.assertQuerysetEqual(
  400. authors, [
  401. 'Rhonda Simpson',
  402. 'John Smith',
  403. ],
  404. lambda a: a.name
  405. )
  406. authors = Author.objects.order_by(Length(Coalesce('alias', 'name')).desc())
  407. self.assertQuerysetEqual(
  408. authors, [
  409. 'John Smith',
  410. 'Rhonda Simpson',
  411. ],
  412. lambda a: a.name
  413. )
  414. def test_now(self):
  415. ar1 = Article.objects.create(
  416. title='How to Django',
  417. text=lorem_ipsum,
  418. written=timezone.now(),
  419. )
  420. ar2 = Article.objects.create(
  421. title='How to Time Travel',
  422. text=lorem_ipsum,
  423. written=timezone.now(),
  424. )
  425. num_updated = Article.objects.filter(id=ar1.id, published=None).update(published=Now())
  426. self.assertEqual(num_updated, 1)
  427. num_updated = Article.objects.filter(id=ar1.id, published=None).update(published=Now())
  428. self.assertEqual(num_updated, 0)
  429. ar1.refresh_from_db()
  430. self.assertIsInstance(ar1.published, datetime)
  431. ar2.published = Now() + timedelta(days=2)
  432. ar2.save()
  433. ar2.refresh_from_db()
  434. self.assertIsInstance(ar2.published, datetime)
  435. self.assertQuerysetEqual(
  436. Article.objects.filter(published__lte=Now()),
  437. ['How to Django'],
  438. lambda a: a.title
  439. )
  440. self.assertQuerysetEqual(
  441. Article.objects.filter(published__gt=Now()),
  442. ['How to Time Travel'],
  443. lambda a: a.title
  444. )
  445. def test_length_transform(self):
  446. try:
  447. CharField.register_lookup(Length, 'length')
  448. Author.objects.create(name='John Smith', alias='smithj')
  449. Author.objects.create(name='Rhonda')
  450. authors = Author.objects.filter(name__length__gt=7)
  451. self.assertQuerysetEqual(
  452. authors.order_by('name'), [
  453. 'John Smith',
  454. ],
  455. lambda a: a.name
  456. )
  457. finally:
  458. CharField._unregister_lookup(Length, 'length')
  459. def test_lower_transform(self):
  460. try:
  461. CharField.register_lookup(Lower, 'lower')
  462. Author.objects.create(name='John Smith', alias='smithj')
  463. Author.objects.create(name='Rhonda')
  464. authors = Author.objects.filter(name__lower__exact='john smith')
  465. self.assertQuerysetEqual(
  466. authors.order_by('name'), [
  467. 'John Smith',
  468. ],
  469. lambda a: a.name
  470. )
  471. finally:
  472. CharField._unregister_lookup(Lower, 'lower')
  473. def test_upper_transform(self):
  474. try:
  475. CharField.register_lookup(Upper, 'upper')
  476. Author.objects.create(name='John Smith', alias='smithj')
  477. Author.objects.create(name='Rhonda')
  478. authors = Author.objects.filter(name__upper__exact='JOHN SMITH')
  479. self.assertQuerysetEqual(
  480. authors.order_by('name'), [
  481. 'John Smith',
  482. ],
  483. lambda a: a.name
  484. )
  485. finally:
  486. CharField._unregister_lookup(Upper, 'upper')
  487. def test_func_transform_bilateral(self):
  488. class UpperBilateral(Upper):
  489. bilateral = True
  490. try:
  491. CharField.register_lookup(UpperBilateral, 'upper')
  492. Author.objects.create(name='John Smith', alias='smithj')
  493. Author.objects.create(name='Rhonda')
  494. authors = Author.objects.filter(name__upper__exact='john smith')
  495. self.assertQuerysetEqual(
  496. authors.order_by('name'), [
  497. 'John Smith',
  498. ],
  499. lambda a: a.name
  500. )
  501. finally:
  502. CharField._unregister_lookup(UpperBilateral, 'upper')
  503. def test_func_transform_bilateral_multivalue(self):
  504. class UpperBilateral(Upper):
  505. bilateral = True
  506. try:
  507. CharField.register_lookup(UpperBilateral, 'upper')
  508. Author.objects.create(name='John Smith', alias='smithj')
  509. Author.objects.create(name='Rhonda')
  510. authors = Author.objects.filter(name__upper__in=['john smith', 'rhonda'])
  511. self.assertQuerysetEqual(
  512. authors.order_by('name'), [
  513. 'John Smith',
  514. 'Rhonda',
  515. ],
  516. lambda a: a.name
  517. )
  518. finally:
  519. CharField._unregister_lookup(UpperBilateral, 'upper')
  520. def test_function_as_filter(self):
  521. Author.objects.create(name='John Smith', alias='SMITHJ')
  522. Author.objects.create(name='Rhonda')
  523. self.assertQuerysetEqual(
  524. Author.objects.filter(alias=Upper(V('smithj'))),
  525. ['John Smith'], lambda x: x.name
  526. )
  527. self.assertQuerysetEqual(
  528. Author.objects.exclude(alias=Upper(V('smithj'))),
  529. ['Rhonda'], lambda x: x.name
  530. )