from django.core.exceptions import ValidationError
from django.forms import FloatField, NumberInput
from django.test import SimpleTestCase
from django.test.selenium import SeleniumTestCase
from django.test.utils import ignore_warnings, override_settings
from django.urls import reverse
from django.utils import formats, translation
from django.utils.deprecation import RemovedInDjango50Warning
from . import FormFieldAssertionsMixin
class FloatFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
def test_floatfield_1(self):
f = FloatField()
self.assertWidgetRendersTo(
f, ''
)
with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
f.clean("")
with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
f.clean(None)
self.assertEqual(1.0, f.clean("1"))
self.assertIsInstance(f.clean("1"), float)
self.assertEqual(23.0, f.clean("23"))
self.assertEqual(3.1400000000000001, f.clean("3.14"))
self.assertEqual(3.1400000000000001, f.clean(3.14))
self.assertEqual(42.0, f.clean(42))
with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
f.clean("a")
self.assertEqual(1.0, f.clean("1.0 "))
self.assertEqual(1.0, f.clean(" 1.0"))
self.assertEqual(1.0, f.clean(" 1.0 "))
with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
f.clean("1.0a")
self.assertIsNone(f.max_value)
self.assertIsNone(f.min_value)
with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
f.clean("Infinity")
with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
f.clean("NaN")
with self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
f.clean("-Inf")
def test_floatfield_2(self):
f = FloatField(required=False)
self.assertIsNone(f.clean(""))
self.assertIsNone(f.clean(None))
self.assertEqual(1.0, f.clean("1"))
self.assertIsNone(f.max_value)
self.assertIsNone(f.min_value)
def test_floatfield_3(self):
f = FloatField(max_value=1.5, min_value=0.5)
self.assertWidgetRendersTo(
f,
'",
)
with self.assertRaisesMessage(
ValidationError, "'Ensure this value is less than or equal to 1.5.'"
):
f.clean("1.6")
with self.assertRaisesMessage(
ValidationError, "'Ensure this value is greater than or equal to 0.5.'"
):
f.clean("0.4")
self.assertEqual(1.5, f.clean("1.5"))
self.assertEqual(0.5, f.clean("0.5"))
self.assertEqual(f.max_value, 1.5)
self.assertEqual(f.min_value, 0.5)
def test_floatfield_4(self):
f = FloatField(step_size=0.02)
self.assertWidgetRendersTo(
f,
'',
)
msg = "'Ensure this value is a multiple of step size 0.02.'"
with self.assertRaisesMessage(ValidationError, msg):
f.clean("0.01")
self.assertEqual(2.34, f.clean("2.34"))
self.assertEqual(2.1, f.clean("2.1"))
self.assertEqual(-0.50, f.clean("-.5"))
self.assertEqual(-1.26, f.clean("-1.26"))
self.assertEqual(f.step_size, 0.02)
def test_floatfield_widget_attrs(self):
f = FloatField(widget=NumberInput(attrs={"step": 0.01, "max": 1.0, "min": 0.0}))
self.assertWidgetRendersTo(
f,
'",
)
def test_floatfield_localized(self):
"""
A localized FloatField's widget renders to a text input without any
number input specific attributes.
"""
f = FloatField(localize=True)
self.assertWidgetRendersTo(f, '')
def test_floatfield_changed(self):
f = FloatField()
n = 4.35
self.assertFalse(f.has_changed(n, "4.3500"))
with translation.override("fr"):
f = FloatField(localize=True)
localized_n = formats.localize_input(n) # -> '4,35' in French
self.assertFalse(f.has_changed(n, localized_n))
# RemovedInDjango50Warning: When the deprecation ends, remove
# @ignore_warnings and USE_L10N=False. The test should remain because
# format-related settings will take precedence over locale-dictated
# formats.
@ignore_warnings(category=RemovedInDjango50Warning)
@override_settings(USE_L10N=False, DECIMAL_SEPARATOR=",")
def test_decimalfield_support_decimal_separator(self):
f = FloatField(localize=True)
self.assertEqual(f.clean("1001,10"), 1001.10)
self.assertEqual(f.clean("1001.10"), 1001.10)
# RemovedInDjango50Warning: When the deprecation ends, remove
# @ignore_warnings and USE_L10N=False. The test should remain because
# format-related settings will take precedence over locale-dictated
# formats.
@ignore_warnings(category=RemovedInDjango50Warning)
@override_settings(
USE_L10N=False,
DECIMAL_SEPARATOR=",",
USE_THOUSAND_SEPARATOR=True,
THOUSAND_SEPARATOR=".",
)
def test_decimalfield_support_thousands_separator(self):
f = FloatField(localize=True)
self.assertEqual(f.clean("1.001,10"), 1001.10)
msg = "'Enter a number.'"
with self.assertRaisesMessage(ValidationError, msg):
f.clean("1,001.1")
@override_settings(ROOT_URLCONF="forms_tests.urls")
class FloatFieldHTMLTest(SeleniumTestCase):
available_apps = ["forms_tests"]
def test_float_field_rendering_passes_client_side_validation(self):
"""
Rendered widget allows non-integer value with the client-side
validation.
"""
from selenium.webdriver.common.by import By
self.selenium.get(self.live_server_url + reverse("form_view"))
number_input = self.selenium.find_element(By.ID, "id_number")
number_input.send_keys("0.5")
is_valid = self.selenium.execute_script(
"return document.getElementById('id_number').checkValidity()"
)
self.assertTrue(is_valid)