Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/fields/datetime.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

109 statements  

1import datetime 

2import warnings 

3from collections.abc import Callable 

4 

5from wtforms import widgets 

6from wtforms.fields.core import Field 

7from wtforms.utils import clean_datetime_format_for_strptime 

8 

9__all__ = ( 

10 "DateTimeField", 

11 "DateField", 

12 "TimeField", 

13 "MonthField", 

14 "DateTimeLocalField", 

15 "WeekField", 

16) 

17 

18 

19class DateTimeField(Field): 

20 """ 

21 A text field which stores a :class:`datetime.datetime` matching one or 

22 several formats. If ``format`` is a list, any input value matching any 

23 format will be accepted, and the first format in the list will be used 

24 to produce HTML values. 

25 

26 .. deprecated:: 3.2.3 

27 ``DateTimeField`` renders ``<input type="datetime">``, which is 

28 obsolete. Use :class:`DateTimeLocalField` instead. ``DateTimeField`` 

29 will be removed in WTForms 3.4. 

30 """ 

31 

32 widget = widgets.DateTimeInput() 

33 

34 def __init__( 

35 self, 

36 label=None, 

37 validators=None, 

38 format="%Y-%m-%d %H:%M:%S", 

39 invalid_value_message=None, 

40 **kwargs, 

41 ): 

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

43 if type(self) is DateTimeField: 

44 warnings.warn( 

45 "'DateTimeField' renders <input type=\"datetime\">, which is" 

46 " obsolete. Use 'DateTimeLocalField' instead. 'DateTimeField'" 

47 " will be removed in WTForms 3.4.", 

48 DeprecationWarning, 

49 stacklevel=2, 

50 ) 

51 self.format = format if isinstance(format, list) else [format] 

52 self.strptime_format = clean_datetime_format_for_strptime(self.format) 

53 self.invalid_value_message = invalid_value_message or self.gettext( 

54 "Not a valid datetime value." 

55 ) 

56 

57 def _value(self): 

58 if self.raw_data: 

59 return " ".join(self.raw_data) 

60 format = self.format[0] 

61 return self.data and self.data.strftime(format) or "" 

62 

63 def process_formdata(self, valuelist): 

64 if not valuelist: 

65 return 

66 

67 date_str = " ".join(valuelist) 

68 for format in self.strptime_format: 

69 try: 

70 self.data = datetime.datetime.strptime(date_str, format) 

71 return 

72 except ValueError: 

73 self.data = None 

74 

75 raise ValueError(self.invalid_value_message) 

76 

77 

78class DateField(DateTimeField): 

79 """ 

80 Same as :class:`~wtforms.fields.DateTimeField`, except stores a 

81 :class:`datetime.date` and renders as an :mdn-input:`date`. 

82 """ 

83 

84 widget = widgets.DateInput() 

85 

86 def __init__( 

87 self, 

88 label=None, 

89 validators=None, 

90 format="%Y-%m-%d", 

91 invalid_value_message=None, 

92 **kwargs, 

93 ): 

94 super().__init__( 

95 label, 

96 validators, 

97 format, 

98 invalid_value_message=invalid_value_message, 

99 **kwargs, 

100 ) 

101 self.invalid_value_message = invalid_value_message or self.gettext( 

102 "Not a valid date value." 

103 ) 

104 

105 def process_formdata(self, valuelist): 

106 if not valuelist: 

107 return 

108 

109 date_str = " ".join(valuelist) 

110 for format in self.strptime_format: 

111 try: 

112 self.data = datetime.datetime.strptime(date_str, format).date() 

113 return 

114 except ValueError: 

115 self.data = None 

116 

117 raise ValueError(self.invalid_value_message) 

118 

119 

120class TimeField(DateTimeField): 

121 """ 

122 Same as :class:`~wtforms.fields.DateTimeField`, except stores a 

123 :class:`datetime.time` and renders as an :mdn-input:`time`. 

124 """ 

125 

126 widget = widgets.TimeInput() 

127 

128 def __init__( 

129 self, 

130 label=None, 

131 validators=None, 

132 format="%H:%M", 

133 invalid_value_message=None, 

134 **kwargs, 

135 ): 

136 super().__init__( 

137 label, 

138 validators, 

139 format, 

140 invalid_value_message=invalid_value_message, 

141 **kwargs, 

142 ) 

143 self.invalid_value_message = invalid_value_message or self.gettext( 

144 "Not a valid time value." 

145 ) 

146 

147 def process_formdata(self, valuelist): 

148 if not valuelist: 

149 return 

150 

151 time_str = " ".join(valuelist) 

152 for format in self.strptime_format: 

153 try: 

154 self.data = datetime.datetime.strptime(time_str, format).time() 

155 return 

