test_decimalfield.py 8.0 KB

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