Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/form.py: 28%

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

144 statements  

1import itertools 

2from collections import OrderedDict 

3 

4from wtforms.meta import DefaultMeta 

5from wtforms.utils import unset_value 

6 

7__all__ = ("BaseForm", "Form") 

8 

9_default_meta = DefaultMeta() 

10 

11 

12class BaseForm: 

13 """ 

14 Base Form Class. Provides core behaviour like field construction, 

15 validation, and data and error proxying. 

16 """ 

17 

18 _parent_form = None 

19 

20 def __init__(self, fields, prefix="", meta=_default_meta): 

21 """ 

22 :param fields: 

23 A dict or sequence of 2-tuples of partially-constructed fields. 

24 :param prefix: 

25 If provided, all fields will have their name prefixed with the 

26 value. 

27 :param meta: 

28 A meta instance which is used for configuration and customization 

29 of WTForms behaviors. 

30 """ 

31 if prefix and prefix[-1] not in "-_;:/.": 

32 prefix += "-" 

33 

34 self.meta = meta 

35 self._parent_form = getattr(meta, "_parent_form", None) 

36 self._form_error_key = "" 

37 self._prefix = prefix 

38 self._fields = OrderedDict() 

39 

40 if hasattr(fields, "items"): 

41 fields = fields.items() 

42 

43 translations = self.meta.get_translations(self) 

44 extra_fields = [] 

45 if meta.csrf: 

46 self._csrf = meta.build_csrf(self) 

47 extra_fields.extend(self._csrf.setup_form(self)) 

48 

49 for name, unbound_field in itertools.chain(fields, extra_fields): 

50 field_name = unbound_field.name or name 

51 options = dict(name=field_name, prefix=prefix, translations=translations) 

52 field = meta.bind_field(self, unbound_field, options) 

53 self._fields[name] = field 

54 

55 self.form_errors = [] 

56 

57 def __iter__(self): 

58 """Iterate form fields in creation order.""" 

59 return iter(self._fields.values()) 

60 

61 def __contains__(self, name): 

62 """Returns `True` if the named field is a member of this form.""" 

63 return name in self._fields 

64 

65 def __getitem__(self, name): 

66 """Dict-style access to this form's fields.""" 

67 return self._fields[name] 

68 

69 def __setitem__(self, name, value): 

70 """Bind a field to this form.""" 

71 self._fields[name] = value.bind(form=self, name=name, prefix=self._prefix) 

72 

73 def __delitem__(self, name): 

74 """Remove a field from this form.""" 

75 del self._fields[name] 

76 

77 def populate_obj(self, obj): 

78 """ 

79 Populates the attributes of the passed `obj` with data from the form's 

80 fields. 

81 

82 :note: This is a destructive operation; Any attribute with the same name 

83 as a field will be overridden. Use with caution. 

84 """ 

85 for name, field in self._fields.items(): 

86 field.populate_obj(obj, name) 

87 

88 def process(self, formdata=None, obj=None, data=None, extra_filters=None, **kwargs): 

89 """Process default and input data with each field. 

90 

91 :param formdata: Input data coming from the client, usually 

92 ``request.form`` or equivalent. Should provide a "multi 

93 dict" interface to get a list of values for a given key, 

94 such as what Werkzeug, Django, and WebOb provide. 

95 :param obj: Take existing data from attributes on this object 

96 matching form field attributes. Only used if ``formdata`` is 

97 not passed. 

98 :param data: Take existing data from keys in this dict matching 

99 form field attributes. ``obj`` takes precedence if it also 

100 has a matching attribute. Only used if ``formdata`` is not 

101 passed. 

102 :param extra_filters: A dict mapping field attribute names to 

103 lists of extra filter functions to run. Extra filters run 

104 after filters passed when creating the field. If the form 

105 has ``filter_<fieldname>``, it is the last extra filter. 

106 :param kwargs: Merged with ``data`` to allow passing existing 

107 data as parameters. Overwrites any duplicate keys in 

108 ``data``. Only used if ``formdata`` is not passed. 

109 """ 

110 formdata = self.meta.wrap_formdata(self, formdata) 

111 

112 if data is not None: 

113 kwargs = dict(data, **kwargs) 

114 

115 filters = extra_filters.copy() if extra_filters is not None else {} 

116 

117 for name, field in self._fields.items(): 

118 field_extra_filters = filters.get(name, []) 

119 

120 inline_filter = getattr(self, f"filter_{name}", None) 

121 if inline_filter is not None: 

122 field_extra_filters.append(inline_filter) 