156 except ValueError: 

157 self.data = None 

158 

159 raise ValueError(self.invalid_value_message) 

160 

161 

162class MonthField(DateField): 

163 """ 

164 Same as :class:`~wtforms.fields.DateField`, except represents a month, 

165 stores a :class:`datetime.date` with `day = 1`, and renders as an 

166 :mdn-input:`month`. 

167 """ 

168 

169 widget = widgets.MonthInput() 

170 

171 def __init__(self, label=None, validators=None, format="%Y-%m", **kwargs): 

172 super().__init__(label, validators, format, **kwargs) 

173 

174 

175class WeekField(DateField): 

176 """ 

177 Same as :class:`~wtforms.fields.DateField`, except represents a week, 

178 stores a :class:`datetime.date` of the monday of the given week, and 

179 renders as an :mdn-input:`week`. 

180 """ 

181 

182 widget = widgets.WeekInput() 

183 

184 def __init__( 

185 self, 

186 label=None, 

187 validators=None, 

188 format="%Y-W%W", 

189 invalid_value_message=None, 

190 **kwargs, 

191 ): 

192 super().__init__( 

193 label, 

194 validators, 

195 format, 

196 invalid_value_message=invalid_value_message, 

197 **kwargs, 

198 ) 

199 self.invalid_value_message = invalid_value_message or self.gettext( 

200 "Not a valid week value." 

201 ) 

202 

203 def process_formdata(self, valuelist): 

204 if not valuelist: 

205 return 

206 

207 time_str = " ".join(valuelist) 

208 for format in self.strptime_format: 

209 try: 

210 if "%w" not in format: 

211 # The '%w' week starting day is needed. This defaults it to monday 

212 # like ISO 8601 indicates. 

213 self.data = datetime.datetime.strptime( 

214 f"{time_str}-1", f"{format}-%w" 

215 ).date() 

216 else: 

217 self.data = datetime.datetime.strptime(time_str, format).date() 

218 return 

219 except ValueError: 

220 self.data = None 

221 

222 raise ValueError(self.invalid_value_message) 

223 

224 

225class DateTimeLocalField(DateTimeField): 

226 """ 

227 Same as :class:`~wtforms.fields.DateTimeField`, but represents an 

228 :mdn-input:`datetime-local`. 

229 

230 :param tz: 

231 Optional timezone associated with the input. The HTML 

232 ``datetime-local`` widget always renders and submits a naive 

233 local datetime; ``tz`` declares the zone in which that local 

234 datetime should be interpreted. Accepts: 

235 

236 - ``None`` (default): legacy behavior, :attr:`data` is naive. 

237 - a :class:`datetime.tzinfo` instance: parsed values get this 

238 zone attached, and aware values rendered through the field 

239 are converted to it before being formatted. 

240 - a callable returning a :class:`datetime.tzinfo` (or ``None``): 

241 resolved on each access, useful when the zone depends on the 

242 request context (e.g. user preferences). Returning ``None`` 

243 falls back to the naive behavior. 

244 

245 No correction is applied for DST gaps or overlaps — submitted 

246 values are annotated as-is via ``replace(tzinfo=...)``. 

247 """ 

248 

249 widget = widgets.DateTimeLocalInput() 

250 

251 def __init__( 

252 self, 

253 *args, 

254 tz: datetime.tzinfo | Callable[[], datetime.tzinfo | None] | None = None, 

255 **kwargs, 

256 ): 

257 kwargs.setdefault( 

258 "format", 

259 [ 

260 "%Y-%m-%d %H:%M:%S", 

261 "%Y-%m-%dT%H:%M:%S", 

262 "%Y-%m-%d %H:%M", 

263 "%Y-%m-%dT%H:%M", 

264 ], 

265 ) 

266 super().__init__(*args, **kwargs) 

267 self.tz = tz 

268 

269 def _resolve_tz(self): 

270 return self.tz() if callable(self.tz) else self.tz 

271 

272 def _value(self): 

273 """Render :attr:`data`, converting aware values to ``tz`` and stripping 

274 the zone before formatting.""" 

275 if self.raw_data: 

276 return " ".join(self.raw_data) 

277 

278 if not self.data: 

279 return "" 

280 

281 value = self.data 

282 tz = self._resolve_tz() 

283 if tz is not None and value.tzinfo is not None: 

284 value = value.astimezone(tz).replace(tzinfo=None) 

285 

286 return value.strftime(self.format[0]) 

287 

288 def process_formdata(self, valuelist): 

289 """Parse the submitted value and annotate it with ``tz`` if set.""" 

290 super().process_formdata(valuelist) 

291 tz = self._resolve_tz() 

292 if tz is not None and self.data is not None: 

293 self.data = self.data.replace(tzinfo=tz)