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:`~wtforms.fields.Field.post_process` hook on every 

140 field, after all fields have been processed. Override this on a form 

141 subclass to add cross-field finalization logic; call 

142 ``super().post_process(formdata)`` to keep per-field hooks running. 

143 

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

145 passed through :meth:`~wtforms.meta.DefaultMeta.wrap_formdata`), or 

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

147 ``post_process``. 

148 

149 :meth:`process` only triggers ``post_process`` on the root form. Forms 

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

151 :class:`~wtforms.fields.FieldList` entries) propagate the call through 

152 ``FormField.post_process`` and ``FieldList.post_process``, so 

153 :meth:`process` runs every nested field's ``post_process`` once. 

154 

155 After changing field data, call ``post_process(formdata)`` again with 

156 the same ``formdata``, so that derived state such as ``choices`` 

157 callables reflects the new data. Overrides must therefore be 

158 idempotent: derive state from ``data``, do not reload ``data`` from 

159 ``formdata``. 

160 """ 

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

162 field.post_process(formdata) 

163 

164 def validate(self, extra_validators=None): 

165 """ 

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

167 

168 :param extra_validators: 

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

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

171 `validate` method. 

172 

173 Returns `True` if no errors occur. 

174 """ 

175 success = True 

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

177 if extra_validators is not None and name in extra_validators: 

178 extra = extra_validators[name] 

179 else: 

180 extra = tuple() 

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

182 success = False 

183 return success 

184 

185 @property 

186 def data(self): 

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

188 

189 @property 

190 def errors(self): 

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

192 if self.form_errors: 

193 errors[self._form_error_key] = self.form_errors 

194 return errors 

195 

196 

197class FormMeta(type): 

198 """ 

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

200 

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

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

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

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

205 re-generated on the next instantiation. 

206 

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

208 instances are ignored by the metaclass. 

209 """ 

210 

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

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

213 cls._unbound_fields = None 

214 cls._wtforms_meta = None 

215 

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

217 """ 

218 Construct a new `Form` instance. 

219 

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

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

222 hierarchy. 

223 """ 

224 if cls._unbound_fields is None: 

225 fields = [] 

226 for name in dir(cls): 

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

228 unbound_field = getattr(cls, name) 

229 if hasattr(unbound_field, "_formfield"): 

230 fields.append((name, unbound_field)) 

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

232 # to ensure a stable sort. 

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

234 cls._unbound_fields = fields 

235 

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

237 if cls._wtforms_meta is None: 

238 bases = [] 

239 for mro_class in cls.__mro__: 

240 if "Meta" in mro_class.__dict__: 

241 bases.append(mro_class.Meta) 

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

243 

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

245 

246 def __setattr__(cls, name, value): 

247 """ 

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

249 """ 

250 if name == "Meta": 

251 cls._wtforms_meta = None 

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

253 cls._unbound_fields = None 

254 type.__setattr__(cls, name, value) 

255 

256 def __delattr__(cls, name): 

257 """ 

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

259 needed. 

260 """ 

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

262 cls._unbound_fields = None 

263 type.__delattr__(cls, name) 

264 

265 

266class Form(BaseForm, metaclass=FormMeta): 

267 """ 

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

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

270 

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

272 and passed to `process()`. 

273 """ 

274 

275 Meta = DefaultMeta 

276 

277 def __init__( 

278 self, 

279 formdata=None, 

280 obj=None, 

281 prefix="", 

282 data=None, 

283 meta=None, 

284 **kwargs, 

285 ): 

286 """ 

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

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

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

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

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

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

293 not passed. 

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

295 prefixed with the value. This is for distinguishing multiple 

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

297 matching input data, not the Python name for matching 

298 existing data. 

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

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

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

302 passed. 

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

304 :attr:`meta` instance. 

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

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

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

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

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

310 data as parameters. Overwrites any duplicate keys in 

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

312 """ 

313 meta_obj = self._wtforms_meta() 

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

315 meta_obj.update_values(meta) 

316 super().__init__( 

317 self._unbound_fields, 

318 meta=meta_obj, 

319 prefix=prefix, 

320 ) 

321 

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

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

324 # attributes with the same names. 

325 setattr(self, name, field) 

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

327 

328 def __setitem__(self, name, value): 

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

330 

331 def __delitem__(self, name): 

332 del self._fields[name] 

333 setattr(self, name, None) 

334 

335 def __delattr__(self, name): 

336 if name in self._fields: 

337 self.__delitem__(name) 

338 else: 

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

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

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

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

343 setattr(self, name, None) 

344 else: 

345 super().__delattr__(name) 

346 

347 def validate(self, extra_validators=None): 

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

349 Returns ``True`` if validation passes. 

350 

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

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

353 

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

355 extra validator methods to run. Extra validators run after 

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

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

358 """ 

359 if extra_validators is not None: 

360 extra = extra_validators.copy() 

361 else: 

362 extra = {} 

363 

364 for name in self._fields: 

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

366 if inline is not None: 

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

368 

369 return super().validate(extra)