123 

124 if obj is not None and hasattr(obj, name): 

125 data = getattr(obj, name) 

126 elif name in kwargs: 

127 data = kwargs[name] 

128 else: 

129 data = unset_value 

130 

131 field.process(formdata, data, extra_filters=field_extra_filters) 

132 

133 if self._parent_form is None: 

134 self.post_process(formdata) 

135 

136 def post_process(self, formdata=None): 

137 """Hook called at the end of :meth:`process` on the root form. 

138 

139 Runs the :meth:`~fields.Field.post_process` hook on every field, after 

140 all fields have been processed. Override this on a form subclass to add 

141 cross-field finalization logic; call ``super().post_process(formdata)`` 

142 to keep per-field hooks running. 

143 

144 :param formdata: The resolved formdata the form was bound with 

145 (already passed through :meth:`~meta.DefaultMeta.wrap_formdata`), 

146 or ``None`` for non-formdata cycles. Forwarded to every nested 

147 ``post_process``. 

148 

149 ``post_process`` is only triggered automatically on the root form. Forms 

150 nested inside a :class:`~fields.FormField` (or via :class:`~fields.FieldList` 

151 entries) propagate the call through :meth:`fields.FormField.post_process` 

152 and :meth:`fields.FieldList.post_process`, so every nested field's 

153 ``post_process`` runs exactly once per processing cycle. 

154 """ 

155 for field in self._fields.values(): 

156 field.post_process(formdata) 

157 

158 def validate(self, extra_validators=None): 

159 """ 

160 Validates the form by calling `validate` on each field. 

161 

162 :param extra_validators: 

163 If provided, is a dict mapping field names to a sequence of 

164 callables which will be passed as extra validators to the field's 

165 `validate` method. 

166 

167 Returns `True` if no errors occur. 

168 """ 

169 success = True 

170 for name, field in self._fields.items(): 

171 if extra_validators is not None and name in extra_validators: 

172 extra = extra_validators[name] 

173 else: 

174 extra = tuple() 

175 if not field.validate(self, extra): 

176 success = False 

177 return success 

178 

179 @property 

180 def data(self): 

181 return {name: f.data for name, f in self._fields.items()} 

182 

183 @property 

184 def errors(self): 

185 errors = {name: f.errors for name, f in self._fields.items() if f.errors} 

186 if self.form_errors: 

187 errors[self._form_error_key] = self.form_errors 

188 return errors 

189 

190 

191class FormMeta(type): 

192 """ 

193 The metaclass for `Form` and any subclasses of `Form`. 

194 

195 `FormMeta`'s responsibility is to create the `_unbound_fields` list, which 

196 is a list of `UnboundField` instances sorted by their order of 

197 instantiation. The list is created at the first instantiation of the form. 

198 If any fields are added/removed from the form, the list is cleared to be 

199 re-generated on the next instantiation. 

200 

201 Any properties which begin with an underscore or are not `UnboundField` 

202 instances are ignored by the metaclass. 

203 """ 

204 

205 def __init__(cls, name, bases, attrs): 

206 type.__init__(cls, name, bases, attrs) 

207 cls._unbound_fields = None 

208 cls._wtforms_meta = None 

209 

210 def __call__(cls, *args, **kwargs): 

211 """ 

212 Construct a new `Form` instance. 

213 

214 Creates the `_unbound_fields` list and the internal `_wtforms_meta` 

215 subclass of the class Meta in order to allow a proper inheritance 

216 hierarchy. 

217 """ 

218 if cls._unbound_fields is None: 

219 fields = [] 

220 for name in dir(cls): 

221 if not name.startswith("_"): 

222 unbound_field = getattr(cls, name) 

223 if hasattr(unbound_field, "_formfield"): 

224 fields.append((name, unbound_field)) 

225 # We keep the name as the second element of the sort 

226 # to ensure a stable sort. 

227 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) 

228 cls._unbound_fields = fields 

229 

230 # Create a subclass of the 'class Meta' using all the ancestors. 

231 if cls._wtforms_meta is None: 

232 bases = [] 

233 for mro_class in cls.__mro__: 

234 if "Meta" in mro_class.__dict__: 

235 bases.append(mro_class.Meta) 

236 cls._wtforms_meta = type("Meta", tuple(bases), {}) 

237 

238 return type.__call__(cls, *args, **kwargs) 

239 

240 def __setattr__(cls, name, value): 

241 """ 

242 Add an attribute to the class, clearing `_unbound_fields` if needed. 

243 """ 

