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

135 

136 def post_process(self): 

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()`` to keep 

142 per-field hooks running. 

143 

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

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

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

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

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

149 """ 

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

151 field.post_process() 

152 

153 def validate(self, extra_validators=None): 

154 """ 

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

156 

157 :param extra_validators: 

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

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

160 `validate` method. 

161 

162 Returns `True` if no errors occur. 

163 """ 

164 success = True 

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

166 if extra_validators is not None and name in extra_validators: 

167 extra = extra_validators[name] 

168 else: 

169 extra = tuple() 

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

171 success = False 

172 return success 

173 

174 @property 

175 def data(self): 

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

177 

178 @property 

179 def errors(self): 

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

181 if self.form_errors: 

182 errors[self._form_error_key] = self.form_errors 

183 return errors 

184 

185 

186class FormMeta(type): 

187 """ 

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

189 

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

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

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

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

194 re-generated on the next instantiation. 

195 

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

197 instances are ignored by the metaclass. 

198 """ 

199 

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

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

202 cls._unbound_fields = None 

203 cls._wtforms_meta = None 

204 

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

206 """ 

207 Construct a new `Form` instance. 

208 

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

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

211 hierarchy. 

212 """ 

213 if cls._unbound_fields is None: 

214 fields = [] 

215 for name in dir(cls): 

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

217 unbound_field = getattr(cls, name) 

218 if hasattr(unbound_field, "_formfield"): 

219 fields.append((name, unbound_field)) 

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

221 # to ensure a stable sort. 

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

223 cls._unbound_fields = fields 

224 

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

226 if cls._wtforms_meta is None: 

227 bases = [] 

228 for mro_class in cls.__mro__: 

229 if "Meta" in mro_class.__dict__: 

230 bases.append(mro_class.Meta) 

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

232 

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

234 

235 def __setattr__(cls, name, value): 

236 """ 

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

238 """ 

239 if name == "Meta": 

240 cls._wtforms_meta = None 

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

242 cls._unbound_fields = None 

243 type.__setattr__(cls, name, value) 

244 

245 def __delattr__(cls, name): 

246 """ 

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

248 needed. 

249 """ 

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

251 cls._unbound_fields = None 

252 type.__delattr__(cls, name) 

253 

254 

255class Form(BaseForm, metaclass=FormMeta): 

256 """ 

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

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

259 

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

261 and passed to `process()`. 

262 """ 

263 

264 Meta = DefaultMeta 

265 

266 def __init__( 

267 self, 

268 formdata=None, 

269 obj=None, 

270 prefix="", 

271 data=None, 

272 meta=None, 

273 **kwargs, 

274 ): 

275 """ 

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

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

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

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

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

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

282 not passed. 

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

284 prefixed with the value. This is for distinguishing multiple 

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

286 matching input data, not the Python name for matching 

287 existing data. 

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

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

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

291 passed. 

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

293 :attr:`meta` instance. 

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

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

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

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

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

299 data as parameters. Overwrites any duplicate keys in 

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

301 """ 

302 meta_obj = self._wtforms_meta() 

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

304 meta_obj.update_values(meta) 

305 super().__init__( 

306 self._unbound_fields, 

307 meta=meta_obj, 

308 prefix=prefix, 

309 ) 

310 

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

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

313 # attributes with the same names. 

314 setattr(self, name, field) 

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

316 

317 def __setitem__(self, name, value): 

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

319 

320 def __delitem__(self, name): 

321 del self._fields[name] 

322 setattr(self, name, None) 

323 

324 def __delattr__(self, name): 

325 if name in self._fields: 

326 self.__delitem__(name) 

327 else: 

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

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

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

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

332 setattr(self, name, None) 

333 else: 

334 super().__delattr__(name) 

335 

336 def validate(self, extra_validators=None): 

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

338 Returns ``True`` if validation passes. 

339 

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

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

342 

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

344 extra validator methods to run. Extra validators run after 

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

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

347 """ 

348 if extra_validators is not None: 

349 extra = extra_validators.copy() 

350 else: 

351 extra = {} 

352 

353 for name in self._fields: 

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

355 if inline is not None: 

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

357 

358 return super().validate(extra)