test_cast.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import datetime
  2. import decimal
  3. import unittest
  4. from django.db import connection, models
  5. from django.db.models.functions import Cast
  6. from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
  7. from django.test.utils import CaptureQueriesContext
  8. from ..models import Author, DTModel, Fan, FloatModel
  9. class CastTests(TestCase):
  10. @classmethod
  11. def setUpTestData(self):
  12. Author.objects.create(name="Bob", age=1, alias="1")
  13. def test_cast_from_value(self):
  14. numbers = Author.objects.annotate(
  15. cast_integer=Cast(models.Value("0"), models.IntegerField())
  16. )
  17. self.assertEqual(numbers.get().cast_integer, 0)
  18. def test_cast_from_field(self):
  19. numbers = Author.objects.annotate(
  20. cast_string=Cast("age", models.CharField(max_length=255)),
  21. )
  22. self.assertEqual(numbers.get().cast_string, "1")
  23. def test_cast_to_char_field_without_max_length(self):
  24. numbers = Author.objects.annotate(cast_string=Cast("age", models.CharField()))
  25. self.assertEqual(numbers.get().cast_string, "1")
  26. # Silence "Truncated incorrect CHAR(1) value: 'Bob'".
  27. @ignore_warnings(module="django.db.backends.mysql.base")
  28. @skipUnlessDBFeature("supports_cast_with_precision")
  29. def test_cast_to_char_field_with_max_length(self):
  30. names = Author.objects.annotate(
  31. cast_string=Cast("name", models.CharField(max_length=1))
  32. )
  33. self.assertEqual(names.get().cast_string, "B")
  34. @skipUnlessDBFeature("supports_cast_with_precision")
  35. def test_cast_to_decimal_field(self):
  36. FloatModel.objects.create(f1=-1.934, f2=3.467)
  37. float_obj = FloatModel.objects.annotate(
  38. cast_f1_decimal=Cast(
  39. "f1", models.DecimalField(max_digits=8, decimal_places=2)
  40. ),
  41. cast_f2_decimal=Cast(
  42. "f2", models.DecimalField(max_digits=8, decimal_places=1)
  43. ),
  44. ).get()
  45. self.assertEqual(float_obj.cast_f1_decimal, decimal.Decimal("-1.93"))
  46. expected = "3.4" if connection.features.rounds_to_even else "3.5"
  47. self.assertEqual(float_obj.cast_f2_decimal, decimal.Decimal(expected))
  48. author_obj = Author.objects.annotate(
  49. cast_alias_decimal=Cast(
  50. "alias", models.DecimalField(max_digits=8, decimal_places=2)
  51. ),
  52. ).get()
  53. self.assertEqual(author_obj.cast_alias_decimal, decimal.Decimal("1"))
  54. def test_cast_to_integer(self):
  55. for field_class in (
  56. models.AutoField,
  57. models.BigAutoField,
  58. models.SmallAutoField,
  59. models.IntegerField,
  60. models.BigIntegerField,
  61. models.SmallIntegerField,
  62. models.PositiveBigIntegerField,
  63. models.PositiveIntegerField,
  64. models.PositiveSmallIntegerField,
  65. ):
  66. with self.subTest(field_class=field_class):
  67. numbers = Author.objects.annotate(cast_int=Cast("alias", field_class()))
  68. self.assertEqual(numbers.get().cast_int, 1)
  69. def test_cast_to_integer_foreign_key(self):
  70. numbers = Author.objects.annotate(
  71. cast_fk=Cast(
  72. models.Value("0"),
  73. models.ForeignKey(Author, on_delete=models.SET_NULL),
  74. )
  75. )
  76. self.assertEqual(numbers.get().cast_fk, 0)
  77. def test_cast_to_duration(self):
  78. duration = datetime.timedelta(days=1, seconds=2, microseconds=3)
  79. DTModel.objects.create(duration=duration)
  80. dtm = DTModel.objects.annotate(
  81. cast_duration=Cast("duration", models.DurationField()),
  82. cast_neg_duration=Cast(-duration, models.DurationField()),
  83. ).get()
  84. self.assertEqual(dtm.cast_duration, duration)
  85. self.assertEqual(dtm.cast_neg_duration, -duration)
  86. def test_cast_from_db_datetime_to_date(self):
  87. dt_value = datetime.datetime(2018, 9, 28, 12, 42, 10, 234567)
  88. DTModel.objects.create(start_datetime=dt_value)
  89. dtm = DTModel.objects.annotate(
  90. start_datetime_as_date=Cast("start_datetime", models.DateField())
  91. ).first()
  92. self.assertEqual(dtm.start_datetime_as_date, datetime.date(2018, 9, 28))
  93. def test_cast_from_db_datetime_to_time(self):
  94. dt_value = datetime.datetime(2018, 9, 28, 12, 42, 10, 234567)
  95. DTModel.objects.create(start_datetime=dt_value)
  96. dtm = DTModel.objects.annotate(
  97. start_datetime_as_time=Cast("start_datetime", models.TimeField())
  98. ).first()
  99. rounded_ms = int(
  100. round(0.234567, connection.features.time_cast_precision) * 10**6
  101. )
  102. self.assertEqual(
  103. dtm.start_datetime_as_time, datetime.time(12, 42, 10, rounded_ms)
  104. )
  105. def test_cast_from_db_date_to_datetime(self):
  106. dt_value = datetime.date(2018, 9, 28)
  107. DTModel.objects.create(start_date=dt_value)
  108. dtm = DTModel.objects.annotate(
  109. start_as_datetime=Cast("start_date", models.DateTimeField())
  110. ).first()
  111. self.assertEqual(
  112. dtm.start_as_datetime, datetime.datetime(2018, 9, 28, 0, 0, 0, 0)
  113. )
  114. def test_cast_from_db_datetime_to_date_group_by(self):
  115. author = Author.objects.create(name="John Smith", age=45)
  116. dt_value = datetime.datetime(2018, 9, 28, 12, 42, 10, 234567)
  117. Fan.objects.create(name="Margaret", age=50, author=author, fan_since=dt_value)
  118. fans = (
  119. Fan.objects.values("author")
  120. .annotate(
  121. fan_for_day=Cast("fan_since", models.DateField()),
  122. fans=models.Count("*"),
  123. )
  124. .values()
  125. )
  126. self.assertEqual(fans[0]["fan_for_day"], datetime.date(2018, 9, 28))
  127. self.assertEqual(fans[0]["fans"], 1)
  128. def test_cast_from_python_to_date(self):
  129. today = datetime.date.today()
  130. dates = Author.objects.annotate(cast_date=Cast(today, models.DateField()))
  131. self.assertEqual(dates.get().cast_date, today)
  132. def test_cast_from_python_to_datetime(self):
  133. now = datetime.datetime.now()
  134. dates = Author.objects.annotate(cast_datetime=Cast(now, models.DateTimeField()))
  135. time_precision = datetime.timedelta(
  136. microseconds=10 ** (6 - connection.features.time_cast_precision)
  137. )
  138. self.assertAlmostEqual(dates.get().cast_datetime, now, delta=time_precision)
  139. def test_cast_from_python(self):
  140. numbers = Author.objects.annotate(
  141. cast_float=Cast(decimal.Decimal(0.125), models.FloatField())
  142. )
  143. cast_float = numbers.get().cast_float
  144. self.assertIsInstance(cast_float, float)
  145. self.assertEqual(cast_float, 0.125)
  146. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL test")
  147. def test_expression_wrapped_with_parentheses_on_postgresql(self):
  148. """
  149. The SQL for the Cast expression is wrapped with parentheses in case
  150. it's a complex expression.
  151. """
  152. with CaptureQueriesContext(connection) as captured_queries:
  153. list(
  154. Author.objects.annotate(
  155. cast_float=Cast(models.Avg("age"), models.FloatField()),
  156. )
  157. )
  158. self.assertIn(
  159. '(AVG("db_functions_author"."age"))::double precision',
  160. captured_queries[0]["sql"],
  161. )
  162. def test_cast_to_text_field(self):
  163. self.assertEqual(
  164. Author.objects.values_list(
  165. Cast("age", models.TextField()), flat=True
  166. ).get(),
  167. "1",
  168. )