244 if name == "Meta": 

245 cls._wtforms_meta = None 

246 elif not name.startswith("_") and hasattr(value, "_formfield"): 

247 cls._unbound_fields = None 

248 type.__setattr__(cls, name, value) 

249 

250 def __delattr__(cls, name): 

251 """ 

252 Remove an attribute from the class, clearing `_unbound_fields` if 

253 needed. 

254 """ 

255 if not name.startswith("_"): 

256 cls._unbound_fields = None 

257 type.__delattr__(cls, name) 

258 

259 

260class Form(BaseForm, metaclass=FormMeta): 

261 """ 

262 Declarative Form base class. Extends BaseForm's core behaviour allowing 

263 fields to be defined on Form subclasses as class attributes. 

264 

265 In addition, form and instance input data are taken at construction time 

266 and passed to `process()`. 

267 """ 

268 

269 Meta = DefaultMeta 

270 

271 def __init__( 

272 self, 

273 formdata=None, 

274 obj=None, 

275 prefix="", 

276 data=None, 

277 meta=None, 

278 **kwargs, 

279 ): 

280 """ 

281 :param formdata: Input data coming from the client, usually 

282 ``request.form`` or equivalent. Should provide a "multi 

283 dict" interface to get a list of values for a given key, 

284 such as what Werkzeug, Django, and WebOb provide. 

285 :param obj: Take existing data from attributes on this object 

286 matching form field attributes. Only used if ``formdata`` is 

287 not passed. 

288 :param prefix: If provided, all fields will have their name 

289 prefixed with the value. This is for distinguishing multiple 

290 forms on a single page. This only affects the HTML name for 

291 matching input data, not the Python name for matching 

292 existing data. 

293 :param data: Take existing data from keys in this dict matching 

294 form field attributes. ``obj`` takes precedence if it also 

295 has a matching attribute. Only used if ``formdata`` is not 

296 passed. 

297 :param meta: A dict of attributes to override on this form's 

298 :attr:`meta` instance. 

299 :param extra_filters: A dict mapping field attribute names to 

300 lists of extra filter functions to run. Extra filters run 

301 after filters passed when creating the field. If the form 

302 has ``filter_<fieldname>``, it is the last extra filter. 

303 :param kwargs: Merged with ``data`` to allow passing existing 

304 data as parameters. Overwrites any duplicate keys in 

305 ``data``. Only used if ``formdata`` is not passed. 

306 """ 

307 meta_obj = self._wtforms_meta() 

308 if meta is not None and isinstance(meta, dict): 

309 meta_obj.update_values(meta) 

310 super().__init__( 

311 self._unbound_fields, 

312 meta=meta_obj, 

313 prefix=prefix, 

314 ) 

315 

316 for name, field in self._fields.items(): 

317 # Set all the fields to attributes so that they obscure the class 

318 # attributes with the same names. 

319 setattr(self, name, field) 

320 self.process(formdata, obj, data=data, **kwargs) 

321 

322 def __setitem__(self, name, value): 

323 raise TypeError("Fields may not be added to Form instances, only classes.") 

324 

325 def __delitem__(self, name): 

326 del self._fields[name] 

327 setattr(self, name, None) 

328 

329 def __delattr__(self, name): 

330 if name in self._fields: 

331 self.__delitem__(name) 

332 else: 

333 # This is done for idempotency, if we have a name which is a field, 

334 # we want to mask it by setting the value to None. 

335 unbound_field = getattr(self.__class__, name, None) 

336 if unbound_field is not None and hasattr(unbound_field, "_formfield"): 

337 setattr(self, name, None) 

338 else: 

339 super().__delattr__(name) 

340 

341 def validate(self, extra_validators=None): 

342 """Validate the form by calling ``validate`` on each field. 

343 Returns ``True`` if validation passes. 

344 

345 If the form defines a ``validate_<fieldname>`` method, it is 

346 appended as an extra validator for the field's ``validate``. 

347 

348 :param extra_validators: A dict mapping field names to lists of 

349 extra validator methods to run. Extra validators run after 

350 validators passed when creating the field. If the form has 

351 ``validate_<fieldname>``, it is the last extra validator. 

352 """ 

353 if extra_validators is not None: 

354 extra = extra_validators.copy() 

355 else: 

356 extra = {} 

357 

358 for name in self._fields: 

359 inline = getattr(self.__class__, f"validate_{name}", None) 

360 if inline is not None: 

361 extra.setdefault(name, []).append(inline) 

362 

363 return super().validate(extra)