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

1import decimal 

2 

3from wtforms import widgets 

4from wtforms.fields.core import Field 

5from wtforms.utils import unset_value 

6 

7__all__ = ( 

8 "IntegerField", 

9 "DecimalField", 

10 "FloatField", 

11 "IntegerRangeField", 

12 "DecimalRangeField", 

13) 

14 

15 

16class LocaleAwareNumberField(Field): 

17 """ 

18 Base class for implementing locale-aware number parsing. 

19 

20 Locale-aware numbers require the 'babel' package to be present. 

21 """ 

22 

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() 

37 

38 def _init_babel(self): 

39 try: 

40 from babel import numbers 

41 

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 

47 

48 def _parse_decimal(self, value): 

49 return self.babel_numbers.parse_decimal(value, self.locale) 

50 

51 def _format_decimal(self, value): 

52 return self.babel_numbers.format_decimal(value, self.number_format, self.locale) 

53 

54 

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 """ 

60 

61 widget = widgets.NumberInput() 

62 

63 def __init__(self, label=None, validators=None, **kwargs): 

64 super().__init__(label, validators, **kwargs) 

65 

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 "" 

72 

73 def process_data(self, value): 

74 if value is None or value is unset_value: 

75 self.data = None 

76 return 

77 

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 

83 

84 def process_formdata(self, valuelist): 

85 if not valuelist: 

86 return 

87 

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 

93 

94 

95class DecimalField(LocaleAwareNumberField): 

96 """ 

97 A text field which displays and coerces data of the `decimal.Decimal` type. 

98 

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 """ 

114 

115 widget = widgets.NumberInput(step="any") 

116 

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 ) 

125 

126 if places is unset_value: 

127 places = 2 

128 self.places = places 

129 self.rounding = rounding 

130 

131 def _value(self): 

132 if self.raw_data: 

133 return self.raw_data[0] 

134 

135 if self.data is None: 

136 return "" 

137 

138 if self.use_locale: 

139 return str(self._format_decimal(self.data)) 

140 

141 if self.places is None: 

142 return str(self.data) 

143 

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 

149 

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) 

156 

157 def process_formdata(self, valuelist): 

158 if not valuelist: 

159 return 

160 

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 

169 

170 

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 """ 

176 

177 widget = widgets.TextInput() 

178 

179 def __init__(self, label=None, validators=None, **kwargs): 

180 super().__init__(label, validators, **kwargs) 

181 

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 "" 

188 

189 def process_formdata(self, valuelist): 

190 if not valuelist: 

191 return 

192 

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 

198 

199 

200class IntegerRangeField(IntegerField): 

201 """ 

202 Represents an ``<input type="range">``. 

203 """ 

204 

205 widget = widgets.RangeInput() 

206 

207 

208class DecimalRangeField(DecimalField): 

209 """ 

210 Represents an ``<input type="range">``. 

211 """ 

212 

213 widget = widgets.RangeInput(step="any")