Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/wtforms/fields/numeric.py: 28%
109 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:32 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:32 +0000
1import decimal
3from wtforms import widgets
4from wtforms.fields.core import Field
5from wtforms.utils import unset_value
7__all__ = (
8 "IntegerField",
9 "DecimalField",
10 "FloatField",
11 "IntegerRangeField",
12 "DecimalRangeField",
13)
16class LocaleAwareNumberField(Field):
17 """
18 Base class for implementing locale-aware number parsing.
20 Locale-aware numbers require the 'babel' package to be present.
21 """
23 def __init__(
24 self,
25 label=None,
26 validators=None,
27 use_locale=False,
28 number_format=None,
29 **kwargs,
30 ):
31 super().__init__(label, validators, **kwargs)
32 self.use_locale = use_locale
33 if use_locale:
34 self.number_format = number_format
35 self.locale = kwargs["_form"].meta.locales[0]
36 self._init_babel()
38 def _init_babel(self):
39 try:
40 from babel import numbers
42 self.babel_numbers = numbers
43 except ImportError as exc:
44 raise ImportError(
45 "Using locale-aware decimals requires the babel library."
46 ) from exc
48 def _parse_decimal(self, value):
49 return self.babel_numbers.parse_decimal(value, self.locale)
51 def _format_decimal(self, value):
52 return self.babel_numbers.format_decimal(value, self.number_format, self.locale)
55class IntegerField(Field):
56 """
57 A text field, except all input is coerced to an integer. Erroneous input
58 is ignored and will not be accepted as a value.
59 """
61 widget = widgets.NumberInput()
63 def __init__(self, label=None, validators=None, **kwargs):
64 super().__init__(label, validators, **kwargs)
66 def _value(self):
67 if self.raw_data:
68 return self.raw_data[0]
69 if self.data is not None:
70 return str(self.data)
71 return ""
73 def process_data(self, value):
74 if value is None or value is unset_value:
75 self.data = None
76 return
78 try:
79 self.data = int(value)
80 except (ValueError, TypeError) as exc:
81 self.data = None
82 raise ValueError(self.gettext("Not a valid integer value.")) from exc
84 def process_formdata(self, valuelist):
85 if not valuelist:
86 return
88 try:
89 self.data = int(valuelist[0])
90 except ValueError as exc:
91 self.data = None
92 raise ValueError(self.gettext("Not a valid integer value.")) from exc
95class DecimalField(LocaleAwareNumberField):
96 """
97 A text field which displays and coerces data of the `decimal.Decimal` type.
99 :param places:
100 How many decimal places to quantize the value to for display on form.
101 If unset, use 2 decimal places.
102 If explicitely set to `None`, does not quantize value.
103 :param rounding:
104 How to round the value during quantize, for example
105 `decimal.ROUND_UP`. If unset, uses the rounding value from the
106 current thread's context.
107 :param use_locale:
108 If True, use locale-based number formatting. Locale-based number
109 formatting requires the 'babel' package.
110 :param number_format:
111 Optional number format for locale. If omitted, use the default decimal
112 format for the locale.
113 """
115 widget = widgets.NumberInput(step="any")
117 def __init__(
118 self, label=None, validators=None, places=unset_value, rounding=None, **kwargs
119 ):
120 super().__init__(label, validators, **kwargs)
121 if self.use_locale and (places is not unset_value or rounding is not None):
122 raise TypeError(
123 "When using locale-aware numbers, 'places' and 'rounding' are ignored."
124 )
126 if places is unset_value:
127 places = 2
128 self.places = places
129 self.rounding = rounding
131 def _value(self):
132 if self.raw_data:
133 return self.raw_data[0]
135 if self.data is None:
136 return ""
138 if self.use_locale:
139 return str(self._format_decimal(self.data))
141 if self.places is None:
142 return str(self.data)
144 if not hasattr(self.data, "quantize"):
145 # If for some reason, data is a float or int, then format
146 # as we would for floats using string formatting.
147 format = "%%0.%df" % self.places
148 return format % self.data
150 exp = decimal.Decimal(".1") ** self.places
151 if self.rounding is None:
152 quantized = self.data.quantize(exp)
153 else:
154 quantized = self.data.quantize(exp, rounding=self.rounding)
155 return str(quantized)
157 def process_formdata(self, valuelist):
158 if not valuelist:
159 return
161 try:
162 if self.use_locale:
163 self.data = self._parse_decimal(valuelist[0])
164 else:
165 self.data = decimal.Decimal(valuelist[0])
166 except (decimal.InvalidOperation, ValueError) as exc:
167 self.data = None
168 raise ValueError(self.gettext("Not a valid decimal value.")) from exc
171class FloatField(Field):
172 """
173 A text field, except all input is coerced to an float. Erroneous input
174 is ignored and will not be accepted as a value.
175 """
177 widget = widgets.TextInput()
179 def __init__(self, label=None, validators=None, **kwargs):
180 super().__init__(label, validators, **kwargs)
182 def _value(self):
183 if self.raw_data:
184 return self.raw_data[0]
185 if self.data is not None:
186 return str(self.data)
187 return ""
189 def process_formdata(self, valuelist):
190 if not valuelist:
191 return
193 try:
194 self.data = float(valuelist[0])
195 except ValueError as exc:
196 self.data = None
197 raise ValueError(self.gettext("Not a valid float value.")) from exc
200class IntegerRangeField(IntegerField):
201 """
202 Represents an ``<input type="range">``.
203 """
205 widget = widgets.RangeInput()
208class DecimalRangeField(DecimalField):
209 """
210 Represents an ``<input type="range">``.
211 """
213 widget = widgets.RangeInput(step="any")