test_decimalfield.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import decimal
  2. from django.core.exceptions import ValidationError
  3. from django.forms import DecimalField, NumberInput, Widget
  4. from django.test import SimpleTestCase, override_settings
  5. from django.utils import formats, translation
  6. from . import FormFieldAssertionsMixin
  7. class DecimalFieldTest(FormFieldAssertionsMixin, SimpleTestCase):
  8. def test_decimalfield_1(self):
  9. f = DecimalField(max_digits=4, decimal_places=2)
  10. self.assertWidgetRendersTo(f, '<input id="id_f" step="0.01" type="number" name="f" required>')
  11. with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
  12. f.clean('')
  13. with self.assertRaisesMessage(ValidationError, "'This field is required.'"):
  14. f.clean(None)
  15. self.assertEqual(f.clean('1'), decimal.Decimal("1"))
  16. self.assertIsInstance(f.clean('1'), decimal.Decimal)
  17. self.assertEqual(f.clean('23'), decimal.Decimal("23"))
  18. self.assertEqual(f.clean('3.14'), decimal.Decimal("3.14"))
  19. self.assertEqual(f.clean(3.14), decimal.Decimal("3.14"))
  20. self.assertEqual(f.clean(decimal.Decimal('3.14')), decimal.Decimal("3.14"))
  21. self.assertEqual(f.clean('1.0 '), decimal.Decimal("1.0"))
  22. self.assertEqual(f.clean(' 1.0'), decimal.Decimal("1.0"))
  23. self.assertEqual(f.clean(' 1.0 '), decimal.Decimal("1.0"))
  24. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"):
  25. f.clean('123.45')
  26. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"):
  27. f.clean('1.234')
  28. msg = "'Ensure that there are no more than 2 digits before the decimal point.'"
  29. with self.assertRaisesMessage(ValidationError, msg):
  30. f.clean('123.4')
  31. self.assertEqual(f.clean('-12.34'), decimal.Decimal("-12.34"))
  32. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"):
  33. f.clean('-123.45')
  34. self.assertEqual(f.clean('-.12'), decimal.Decimal("-0.12"))
  35. self.assertEqual(f.clean('-00.12'), decimal.Decimal("-0.12"))
  36. self.assertEqual(f.clean('-000.12'), decimal.Decimal("-0.12"))
  37. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"):
  38. f.clean('-000.123')
  39. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 4 digits in total.'"):
  40. f.clean('-000.12345')
  41. self.assertEqual(f.max_digits, 4)
  42. self.assertEqual(f.decimal_places, 2)
  43. self.assertIsNone(f.max_value)
  44. self.assertIsNone(f.min_value)
  45. def test_enter_a_number_error(self):
  46. f = DecimalField(max_digits=4, decimal_places=2)
  47. values = (
  48. '-NaN', 'NaN', '+NaN',
  49. '-sNaN', 'sNaN', '+sNaN',
  50. '-Inf', 'Inf', '+Inf',
  51. '-Infinity', 'Infinity', '+Infinity',
  52. 'a', 'łąść', '1.0a', '--0.12',
  53. )
  54. for value in values:
  55. with self.subTest(value=value), self.assertRaisesMessage(ValidationError, "'Enter a number.'"):
  56. f.clean(value)
  57. def test_decimalfield_2(self):
  58. f = DecimalField(max_digits=4, decimal_places=2, required=False)
  59. self.assertIsNone(f.clean(''))
  60. self.assertIsNone(f.clean(None))
  61. self.assertEqual(f.clean('1'), decimal.Decimal("1"))
  62. self.assertEqual(f.max_digits, 4)
  63. self.assertEqual(f.decimal_places, 2)
  64. self.assertIsNone(f.max_value)
  65. self.assertIsNone(f.min_value)
  66. def test_decimalfield_3(self):
  67. f = DecimalField(
  68. max_digits=4, decimal_places=2,
  69. max_value=decimal.Decimal('1.5'),
  70. min_value=decimal.Decimal('0.5')
  71. )
  72. self.assertWidgetRendersTo(
  73. f,
  74. '<input step="0.01" name="f" min="0.5" max="1.5" type="number" id="id_f" required>',
  75. )
  76. with self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'"):
  77. f.clean('1.6')
  78. with self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'"):
  79. f.clean('0.4')
  80. self.assertEqual(f.clean('1.5'), decimal.Decimal("1.5"))
  81. self.assertEqual(f.clean('0.5'), decimal.Decimal("0.5"))
  82. self.assertEqual(f.clean('.5'), decimal.Decimal("0.5"))
  83. self.assertEqual(f.clean('00.50'), decimal.Decimal("0.50"))
  84. self.assertEqual(f.max_digits, 4)
  85. self.assertEqual(f.decimal_places, 2)
  86. self.assertEqual(f.max_value, decimal.Decimal('1.5'))
  87. self.assertEqual(f.min_value, decimal.Decimal('0.5'))
  88. def test_decimalfield_4(self):
  89. f = DecimalField(decimal_places=2)
  90. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 2 decimal places.'"):
  91. f.clean('0.00000001')
  92. def test_decimalfield_5(self):
  93. f = DecimalField(max_digits=3)
  94. # Leading whole zeros "collapse" to one digit.
  95. self.assertEqual(f.clean('0000000.10'), decimal.Decimal("0.1"))
  96. # But a leading 0 before the . doesn't count towards max_digits
  97. self.assertEqual(f.clean('0000000.100'), decimal.Decimal("0.100"))
  98. # Only leading whole zeros "collapse" to one digit.
  99. self.assertEqual(f.clean('000000.02'), decimal.Decimal('0.02'))
  100. with self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 3 digits in total.'"):
  101. f.clean('000000.0002')
  102. self.assertEqual(f.clean('.002'), decimal.Decimal("0.002"))
  103. def test_decimalfield_6(self):
  104. f = DecimalField(max_digits=2, decimal_places=2)
  105. self.assertEqual(f.clean('.01'), decimal.Decimal(".01"))
  106. msg = "'Ensure that there are no more than 0 digits before the decimal point.'"
  107. with self.assertRaisesMessage(ValidationError, msg):
  108. f.clean('1.1')
  109. def test_decimalfield_scientific(self):
  110. f = DecimalField(max_digits=4, decimal_places=2)
  111. with self.assertRaisesMessage(ValidationError, "Ensure that there are no more"):
  112. f.clean('1E+2')
  113. self.assertEqual(f.clean('1E+1'), decimal.Decimal('10'))
  114. self.assertEqual(f.clean('1E-1'), decimal.Decimal('0.1'))
  115. self.assertEqual(f.clean('0.546e+2'), decimal.Decimal('54.6'))
  116. def test_decimalfield_widget_attrs(self):
  117. f = DecimalField(max_digits=6, decimal_places=2)
  118. self.assertEqual(f.widget_attrs(Widget()), {})
  119. self.assertEqual(f.widget_attrs(NumberInput()), {'step': '0.01'})
  120. f = DecimalField(max_digits=10, decimal_places=0)
  121. self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1'})
  122. f = DecimalField(max_digits=19, decimal_places=19)
  123. self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1e-19'})
  124. f = DecimalField(max_digits=20)
  125. self.assertEqual(f.widget_attrs(NumberInput()), {'step': 'any'})
  126. f = DecimalField(max_digits=6, widget=NumberInput(attrs={'step': '0.01'}))
  127. self.assertWidgetRendersTo(f, '<input step="0.01" name="f" type="number" id="id_f" required>')
  128. def test_decimalfield_localized(self):
  129. """
  130. A localized DecimalField's widget renders to a text input without
  131. number input specific attributes.
  132. """
  133. f = DecimalField(localize=True)
  134. self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" required>')
  135. def test_decimalfield_changed(self):
  136. f = DecimalField(max_digits=2, decimal_places=2)
  137. d = decimal.Decimal("0.1")
  138. self.assertFalse(f.has_changed(d, '0.10'))
  139. self.assertTrue(f.has_changed(d, '0.101'))
  140. with translation.override('fr'), self.settings(USE_L10N=True):
  141. f = DecimalField(max_digits=2, decimal_places=2, localize=True)
  142. localized_d = formats.localize_input(d) # -> '0,1' in French
  143. self.assertFalse(f.has_changed(d, localized_d))
  144. @override_settings(USE_L10N=False, DECIMAL_SEPARATOR=',')
  145. def test_decimalfield_support_decimal_separator(self):
  146. f = DecimalField(localize=True)
  147. self.assertEqual(f.clean('1001,10'), decimal.Decimal("1001.10"))
  148. self.assertEqual(f.clean('1001.10'), decimal.Decimal("1001.10"))
  149. @override_settings(USE_L10N=False, DECIMAL_SEPARATOR=',', USE_THOUSAND_SEPARATOR=True,
  150. THOUSAND_SEPARATOR='.')
  151. def test_decimalfield_support_thousands_separator(self):
  152. f = DecimalField(localize=True)
  153. self.assertEqual(f.clean('1.001,10'), decimal.Decimal("1001.10"))
  154. msg = "'Enter a number.'"
  155. with self.assertRaisesMessage(ValidationError, msg):
  156. f.clean('1,001.1')