test_floatfield.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. from django.core.exceptions import ValidationError
  2. from django.forms import FloatField, NumberInput
  3. from django.test import SimpleTestCase
  4. from django.test.selenium import SeleniumTestCase
  5. from django.test.utils import ignore_warnings, override_settings
  6. from django.urls import reverse
  7. from django.utils import formats, translation
  8. from django.utils.deprecation import RemovedInDjango50Warning
  9. from . import FormFieldAssertionsMixin
  10. class FloatFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
  11. def test_floatfield_1(self):
  12. f = FloatField()
  13. self.assertWidgetRendersTo(
  14. f, '<input step="any" type="number" name="f" id="id_f" required>'
  15. )
  16. with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
  17. f.clean("")
  18. with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
  19. f.clean(None)
  20. self.assertEqual(1.0, f.clean("1"))
  21. self.assertIsInstance(f.clean("1"), float)
  22. self.assertEqual(23.0, f.clean("23"))
  23. self.assertEqual(3.1400000000000001, f.clean("3.14"))
  24. self.assertEqual(3.1400000000000001, f.clean(3.14))
  25. self.assertEqual(42.0, f.clean(42))
  26. with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
  27. f.clean("a")
  28. self.assertEqual(1.0, f.clean("1.0 "))
  29. self.assertEqual(1.0, f.clean(" 1.0"))
  30. self.assertEqual(1.0, f.clean(" 1.0 "))
  31. with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
  32. f.clean("1.0a")
  33. self.assertIsNone(f.max_value)
  34. self.assertIsNone(f.min_value)
  35. with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
  36. f.clean("Infinity")
  37. with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
  38. f.clean("NaN")
  39. with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
  40. f.clean("-Inf")
  41. def test_floatfield_2(self):
  42. f = FloatField(required=False)
  43. self.assertIsNone(f.clean(""))
  44. self.assertIsNone(f.clean(None))
  45. self.assertEqual(1.0, f.clean("1"))
  46. self.assertIsNone(f.max_value)
  47. self.assertIsNone(f.min_value)
  48. def test_floatfield_3(self):
  49. f = FloatField(max_value=1.5, min_value=0.5)
  50. self.assertWidgetRendersTo(
  51. f,
  52. '<input step="any" name="f" min="0.5" max="1.5" type="number" id="id_f" '
  53. "required>",
  54. )
  55. with self.assertRaisesMessage(
  56. ValidationError, "'Ensure this value is less than or equal to 1.5.'"
  57. ):
  58. f.clean("1.6")
  59. with self.assertRaisesMessage(
  60. ValidationError, "'Ensure this value is greater than or equal to 0.5.'"
  61. ):
  62. f.clean("0.4")
  63. self.assertEqual(1.5, f.clean("1.5"))
  64. self.assertEqual(0.5, f.clean("0.5"))
  65. self.assertEqual(f.max_value, 1.5)
  66. self.assertEqual(f.min_value, 0.5)
  67. def test_floatfield_4(self):
  68. f = FloatField(step_size=0.02)
  69. self.assertWidgetRendersTo(
  70. f,
  71. '<input name="f" step="0.02" type="number" id="id_f" required>',
  72. )
  73. msg = "'Ensure this value is a multiple of step size 0.02.'"
  74. with self.assertRaisesMessage(ValidationError, msg):
  75. f.clean("0.01")
  76. self.assertEqual(2.34, f.clean("2.34"))
  77. self.assertEqual(2.1, f.clean("2.1"))
  78. self.assertEqual(-0.50, f.clean("-.5"))
  79. self.assertEqual(-1.26, f.clean("-1.26"))
  80. self.assertEqual(f.step_size, 0.02)
  81. def test_floatfield_widget_attrs(self):
  82. f = FloatField(widget=NumberInput(attrs={"step": 0.01, "max": 1.0, "min": 0.0}))
  83. self.assertWidgetRendersTo(
  84. f,
  85. '<input step="0.01" name="f" min="0.0" max="1.0" type="number" id="id_f" '
  86. "required>",
  87. )
  88. def test_floatfield_localized(self):
  89. """
  90. A localized FloatField's widget renders to a text input without any
  91. number input specific attributes.
  92. """
  93. f = FloatField(localize=True)
  94. self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" required>')
  95. def test_floatfield_changed(self):
  96. f = FloatField()
  97. n = 4.35
  98. self.assertFalse(f.has_changed(n, "4.3500"))
  99. with translation.override("fr"):
  100. f = FloatField(localize=True)
  101. localized_n = formats.localize_input(n) # -> '4,35' in French
  102. self.assertFalse(f.has_changed(n, localized_n))
  103. # RemovedInDjango50Warning: When the deprecation ends, remove
  104. # @ignore_warnings and USE_L10N=False. The test should remain because
  105. # format-related settings will take precedence over locale-dictated
  106. # formats.
  107. @ignore_warnings(category=RemovedInDjango50Warning)
  108. @override_settings(USE_L10N=False, DECIMAL_SEPARATOR=",")
  109. def test_decimalfield_support_decimal_separator(self):
  110. f = FloatField(localize=True)
  111. self.assertEqual(f.clean("1001,10"), 1001.10)
  112. self.assertEqual(f.clean("1001.10"), 1001.10)
  113. # RemovedInDjango50Warning: When the deprecation ends, remove
  114. # @ignore_warnings and USE_L10N=False. The test should remain because
  115. # format-related settings will take precedence over locale-dictated
  116. # formats.
  117. @ignore_warnings(category=RemovedInDjango50Warning)
  118. @override_settings(
  119. USE_L10N=False,
  120. DECIMAL_SEPARATOR=",",
  121. USE_THOUSAND_SEPARATOR=True,
  122. THOUSAND_SEPARATOR=".",
  123. )
  124. def test_decimalfield_support_thousands_separator(self):
  125. f = FloatField(localize=True)
  126. self.assertEqual(f.clean("1.001,10"), 1001.10)
  127. msg = "'Enter a number.'"
  128. with self.assertRaisesMessage(ValidationError, msg):
  129. f.clean("1,001.1")
  130. @override_settings(ROOT_URLCONF="forms_tests.urls")
  131. class FloatFieldHTMLTest(SeleniumTestCase):
  132. available_apps = ["forms_tests"]
  133. def test_float_field_rendering_passes_client_side_validation(self):
  134. """
  135. Rendered widget allows non-integer value with the client-side
  136. validation.
  137. """
  138. from selenium.webdriver.common.by import By
  139. self.selenium.get(self.live_server_url + reverse("form_view"))
  140. number_input = self.selenium.find_element(By.ID, "id_number")
  141. number_input.send_keys("0.5")
  142. is_valid = self.selenium.execute_script(
  143. "return document.getElementById('id_number').checkValidity()"
  144. )
  145. self.assertTrue(is_valid)